Hello everyone,
I’m working on a multiplayer game using Unity’s Netcode for GameObjects where players can edit terrain in a procedurally generated world hosted by one of the players. The editing functionality works great on a single client, but I’m facing challenges syncing these edits across all connected clients.
My initial approach was straightforward: when a player wants to make an edit, they send a ServerRpc to the host with the details of the edit (position and type). Subsequently, the host should broadcast this information to all clients using a ClientRpc, which would then execute the edit locally on each client, thus keeping the terrain state consistent across the game.
Here’s the process I intended to implement:
- Player sends a ServerRpc to the server.
- Server processes the request.
- Server sends a ClientRpc to all clients with the edit details.
- All clients receive the ClientRpc and perform the terrain edit locally.
However, the issue arises after the ServerRpc is called: the ClientRpc seems to only execute on the client that initiated the ServerRpc call. Other clients ignore the ClientRpc, failing the if (!IsOwner) return; check.
This behavior does not align with my understanding of the documentation, which suggests that a ClientRpc should execute on all clients. I’m beginning to suspect there’s a fundamental concept I’ve misunderstood about the ownership and execution flow of ClientRpc and ServerRpc methods.
Below is the code snippet illustrating the logic for the terrain editing and networking calls:
Code example
public class TerrainNetworkLogic : NetworkBehaviour
{
private myBrush brush;
private TerrainEditorUpdater updater;
private LayerMask terrainLayer;
public void Setup(myBrush b, TerrainEditorUpdater u, LayerMask t)
{
brush = b;
updater = u;
terrainLayer = t;
DebugController.Log("TerrainEditor components set on TerrainNetworkLogic " + brush + " " + updater + " " + terrainLayer);
}
private void Update()
{
if (!IsOwner) return;
if (Input.GetKeyDown(KeyCode.T))
{
TestGroundInteraction();
}
}
[Command] // command that can be called from console
private void TestGroundInteraction()
{
if (IsOwner)
{
DebugController.Log("TestGroundInteraction called");
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 1000, terrainLayer))
{
Vector3 pos = hit.point;
DebugController.Log("Hit terrain at " + pos);
EditTerrainServerRpc(pos, BrushType.Raise); // call the serverrpc so that this terrain edit can be synchronised by the server
}
}
}
[ServerRpc]
private void EditTerrainServerRpc(Vector3 pos, BrushType brushType)
{
DebugController.Log("EditTerrainServerRpc called test " + OwnerClientId + " " + pos + " " + brushType);
testClientRpc(pos, BrushType.Raise); // simply forward this information too all clients, so the terrain edit can happen in everyones world
}
[ClientRpc]
private void testClientRpc(Vector3 pos, BrushType brushType)
{
if (!IsOwner) return;
DebugController.Log("testClientRpc called " + pos + " " + brushType + OwnerClientId);
PerformTerrainUpdate(pos, brushType);
}
// a local method
private void PerformTerrainUpdate(Vector3 pos, BrushType brushType)
{
var tmp = brush.preset; // this is never run because it's not owner
...
DebugController.Log("Performed " + brushType + " at " + pos);
}
}
Has anyone encountered a similar issue, or can anyone spot what I might be doing wrong here? I’m looking for any advice or suggestions that could help me understand this problem better and, hopefully, resolve it.
Thank you in advance for your time and help!