unity-game-engineunity3d-mirror

ClientRPC method call in Mirror does not properly keep event references for other clients


I am testing a chat system between two clients using the Mirror library for Unity. On my Player object, I attached a script which stores an event that is supposed to be called via ClientRPC.

    public delegate void ReceivedChatMessageHandler(string message);
    public event ReceivedChatMessageHandler ChatMessageReceived;

    [Client]
    public void SendChatMessage(string message)
    {
        if (!Keyboard.current.enterKey.wasPressedThisFrame) return;
        if (string.IsNullOrWhiteSpace(message)) return;
        CmdSendMessage(message);
    }

    [Command]
    private void CmdSendMessage(string message)
    {
        RpcHandleMessage(message);
    }

    [ClientRpc]
    public void RpcHandleMessage(string message)
    {
        if (ChatMessageReceived != null)
        {
            ChatMessageReceived($"[Player]: {message}\n");
        }
    }

Each player attaches an event handler to this event for their local UI, which is supposed to display the message in a chatbox. This works fine, and both players can send a message to the server, and it will pop up in their own chat (i.e. the event handlers are properly attached on both clients). However, for the client which did not send the message, ChatMessageReceived returns null, even though it is defined locally.

I looked around online, but could not really find any proper explanations as to why this happens. How can I properly pass an event generated through a ClientRPC on to other local objects which are not part of the Player GameObject itself?

Edit: The Event Listener is attached via a Script attached to the player game object. On Start of this script, i.e. when the Player Game Object is spawned in, it will find the Chatbox game object and pass the ChatInterface script (seen above) of the local player to another script.

    public void Start()
    {
        if (!isLocalPlayer) return;

        GameObject.Find("Chat").GetComponent<Chatbox>().PlayerLoaded(this.gameObject.GetComponent<ChatInterface>());
    }

The Chatbox script then attaches its own local method to the event handler.

    public void PlayerLoaded(ChatInterface chat)
    {
        this.chat = chat;
        chat.ChatMessageReceived += Receive;
    }

As said, each client receives events they send on their own client just fine, but events from other clients cause the whole event to act as if no handler was defined on it.


Solution

  • I now found an answer to my problem in an old Unity forum thread. The important part is this:

    So if Player1 sends a command "CmdMoveForward" the command is executed on the server only on the object that is accociated with Player1. Likewise when the server calls a ClientRPC methhod RpcMovePlayer on the object accociated with Player3, this method is called on all clients on the Player3 object.

    As I'm creating an event listener to the local player object, but the Client RPC is executed on the player object corresponding to the user which sent the Command to the server, the event listener is not attached to this event. This also explains why there is no problem for the client who sends the RPC, as their event listener is attached to the correct player object.

    I've solved this issue now by turning my Chatbox Script into a Singleton, so that it can easily be called from any player object. Another option to preserve the event-based architecture would be to attach the listener to every player object in the scene.