com.unity.netcode.gameobjects icon indicating copy to clipboard operation
com.unity.netcode.gameobjects copied to clipboard

Client Rpc invoked on the wrong client

Open adham1177 opened this issue 2 years ago • 4 comments

This template is for issues not covered by the other issue categories. public void Bridge(int cardId , ulong number) { Debug.Log("Reached server 2"); ClientRpcParams clientRpcParams = new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = new List { 1 } } }; RevealOpponentCardClientRpc(cardId , clientRpcParams); } [ClientRpc] public void RevealOpponentCardClientRpc(int cardId, ClientRpcParams clientRpcParams) { Debug.Log("Reached Client"); clientController.RevealOpponentCard(cardId); }

When experimenting with these two functions, the "Bridge" function appears to be invoked twice at the server. Additionally, the "RevealOpponentCardClientRpc" should ideally be invoked twice at the client with ID 1, as a clientRpcParams with ID 1 is sent. However, there seems to be a discrepancy where the "RevealOpponentCardClientRpc" is executed once for client ID 1 and once for client ID 0. This behavior raises the question of why this is occurring. Can anyone provide insight into the possible reasons behind this phenomenon?

adham1177 avatar Aug 02 '23 19:08 adham1177

@adham1177 What component/script invokes the Bridge method? I only seen the RPC and the Bridge method but not what invoke the Bridge method.

NoelStephensUnity avatar Aug 02 '23 19:08 NoelStephensUnity

I invoke it in a serverRpc like this [ServerRpc(RequireOwnership = false)] public void OnRevealCardPhaseServerRpc(int CardId, ulong otherClient) {

        serverController.Bridge(CardId, otherClient);
        
    }

adham7711 avatar Aug 02 '23 20:08 adham7711

Ok, so basically:

  • ? Something ? invokes OnRevealCardPhaseServerRpc
    • OnRevealCardPhaseServerRpc invokes the serverController.Bridge
      • serverController.Bridge invokes RevealOpponentCardClientRpc
        • RevealOpponentCardClientRpc invokes clientController.RevealOpponentCard(cardId)

However Bridge is being invoked twice on the server? I would possibly need to see the project to find where the issue is... the code you have provided doesn't seem to be problematic.

However, I might suggest a few pointers: Use ServerRpcParams in place of sending the client identifier, this way you only need to pass in the card id and not send the ulong of the player. ServerRpcParams is automatically filled by NGO (i.e. the server already know who sent the RPC) and so you get the sender's client id "for free" without the cost of sending it.

    public void RevealCardId(int cardId)
    {
        OnRevealCardPhaseServerRpc(cardId);
    }

    [ServerRpc(RequireOwnership = false)]
    public void OnRevealCardPhaseServerRpc(int CardId, ServerRpcParams serverRpcParams = default)
    {        
        if (serverRpcParams.Receive.SenderClientId == NetworkManager.ServerClientId)
        {
            Debug.Log($"The host-server invoked OnRevealCardPhaseServerRpc on itself!");
        }
        else
        {
            Debug.Log($"[Client-{serverRpcParams.Receive.SenderClientId}] invoked OnRevealCardPhaseServerRpc.");
        }
        serverController.Bridge(CardId, serverRpcParams.Receive.SenderClientId);
    }

This will tell you who is invoking the entry point in that chain of events to see if the host is invoking it too.

The other thing you might contemplate (if visibility is not bound to just a single player) is to just use a NetworkList that contains card information and is configured with owner write permissions so the player just updates their card and the update is automatically propagated to the server/host and all other players. Sort of like this (pseudo code):

public class MyCardPlayer : NetworkBehaviour
{
    public struct CardInfo : INetworkSerializable, IEquatable<CardInfo>
    {
        public bool IsVisible;
        int CardIdOrType; // Adjust whatever you need to identify the card

        public bool Equals(CardInfo other)
        {
            return other.CardIdOrType == CardIdOrType && other.IsVisible == IsVisible;
        }

        public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
        {
            serializer.SerializeValue(ref IsVisible);
            serializer.SerializeValue(ref CardIdOrType);
        }
    }    

    private NetworkList<CardInfo> m_PlayerCards = new NetworkList<CardInfo>(null, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);

    // Use to set visibility
    public void UpdatePlayerCard(CardInfo cardInfo)
    {
        var cardIndex = m_PlayerCards.IndexOf(cardInfo);
        m_PlayerCards[cardIndex] = cardInfo;
    }

    // Add a card to player's hand
    public void AddPlayerCard(CardInfo cardInfo)
    {
        if (!m_PlayerCards.Contains(cardInfo))
        {
            m_PlayerCards.Add(cardInfo);
        }
    }

    //Drop/Remove a card from the player's hand
    public void DropPlayerCard(CardInfo cardInfo) 
    {
        if (m_PlayerCards.Contains(cardInfo))
        {
            m_PlayerCards.Remove(cardInfo);
        }
    }

    public override void OnNetworkSpawn()
    {
        if (!IsOwner)
        {
            m_PlayerCards.OnListChanged += M_PlayerCards_OnListChanged;
        }
        base.OnNetworkSpawn();
    }

    private void M_PlayerCards_OnListChanged(NetworkListEvent<CardInfo> changeEvent)
    {
        foreach(var card in m_PlayerCards)
        {
            if (card.IsVisible)
            {
                // invoke method that makes the card visible
            }
        }
    }
}

NoelStephensUnity avatar Aug 02 '23 22:08 NoelStephensUnity

You're welcome for the previous guidance.

In my project, I have an empty game object that acts as a container for a network object. This object includes two essential scripts: the client controller and the server controller. The client controller is responsible for invoking the bridge function, while the server controller contains both the bridge function and the RevealOpponentCardClientRpc function.

Interestingly, when I place the RevealOpponentCardClientRpc function within the same script as the client controller, it operates as expected. However, if I attempt to call this function from the server controller script, it produces incorrect results.

adham1177 avatar Aug 03 '23 08:08 adham1177