I’m trying to learn more about multiplayer games by developing my very own game using unity NetCode. The problem started from my need to create my own custom Rpc like communication between the players, as my game is different from the typical ones supported by NetCode.
the idea is for an online 2D mobile “fighting games” between two players. When both of them spawn the server will signal for them to start the game, by each creating a player and enemy object on their screen. When one player swipe on their screen their player object will move, message the server that it moved to the new position, so the server can approve it or correct any illegal move, and update the other layer that their enemy object should move to a new position. By this the server is supposed to ‘sync’ the two players movements, by moving the enemy object according to the other player’s player object movement.
Every thing works fine except the part of updating the enemy of the other player.
I have three custom named channels in this class:
{
private static PlayerMovementMessages _instance;
private static readonly object padlock = new object();
private CustomMessagingManager.HandleNamedMessageDelegate serverApproval;
private CustomMessagingManager.HandleNamedMessageDelegate playerRequestMove;
private CustomMessagingManager.HandleNamedMessageDelegate serverUpdate;
private PlayerMovementMessages() {}
public static PlayerMovementMessages Instance()
{
lock(padlock)
{
if(_instance == null)
{
_instance = new PlayerMovementMessages();
}
return _instance;
}
}
public void setServerReceiver(CustomMessagingManager.HandleNamedMessageDelegate playerRequestMove)
{
this.playerRequestMove = playerRequestMove;
if(this.serverApproval != null && this.playerRequestMove != null && this.serverUpdate != null)
{
registerNetMessages();
}
}
public void setPlayerReceiver(CustomMessagingManager.HandleNamedMessageDelegate serverApproval)
{
this.serverApproval = serverApproval;
if(this.serverApproval != null && this.playerRequestMove != null && this.serverUpdate != null)
{
registerNetMessages();
}
}
public void setEnemyReceiver(CustomMessagingManager.HandleNamedMessageDelegate serverUpdate)
{
this.serverUpdate = serverUpdate;
if(this.serverApproval != null && this.playerRequestMove != null && this.serverUpdate != null)
{
registerNetMessages();
}
}
private void registerNetMessages()
{
//note: custome messages are not two-way, so two channels are needed to complete the transaction
//a channel for the player to request a move from the server
NetworkManager.Singleton.CustomMessagingManager.RegisterNamedMessageHandler("movePlayer", playerRequestMove);
//a channel for the server to send to the player if the move was accepted
NetworkManager.Singleton.CustomMessagingManager.RegisterNamedMessageHandler("approvedMove", serverApproval);
//a channel for the server to send target position update to the Enemy
NetworkManager.Singleton.CustomMessagingManager.RegisterNamedMessageHandler("serverUpdate", serverUpdate);
Debug.Log("Messgaes channels ready");
}
public void UnrigesterNetMessages()
{
NetworkManager.Singleton.CustomMessagingManager.UnregisterNamedMessageHandler("movePlayer");
NetworkManager.Singleton.CustomMessagingManager.UnregisterNamedMessageHandler("approvedMove");
NetworkManager.Singleton.CustomMessagingManager.UnregisterNamedMessageHandler("serverUpdate");
}
//syrialize the struct to byte array so it can be send in the custom message
public static byte[] structToByte(System.Object obj)
{
string json = JsonUtility.ToJson(obj);
return System.Text.Encoding.UTF8.GetBytes(json);
}
//desyrialize (turn the byte array) to an object to use what was sent in the custom message
public static System.Object byteToStruct(byte[] byteArray)
{
string json = System.Text.Encoding.UTF8.GetString(byteArray);
return JsonUtility.FromJson<Vector3>(json);
}
//player send through movePlayer channel a move for the server to approve it
public void requestMove(Vector3 targetPosition)
{
byte[] serializedData = PlayerMovementMessages.structToByte(targetPosition);
var manager = NetworkManager.Singleton.CustomMessagingManager;
var writer = new FastBufferWriter(FastBufferWriter.GetWriteSize(serializedData), Allocator.Temp);
using(writer)
{
writer.WriteValueSafe(serializedData);
//send to server only once
manager.SendNamedMessage("movePlayer", NetworkManager.ServerClientId , writer, NetworkDelivery.Reliable);
}
}
//server sends through approvedMove the move ot approved to the player
public void approveMove(Vector3 approved, ulong clientID, ulong enemyID)
{
byte[] serializedData = PlayerMovementMessages.structToByte(approved);
var manager = NetworkManager.Singleton.CustomMessagingManager;
var writer = new FastBufferWriter(FastBufferWriter.GetWriteSize(serializedData), Allocator.Temp);
using(writer)
{
writer.WriteValueSafe(serializedData);
//send to Client only once
manager.SendNamedMessage("approvedMove", clientID , writer, NetworkDelivery.Reliable);
}
updateEnemyMove(approved, enemyID);
}
//server sends to the Enemy an update of the target position
//after the player requested a move
public void updateEnemyMove(Vector3 newPosition, ulong enemyID)
{
Debug.Log("server sent update to enemy " + enemyID);
newPosition.y = 2.7f;
byte[] serializedNewPosition = structToByte(newPosition);
var manager = NetworkManager.Singleton.CustomMessagingManager;
var writer = new FastBufferWriter(FastBufferWriter.GetWriteSize(serializedNewPosition), Allocator.Temp);
using(writer)
{
writer.WriteValueSafe(serializedNewPosition);
//send to Client only once
manager.SendNamedMessage("serverUpdate", enemyID , writer, NetworkDelivery.Reliable);
}
}
}
this is the relevant code for the server
public class Server : NetworkBehaviour
{
private List<ulong> players = new List<ulong>();
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
if(!IsServer)
{
this.enabled = false;
return;
}
//Debug.Log("server spawned");
NetworkManager.Singleton.OnClientConnectedCallback += OnPlayerJoined;
PlayerMovementMessages.Instance().setServerReceiver(playerRequestMove);
}
public void playerRequestMove(ulong senderID, FastBufferReader messagePayload)
{
if(!IsServer)
return;
Vector3 requested;
byte[] reseivedData;
messagePayload.ReadValueSafe(out reseivedData);
requested = (Vector3)PlayerMovementMessages.byteToStruct(reseivedData);
ulong enemyID = getEnemyID(senderID);
if(isIligibale(requested))
{
Debug.Log("server approved");
}else
{
Debug.Log("server did not approved");
requested = correctPlayerPosition(requested);
}
PlayerMovementMessages.Instance().approveMove(requested, senderID, enemyID);
}
private ulong getEnemyID(ulong senderID)
{
if(players.Count <2)
{
Debug.Log("*** server don't have two players in it's list ***");
return 0;
}
if(senderID == players[0])
return players[1];
else
return players[0];
}
this is the enemy script:
public class NetEnemy : MonoBehaviour
{
private Vector3 targetPosition;
void Start()
{
PlayerMovementMessages.Instance().setEnemyReceiver(serverUpdate);
targetPosition = transform.position;
}
//Enemy receive target position update from the server
private void serverUpdate(ulong senderId, FastBufferReader messagePayload)
{
Debug.Log("enemy received update from " + senderId);
byte[] reseivedData;
messagePayload.ReadValueSafe(out reseivedData);
Vector3 newPosition = (Vector3)PlayerMovementMessages.byteToStruct(reseivedData);
updateTargetPosition(newPosition);
}
private void updateTargetPosition(Vector3 newPosition)
{
targetPosition = newPosition;
Debug.Log(NetworkManager.Singleton.LocalClientId + " enemy target position are now " + targetPosition);
}
the weird thing is when I test the script where I pass the enemyId to the enemy update function as the same clientID ( so updating the enemy of same player 0 who made the move) it works, and the enemy moves with the player object (with all the correct Debug.Log prints), but when I try to pass the enemyID of the other player, the Debug prints stop at ‘server sent to update enemy 1’, and the enemy simply don’t receive any messages (the function doesn’t run). That’s why I believe I’m using the custom messages wrong.
Can someone please help me understand what I’m doing wrong? and how to achieve what I’m looking for?
