Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Drawing Cards Replicates on host client only

Discussion in 'Multiplayer' started by x77o, Sep 13, 2023.

  1. x77o

    x77o

    Joined:
    May 28, 2021
    Posts:
    5
    Hello,
    I am making a multiplayer card game to teach myself NGO and am stuck, I have a script written where a player should be able to draw a card when their turn starts, the following is that code:

    [ServerRpc(RequireOwnership = false)]
    public void DrawCardServerRpc(int amountToDraw = 1) {

    for (int i = 0; i < amountToDraw; i++) {
    Instantiate(CardToHand, transform.position, transform.rotation);
    }
    }

    CardToHand is a prefab containing the information for the card and it is correctly put into the hand.
    The problem I have is that when I run this code no matter which client initializes the function call, the card is always drawn to the host hand and the other client hand is unchanged. Here is my code relating to the call:

    var clientId = serverRpcParams.Receive.SenderClientId;
    if (NetworkManager.ConnectedClients.ContainsKey(clientId)) {
    var client = NetworkManager.ConnectedClients[clientId];
    Player_Script pScript = (Player_Script)client.PlayerObject.GetComponent(typeof(Player_Script));
    DeckObjectScript deckObj (DeckObjectScript)GameObject.Find("DeckObject").GetComponent(typeof(DeckObjectScript));
    deckObj.DrawCardServerRpc();

    The code is able to check which client sent the request and can correctly edit parts of the playerprefab relating to the client but I can't figure out how to send the card there. I have messed around with other kinds of calls aside from serverrpc but have had no luck yet. I'm sure I'm messing up the call somehow or maybe the server needs to do more for this to work? For clarity the DrawCardServerRpc function is not in the playerprefab script but is actually attatched to the deckobject which is a gameobject with a network object function and is used to track cards.
     
  2. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    603
    Is CardToHand a network object, if it is you'll need to spawn it for it to show on the client.
    It looks like you're calling a server rpc from a server rpc so the sending client will be the host. Actually that doesn't seem to be a problem, how are you associating a card with the player?
     
    Last edited: Sep 13, 2023
  3. x77o

    x77o

    Joined:
    May 28, 2021
    Posts:
    5
    I have attached some photos of my heirachy and the relevant objects for clarity to make sure I don't misrepresent something, CardToHand is not currently a network object because I don't really need it to display on both clients currently I just want to be able to see the different cards on each client and be able to interact with them for that client. I thought that instantiating was spawning the card for the client so I don't have anything else to spawn it other than that. I may be wrong in how in my understanding of how unity works with multiple clients; in my head I thought that each client had their own 'hierarchy' so bringing things into it (instantiating) would work the same for each player with my error possibly coming from who the server thinks is making the call. I have no idea if that is correct or not and if I've been visualizing it wrong it definitely would explain a lot of the times I've become stuck haha

    The card isn't directly associated with the player, it is sent to the hand object for the client that I give to the CardToHand script, I have included that full script below. I have also thought that the error may be here in that the script is somehow locating the Hand object for the host client regardless of which client calls it and that I need to locate it differently somehow (if how I've been viewing multiple clients is wrong then that makes sense).


    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class CardToHandScript : MonoBehaviour {

    public GameObject Hand; // The player hand
    public GameObject HandCard; // Reference to a card in the players hand

    // Start is called before the first frame update
    void Start()
    {
    print(gameObject.name);

    Hand = GameObject.Find("Hand"); // Finds the panel named 'Hand' which is acting as our player hand.

    HandCard.transform.SetParent(Hand.transform); // Sets the parent of 'HandCard' to the panel 'Hand'.
    HandCard.transform.localScale = Vector3.one; // Sets the scale of 'HandCard'.
    HandCard.transform.position = new Vector3(transform.position.x, transform.position.y, 101);

    }

    }



    3.png 2.png 1.png 5.png 4.png
     
  4. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    603
    Hmm okay, I had put together something which spawned the cards but there's complications with this so I've opted with the players having a network list of card structs instead. It doesn't sound like it will fit with how your code is structured but you can change this around if need be.

    This class handles the deck of cards. If you have decks for each player you'll need something more involved. It's best the host has control of the deck/s so only they can cheat :D ideally you'll want to run the game on a dedicated server.
    Code (CSharp):
    1. public class Deck : MonoBehaviour
    2. {
    3.     public static Deck Singleton;
    4.  
    5.     List<DeckCard> deck;
    6.  
    7.     private void Awake()
    8.     {
    9.         Singleton = this;
    10.         deck = new List<DeckCard>
    11.         {
    12.             new DeckCard(Suit.Clubs, Rank.Ace),
    13.             new DeckCard(Suit.Clubs, Rank.Two),
    14.             new DeckCard(Suit.Clubs, Rank.Three)
    15.             // etc
    16.         };
    17.  
    18.         ShuffleDeck();
    19.     }
    20.  
    21.     public DeckCard DealCard()
    22.     {
    23.         DeckCard deckCard = deck[deck.Count - 1];
    24.         deck.RemoveAt(deck.Count - 1);
    25.  
    26.         return deckCard;
    27.     }
    28.  
    29.     // todo there's additional complications with this so I'll omit it for now
    30.     //public Card DealCard(ulong clientId)
    31.     //{
    32.     //    DeckCard deckCard = deck[deck.Count - 1];
    33.     //    deck.RemoveAt(deck.Count - 1);
    34.  
    35.     //    Card card = SpawnService.Instance().InstantiatePrefab<Card>();
    36.     //    card.NetworkObject.SpawnWithOwnership(clientId);
    37.     //    card.Suit = deckCard.Suit;
    38.     //    card.Rank = deckCard.Rank;
    39.  
    40.     //    card.NetworkObject.NetworkShow(clientId);
    41.  
    42.     //    return card;
    43.     //}
    44.  
    45.     private void ShuffleDeck()
    46.     {
    47.         int n = deck.Count;
    48.         for (int i = 0; i < n - 1; i++)
    49.         {
    50.             int randIndex = Random.Range(i, n);
    51.             DeckCard temp = deck[i];
    52.             deck[i] = deck[randIndex];
    53.             deck[randIndex] = temp;
    54.         }
    55.     }
    56. }
    Each player has a NetworkList of cards and they can only view their own list, you can use OnListChanged to be notified of changes to the list.

    Code (CSharp):
    1. public class DeckPlayer : NetworkBehaviour
    2. {
    3.     NetworkList<DeckCard> cards;
    4.  
    5.     private void Awake()
    6.     {
    7.         cards = new NetworkList<DeckCard>(default, NetworkVariableReadPermission.Owner);
    8.         cards.OnListChanged += OnCardListChanged;
    9.     }
    10.  
    11.     public override void OnNetworkSpawn()
    12.     {
    13.         base.OnNetworkSpawn();
    14.  
    15.         if (IsOwner)
    16.         {
    17.             GetCardServerRpc();
    18.         }
    19.  
    20.         Debug.Log($"DeckPlayer {OwnerClientId} cards: " + cards.Count);
    21.     }
    22.  
    23.     private void OnCardListChanged(NetworkListEvent<DeckCard> changeEvent)
    24.     {
    25.         Debug.Log($"DeckPlayer {OwnerClientId} OnCardListChanged type: {changeEvent.Type} index: {changeEvent.Index} value: {changeEvent.Value} count: {cards.Count}");
    26.  
    27.         Debug.Log($"DeckPlayer card suit: {changeEvent.Value.Suit} rank: {changeEvent.Value.Rank}");
    28.     }
    29.    
    30.     [ServerRpc]
    31.     private void GetCardServerRpc()
    32.     {
    33.         DeckCard deckCard = Deck.Singleton.DealCard();
    34.  
    35.         cards.Add(deckCard);
    36.     }
    37. }

    The struct for the cards.
    Code (CSharp):
    1. public struct DeckCard : INetworkSerializeByMemcpy, IEquatable<DeckCard>
    2. {
    3.     public readonly Suit Suit;
    4.     public readonly Rank Rank;
    5.  
    6.     public DeckCard(Suit suit, Rank rank)
    7.     {
    8.         Suit = suit;
    9.         Rank = rank;
    10.     }
    11.  
    12.     public bool Equals(DeckCard other)
    13.     {
    14.         return other.Rank == Rank && other.Suit == Suit;
    15.     }
    16. }
    Hopefully you can bend that code around a bit to fit with what you're doing, or change your code to fit if it's more preferable.