Projectile does not synchronize in multiplayer

Godot Version

4.6.3

Question

I am currently makeing a LAN multiplayer game where players should be able to throw boomeangs.

However, although I have done necessary configurations, when player A throws a boomerang, player B can not see it.

The Boomerang(RigidBody3D) scene has a MultiplayerSynchronizer child synchronizing the boomerang’s position, rotation, and scale. And I added a MultiplayerSpawner for spawning Boomerang.

The method to create a boomerang looks like this. It is written in class Player.

public void ThrowBoomerang(float strength)
{
    if (!IsMultiplayerAuthority()) return;
    if (BoomerangAmount <= 0) return;

    BoomerangAmount--;

    Boomerang = _boomerangScene.Instantiate<Boomerang>();
    Boomerang.OwnerName = this.Name;
    Boomerang.Name = this.Name + "Boomerang";
    _boomerangContainer.AddChild(Boomerang);// _boomerangContainer refers to node /root/Root/BoomerangContainer
    Boomerang.GlobalPosition = this.GlobalPosition + Vector3.Forward.Rotated(Vector3.Up, Rotation.Y + Mathf.Pi) * 2;
    Boomerang.ApplyImpulse(Vector3.Forward.Rotated(Vector3.Up, Rotation.Y + Mathf.Pi) * strength);
}

Is there anything wrong I have done to cause the problem? I have tried RPC the ThrowBoomerang method, but when I do it it just turns out that when player A clicks the throw button, the player A would not throw anything, but instead player B would throw a boomerang, which is even more confuisng.

I would gratefully appreciate any help! :wink:

The spawner will create new instances at position 0,0,0 it does not understand that you’ve assigned global position nor applied an impulse, only that something has spawned. Making use of the MultiplayerSpawner’s spawn function may be more useful.

The MultiplayerSynchronizer may not have authority to change the boomerang’s position, and may be fighting the rigid body’s physics. Ensure the authority is set correctly and try freezing the rigid body if not the network authority?

Thank you very much! I found that if I add Rpc annotations on all method this problem can be solved.

The solution is to Rpc everything.

public partial class BoomerangContainer : Node3D
{
    [Export] private PackedScene _boomerangScene;

    private Dictionary<string, Boomerang> _boomerangs = new();

    [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
    public void SpawnBoomerang(string playerName)
    {
        if(!Multiplayer.IsServer()) return;
        Boomerang boomerang = _boomerangScene.Instantiate<Boomerang>();
        AddChild(boomerang, true);
        boomerang.OwnerName = playerName;
        _boomerangs[playerName] = boomerang;
    }

    [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
    public void SetBoomerangGlobalPosition(string playerName, Vector3 position)
    {
        if (!Multiplayer.IsServer()) return;
        if (!_boomerangs.ContainsKey(playerName)) return;
        _boomerangs[playerName].GlobalPosition = position;
    }

    [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
    public void ApplyForceToBoomerang(string playerName, Vector3 force)
    {
        if (!Multiplayer.IsServer()) return;
        if (!_boomerangs.ContainsKey(playerName)) return;
        _boomerangs[playerName].ApplyForce(force);
    }


    [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
    public void ApplyForceToBoomerangTowardPosition(string playerName, Vector3 position, float strength)
    {
        if (!Multiplayer.IsServer()) return;
        if (!_boomerangs.ContainsKey(playerName)) return;
        var boomerang = _boomerangs[playerName];
        boomerang.ApplyForce((position - boomerang.GlobalPosition).Normalized() * strength);
    }

    [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
    public void ApplyImpulseToBoomerang(string playerName, Vector3 impulse)
    {
        if (!Multiplayer.IsServer()) return;
        if (!_boomerangs.ContainsKey(playerName)) return;
        _boomerangs[playerName].ApplyImpulse(impulse);
    }

    [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
    public void FreeBoomerang(string playerName)
    {
        if (!Multiplayer.IsServer()) return;
        if (!_boomerangs.ContainsKey(playerName)) return;
        _boomerangs[playerName].QueueFree();
        _boomerangs.Remove(playerName);
    }
}