I’m trying to make a Multiplayer Pong game with Photon Fusion network. I’ve followed the photon fusion tutorial from the documentation and setup my project.
I’m able to spawn the Host and control it normally. But when the client joins the session and tries to move, the movement is only reflected in the host device, but not in itself (client device). The collisions seem to be working and the ball is hit back.
The host is able to see all the gameplay, but the client can only see the host move.
Sharing my code for Network Runner Handler below,
public class NetworkRunnerHandler : MonoBehaviour, INetworkRunnerCallbacks
{
public int currentPlayer;
public int maxPlayers = 2;
public NetworkPrefabRef playerPrefab;
NetworkRunner networkRunner;
public Dictionary<PlayerRef, NetworkObject> spawnedPlayers = new Dictionary<PlayerRef, NetworkObject>();
async void GameStart(GameMode gameMode, string sessionName)
{
networkRunner = gameObject.AddComponent<NetworkRunner>();
gameObject.AddComponent<RunnerSimulatePhysics2D>();
// networkRunner.ProvideInput = true;
networkRunner.ProvideInput = gameMode != GameMode.Server;
networkRunner.AddCallbacks(this);
var scene = SceneRef.FromIndex(SceneManager.GetActiveScene().buildIndex);
var sceneInfo = new NetworkSceneInfo();
if(scene.IsValid)
sceneInfo.AddSceneRef(scene, LoadSceneMode.Additive);
await networkRunner.StartGame( new StartGameArgs()
{
GameMode = gameMode,
SessionName = sessionName,
Scene = scene,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
}
public void StartGame_UI(int state)
{
if(state == 0)
GameStart(GameMode.Host, "Feda");
else
GameStart(GameMode.Client, "Feda");
}
public void OnConnectedToServer(NetworkRunner runner){}
public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason){}
public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token)
{
if (currentPlayer >= maxPlayers)
{
request.Refuse();
}
else
{
request.Accept();
}
}
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data){}
public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason){}
public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken){}
private bool _mouseButton0;
private void Update()
{
_mouseButton0 = _mouseButton0 | Input.GetMouseButton(0);
}
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new NetworkInputData();
if (Input.GetMouseButton(0)) // Left mouse button
{
data.isMousePressed = true;
data.mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
if(Input.GetKey(KeyCode.W))
data.direction += Vector2.up;
if(Input.GetKey(KeyCode.S))
data.direction += Vector2.down;
if(Input.GetKey(KeyCode.A))
data.direction += Vector2.left;
if(Input.GetKey(KeyCode.D))
data.direction += Vector2.right;
data.buttons.Set( NetworkInputData.MOUSEBUTTON0, _mouseButton0);
_mouseButton0 = false;
input.Set(data);
}
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input){}
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player){}
public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player){}
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if(networkRunner.IsServer)
{
NetworkObject networkObject = networkRunner.Spawn(playerPrefab, spawnPos(currentPlayer), Quaternion.identity, player);
spawnedPlayers.Add(player, networkObject);
currentPlayer++;
}
}
public Vector3 spawnPos(int x)
{
if(x == 0)
return new Vector3(0, -4f, 0);
else
return new Vector3(0, 4f, 0);
}
public Vector3 spawnRot(int x)
{
if(x == 0)
return Vector3.zero;
else
return new Vector3(0, 0, 180);
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if(spawnedPlayers.TryGetValue(player, out NetworkObject networkObject))
{
networkRunner.Despawn(networkObject);
spawnedPlayers.Remove(player);
}
}
public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress){}
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment<byte> data){}
public void OnSceneLoadDone(NetworkRunner runner){}
public void OnSceneLoadStart(NetworkRunner runner){}
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList){}
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason){}
public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message){}
}
Code for PlayerController which is attached to the prefab of the player being spawned
public class PlayerController : NetworkBehaviour
{
[SerializeField] private float moveSpeed;
[SerializeField] private Rigidbody2D rb;
public override void FixedUpdateNetwork()
{
if(GetInput(out NetworkInputData data))
{
// rb.velocity = data.direction * moveSpeed * Runner.DeltaTime;
if(data.isMousePressed)
{
Vector2 targetPosition = data.mousePos;
Vector2 newPosition = Vector2.MoveTowards(rb.position, targetPosition, moveSpeed * Runner.DeltaTime);
rb.MovePosition(newPosition);
transform.position = newPosition;
}
}
}
public override void Spawned()
{
if (!Object.HasInputAuthority)
{
// Disable Rigidbody2D on non-authoritative clients to avoid conflicts
rb.isKinematic = true;
}
}
public override void Render()
{
if (!Object.HasInputAuthority)
{
rb.position = transform.position;
}
}
}
Network Input Struct
public struct NetworkInputData : INetworkInput
{
public const byte MOUSEBUTTON0 = 1;
public Vector2 direction;
public Vector2 mousePos;
public bool isMousePressed;
public NetworkButtons buttons;
}
Please let me know where am I doing this wrong?