I struggle to display the turnUI objects only for the player with the turn.
Objective; make turnUI objects only visible when player.HasTurn == true. and only visible on the specific player's screen who has the turn.
I'm exploring now unity Netcode NGO through a simple multiplayer card game.
The project is server authoritative and where separate game and UI logic are separated... to the best of my knowledge.
I try to activate the turnUI objects through the hasTurn attribute. However, the turn ui objects currently appear all clients screens as well as on the server. I tried different approaches, with and without ClientRpc, I couldn't managed to display the turnui for the player with the turn alone.
The flow of the hasTurn attribute:
The networkvariable and the event are a good starting point to display and update the hasTurn on all screen. I didn't manage though to find the condition to display it only on the player with the turn. I tried to use IsOwner,it didn'to help. I suppose that as a bool, hasTurn is a state and isn't owned by the Player. I also tried IsLocalPlayer.
After banging my head against the wall, I ask for help. Any input, reference will be highly appreciated.
Here is a short data flow of the hasTurn followed by full scripts, in case it is needed.
In short, when spawned all players have hasTurn false. At a certain time, PlayerManager assign a turn to a random player, thus changing the networkvariable HasTurn to true.
public NetworkVariable<bool> HasTurn = new NetworkVariable<bool>(false);
This triggers the event in
public override void OnNetworkSpawn()
{
playerUI = GetComponent<PlayerUI>();
HasTurn.OnValueChanged += OnHasTurnChanged;
OnHasTurnChanged(false, HasTurn.Value);
}
which then calls:
private void OnHasTurnChanged(bool oldValue, bool newValue)
{
Debug.Log($"[OnHasTurnChanged] Player {PlayerName.Value}, oldValue: {oldValue}, newValue: {newValue}");
// This ensures the UI update is only called for the player who owns this Player object.
if (IsLocalPlayer)
{
Debug.Log($"[OnHasTurnChanged] Locally updating turn UI for {PlayerName.Value}");
playerUI?.UpdateHasTurnUI(this);
}
}
in playerui.cs:
public void UpdateHasTurnUI(Player player)
{
Debug.Log($"[UpdateHasTurnUI] Attempting to update UI for Player {player.PlayerName.Value} with HasTurn: {player.HasTurn.Value}");
if (player.HasTurn.Value && player == currentPlayer)
{
ActivateTurnUI(true);
}
else
{
ActivateTurnUI(false);
}
}
private void ActivateTurnUI(bool activate)
{
if (hasTurnIndicator != null)
{
hasTurnIndicator.SetActive(activate);
}
cardsDropdown.gameObject.SetActive(activate);
playersDropdown.gameObject.SetActive(activate);
guessButton.gameObject.SetActive(activate);
}
When these turn UI objects are activated they provide some data, I don't detail this as it isn't in the scope of my question.
The full scripts player and playerui.
using Unity.Collections;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Linq;
public class PlayerUI : MonoBehaviour
{
// Personal UI Elements
#region Personal
[SerializeField] private TextMeshProUGUI playerNameText;
[SerializeField] private Image playerImage;
[SerializeField] private TextMeshProUGUI scoreText;
#endregion
// Turn UI Elements
#region Turn
[SerializeField] private TMP_Dropdown cardsDropdown;
[SerializeField] private TMP_Dropdown playersDropdown;
[SerializeField] private GameObject hasTurnIndicator;
[SerializeField] private Button guessButton;
#endregion
// Hand UI Elements
#region Hand
[SerializeField] private Transform cardDisplayTransform;
#endregion
//lists to retreive cards value in the guessbuttonclickhandler
private List<Card> CardsPlayerCanAsk;
private List<Player> PlayerToAsk;
private List<int> playerIDs = new List<int>();
private List<int> cardIDs = new List<int>();
private const string DefaultImagePath = "Images/character_01";
public Player currentPlayer;
public void InitializePlayerUI(string playerName, string imagePath)
{
if (playerNameText != null)
{
playerNameText.text = playerName;
}
if (!string.IsNullOrEmpty(imagePath))
{
var imageSprite = Resources.Load<Sprite>(imagePath);
if (playerImage != null && imageSprite != null)
{
playerImage.sprite = imageSprite;
}
}
}
public void InitializeTurnUI(Player player)
{
currentPlayer = player;
// Assign CardsPlayerCanAsk and PlayerToAsk lists
CardsPlayerCanAsk = player.CardsPlayerCanAsk;
PlayerToAsk = player.PlayerToAsk;
UpdatePlayersDropdown(currentPlayer.PlayerToAsk);
UpdateCardsDropdown(currentPlayer.CardsPlayerCanAsk);
if (!player.HasTurn.Value)
{
cardsDropdown.gameObject.SetActive(false);
playersDropdown.gameObject.SetActive(false);
guessButton.gameObject.SetActive(false);
}
else
{
cardsDropdown.gameObject.SetActive(true);
playersDropdown.gameObject.SetActive(true);
guessButton.gameObject.SetActive(true);
}
}
public void UpdatePlayerHandUIWithIDs(List<int> cardIDs)
{
foreach (Transform child in cardDisplayTransform)
{
child.gameObject.SetActive(false);
}
Debug.Log("playerui UpdatePlayerHandUIWithIDs is called");
foreach (int cardID in cardIDs)
{
Debug.Log("playerui UpdatePlayerHandUIWithIDs is called in the loop");
CardUI cardUI = CardManager.Instance.FetchCardUIById(cardID);
if (cardUI != null)
{
cardUI.gameObject.SetActive(true);
cardUI.transform.SetParent(cardDisplayTransform, false);
}
else
{
Debug.LogWarning($"No CardUI found for card ID: {cardID}");
}
}
}
public void UpdateScoreUI(int score)
{
if (scoreText != null)
{
scoreText.text = "Score: " + score.ToString();
}
}
public void UpdateHasTurnUI(Player player)
{
Debug.Log($"[UpdateHasTurnUI] Attempting to update UI for Player {player.PlayerName.Value} with HasTurn: {player.HasTurn.Value}");
if (player.HasTurn.Value && player == currentPlayer)
{
ActivateTurnUI(true);
}
else
{
ActivateTurnUI(false);
}
}
private void ActivateTurnUI(bool activate)
{
if (hasTurnIndicator != null)
{
hasTurnIndicator.SetActive(activate);
}
cardsDropdown.gameObject.SetActive(activate);
playersDropdown.gameObject.SetActive(activate);
guessButton.gameObject.SetActive(activate);
}
public void UpdatePlayersDropdown(List<Player> updatedPlayersToAsk)
{
Debug.Log("UpdatePlayersDropdown is called");
if (updatedPlayersToAsk != null && updatedPlayersToAsk.Count > 0)
{
// Clear the current dropdown options and lists
playersDropdown.ClearOptions();
playerIDs.Clear();
// Add the player names to the dropdown and store their IDs
List<string> playerNames = updatedPlayersToAsk.Select(player =>
{
playerIDs.Add(player.PlayerDbId.Value);
return player.PlayerName.Value.ToString();
}).ToList();
playersDropdown.AddOptions(playerNames);
}
else
{
Debug.LogWarning("Updated players list is null or empty.");
}
}
public void UpdateCardsDropdown(List<Card> cards)
{
if (cardsDropdown != null)
{
cardsDropdown.ClearOptions(); // Clearing the dropdown options
cardIDs.Clear(); // Clearing the associated IDs list
if (cards != null)
{
List<string> cardNames = new List<string>();
foreach (Card card in cards)
{
cardIDs.Add(card.cardId.Value);
string cardName = card.cardName.Value.ToString();
cardNames.Add(cardName);
Debug.Log($"Added card name: {cardName} to dropdown options.");
}
Debug.Log($"Options passed to cardsDropdown: {string.Join(", ", cardNames)}");
cardsDropdown.AddOptions(cardNames);
}
else
{
Debug.LogWarning("UpdateCardsDropdown - cards is null");
}
}
}
}
public NetworkVariable<FixedString128Bytes> PlayerName = new NetworkVariable<FixedString128Bytes>();
public NetworkVariable<int> PlayerDbId = new NetworkVariable<int>();
public NetworkVariable<FixedString128Bytes> PlayerImagePath = new NetworkVariable<FixedString128Bytes>();
public NetworkVariable<int> Score = new NetworkVariable<int>(0);
public NetworkVariable<int> Result = new NetworkVariable<int>(0);
public NetworkVariable<bool> IsWinner = new NetworkVariable<bool>(false);
public NetworkVariable<bool> HasTurn = new NetworkVariable<bool>(false);
public List<Card> HandCards { get; set; } = new List<Card>();
public List<Player> PlayerToAsk { get; private set; } = new List<Player>();
public List<Card> CardsPlayerCanAsk { get; private set; } = new List<Card>();
public List<Card> Quartets { get; private set; } = new List<Card>();
public event Action OnPlayerToAskListUpdated;
public event Action OnCardsPlayerCanAskListUpdated;
private PlayerUI playerUI;
public override void OnNetworkSpawn()
{
playerUI = GetComponent<PlayerUI>();
if (IsServer)
{
Score.Value = 0;
}
Score.OnValueChanged += OnScoreChanged;
HasTurn.OnValueChanged += OnHasTurnChanged;
OnScoreChanged(0, Score.Value);
OnHasTurnChanged(false, HasTurn.Value);
}
public void InitializePlayer(string name, int dbId, string imagePath)
{
if (IsServer)
{
PlayerName.Value = name;
PlayerDbId.Value = dbId;
PlayerImagePath.Value = imagePath;
UpdateServerUI(name, imagePath);
BroadcastPlayerDbAttributes();
}
}
private void UpdateServerUI(string playerName, string playerImagePath)
{
if (playerUI != null)
{
playerUI.InitializePlayerUI(playerName, playerImagePath);
}
}
public void BroadcastPlayerDbAttributes()
{
if (IsServer)
{
UpdatePlayerDbAttributes_ClientRpc(PlayerName.Value.ToString(), PlayerImagePath.Value.ToString());
}
}
[ClientRpc]
private void UpdatePlayerDbAttributes_ClientRpc(string playerName, string playerImagePath)
{
if (playerUI != null)
{
playerUI.InitializePlayerUI(playerName, playerImagePath);
}
}
public void AddCardToHand(Card card)
{
if (IsServer) {
HandCards.Add(card);
UpdatePlayerHandUI();
UpdateCardsPlayerCanAsk();
CheckForQuartets();
}
}
public void RemoveCardFromHand(Card card)
{
if (card != null && IsServer)
{
Debug.Log($"Removed card {card.cardName} from player's hand.");
UpdatePlayerHandUI();
UpdateCardsPlayerCanAsk();
}
}
//Update section
private void OnHasTurnChanged(bool oldValue, bool newValue)
{
Debug.Log($"[OnHasTurnChanged] Player {PlayerName.Value}, oldValue: {oldValue}, newValue: {newValue}");
if (IsLocalPlayer)
{
Debug.Log($"[OnHasTurnChanged] Locally updating turn UI for {PlayerName.Value}");
playerUI?.UpdateHasTurnUI(this);
}
}
public void UpdateTurnStatus(bool hasTurn)
{
HasTurn.Value = hasTurn;
Debug.Log($"[UpdateTurnStatus] Player {PlayerName.Value} hasTurn set to: {hasTurn}");
if (playerUI != null && IsLocalPlayer)
{
Debug.Log($"[UpdateTurnStatus] Updating UI for Player {PlayerName.Value}.");
playerUI.UpdateHasTurnUI(this);
}
}
/*[ClientRpc]
private void UpdateTurnUI_ClientRpc(bool hasTurn)
{
if (playerUI != null)
{
playerUI.UpdateHasTurnUI(hasTurn);
}
}*/
/*public void BroadcastPlayerDbAttributes()
{
if (IsServer)
{
UpdatePlayerDbAttributes_ClientRpc(PlayerName.Value.ToString(), PlayerImagePath.Value.ToString());
}
}*/
private void OnScoreChanged(int oldValue, int newValue)
{
// This method is called whenever Score changes
UpdateScoreUI(newValue);
}
private void UpdateScoreUI(int score)
{
if (playerUI != null)
{
playerUI.UpdateScoreUI(score);
}
}
public void IncrementScore()
{
Score.Value += 1;
}
public void UpdatePlayerHandUI()
{
List<int> cardIDs = HandCards.Select(c => c.cardId.Value).ToList();
playerUI?.UpdatePlayerHandUIWithIDs(cardIDs);
Debug.Log($"Card UpdatePlayerHandUIWithIDs was called for player {PlayerName.Value}'s HandCards list.");
}
public void SendCardIDsToClient()
{
if (IsServer)
{
int[] cardIDs = HandCards.Select(card => card.cardId.Value).ToArray();
UpdatePlayerHandUI_ClientRpc(cardIDs, OwnerClientId);
}
}
[ClientRpc]
private void UpdatePlayerHandUI_ClientRpc(int[] cardIDs, ulong targetClient)
{
if (IsOwner)
{
playerUI?.UpdatePlayerHandUIWithIDs(cardIDs.ToList());
}
}
public void UpdateCardsPlayerCanAsk()
{
if (CardsPlayerCanAsk == null)
{
CardsPlayerCanAsk = new List<Card>();
}
else
{
CardsPlayerCanAsk.Clear();
}
//var allCards = CardManager.Instance.allSpawnedCards; // Make sure this is a List<Card>
var allCardComponents = CardManager.Instance.allSpawnedCards.Select(go => go.GetComponent<Card>()).Where(c => c != null);
foreach (var card in allCardComponents)
{
if (HandCards.Any(handCard => handCard.Suit.Value == card.Suit.Value) && !HandCards.Contains(card))
{
CardsPlayerCanAsk.Add(card);
}
}
OnCardsPlayerCanAskListUpdated?.Invoke();
Debug.Log($"Player {PlayerName.Value} can ask for {CardsPlayerCanAsk.Count} cards based on suits.");
}
public void UpdatePlayerToAskList(List<Player> allPlayers)
{
PlayerToAsk.Clear();
foreach (var potentialPlayer in allPlayers)
{
if (potentialPlayer != this)
{
PlayerToAsk.Add(potentialPlayer);
}
}
OnPlayerToAskListUpdated?.Invoke(); // Raise the event
Debug.Log($"Player {PlayerName.Value} has {PlayerToAsk.Count} players to ask.");
}
//utility method section:
public void CheckForQuartets()
{...}void Update()
{
if (IsServer && Input.GetMouseButtonDown(0))
{
IncrementScoreTest();
}
}
//to remove when isn't needed anymore:
public void IncrementScoreTest()
{
Score.Value += 1;
}}