Disclaimer: I am neither a professional game developer nor am I a professional programmer. I doubt I am following best practices. This is just what worked for me at the time. Hopefully it works for you if you’re playing along while reading!
Version of Unity used: 2019.4.13f1 Personal

The previous posts in this series can be found under the CardConquest category on this blog. The github for the project can be found here. Zip archives for specific posts can be found here.
The next step in making the Unit Movement phase work in multiplayer is to allow players to view the cards of their opponents. That means new UI! New code! New scripts! Yay!
Some New UI
In the Gameplay scene, there are currently two buttons called showOpponentsHand and showOpponentsDiscard that exist but aren’t actually used for anything. To get started on making the new UI, first delete the showOpponentsDiscard button since it won’t be used. Then, rename showOpponentsHand to showOpponentCards. Select the showPlayerHandButton and use ctrl+d to duplicate it, then rename the duplicated button to hideOpponentCards.

Expand the “showOpponentCards” button and select its Text child object. Set the text to “Show Opponent Cards” and the font size to 31.

Select the hideOpponentCards button. Click the “-” sign under On Click to remove its On Click function.

Then expand the hideOpponentCards button and select its text child object. Set the text to “Hide Opponent Cards” and the font size to 31.

The last thing to do will be to create a button prefab. Duplicate the hideOpponentCards button. Rename the button to gamePlayerHandButton, and then save it as a prefab in the OfflinePrefabs directory.

In the OfflinePrefabs directory, duplicate the gamePlayerHandButton prefab and rename it to gamePlayerDiscardButton.

With the prefabs saved, you can now remove the gamePlayerHandButton from the scene and you can deactivate the hideOpponentCardsButton in the hierarchy.
Accessing the Buttons in GameplayManager
In GameplayManager.cs, you should already have references to some of the Unit Movement UI. The showPlayerHandButton and hidePlayerHandButton are referenced with the following variables:
[SerializeField]
private GameObject showPlayerHandButton, hidePlayerHandButton;
This can be replaced with the following variables to reference the show/hide player hand buttons, as well as the other buttons that will be used to view/hide cards in the Unit Movement UI.
[Header("Your Hand Buttons")]
[SerializeField] private GameObject showPlayerHandButton;
[SerializeField] private GameObject hidePlayerHandButton;
[SerializeField] private GameObject showPlayerDiscardButton;
[Header("Other Player Hand Buttons")]
[SerializeField] private GameObject showOpponentCardButton;
[SerializeField] private GameObject hideOpponentCardButton;
[SerializeField] private GameObject opponentHandButtonPrefab;
[SerializeField] private GameObject opponentDiscardButtonPrefab;
public List<GameObject> opponentHandButtons = new List<GameObject>();

Save GameplayManager.cs and go back into the Unity Editor. Select the GameplayManager object and make sure that the button objects are attached to the appropriate variables. Remember, the Opponent hand/discard buttons are the button prefabs.

Button Scripts
GameplayManager will next be used to spawn the gamePlayerButton prefabs. But first a script will be created for the gamePlayerHandButton prefab so that GameplayManager has something to interact with and do stuff with. The script will be used to store some information about what GamePlayer object the button will reference, as well as the code for what to do when the button is clicked.
First, create a new script called OpponentHandButtonScript.cs

Select the gamePlayerHandButton prefab, click on “Add Component” then search for and add the OpponentHandButtonScript to the button.

Open OpponentHandButtonScript.cs in VisualStudio. Some variables will be added to store information that relates to the gameplayer the button is associated with. There will also be a myPlayerHand variable to store the gameobject of the player hand the button will reference when showing/hiding the opponent cards. Finally, a FindOpponentHand function is created to find the playerhand that the button is associated with. Code is shown below.
public class OpponentHandButtonScript : MonoBehaviour
{
public int playerHandConnId;
public string playerHandOwnerName;
public GameObject myPlayerHand;
// Start is called before the first frame update
public void FindOpponentHand()
{
GameObject[] allPlayerHands = GameObject.FindGameObjectsWithTag("PlayerHand");
foreach (GameObject playerHand in allPlayerHands)
{
PlayerHand playerHandScript = playerHand.GetComponent<PlayerHand>();
if (playerHandScript.ownerConnectionId == playerHandConnId && playerHandScript.ownerPlayerName == playerHandOwnerName)
{
myPlayerHand = playerHand;
break;
}
}
}
}

The FindOpponentHand does the following:
- Finds all the PlayerHand objects with the PlayerHand tag
- iterates through every PlayerHand
- Checks if the owner information of the PlayerHand object matches the button’s information. If they match:
- store that PlayerHand in the myPlayerHand variable
- break out of the foreach loop to end the search
- Checks if the owner information of the PlayerHand object matches the button’s information. If they match:
Spawning the Button Prefabs
Back in GameplayManager.cs, a function called CreateGamePlayerHandButtons will be used to spawn the button prefabs. The code for CreateGamePlayerHandButtons is shown below:
void CreateGamePlayerHandButtons()
{
GameObject[] allGamePlayers = GameObject.FindGameObjectsWithTag("GamePlayer");
Vector3 buttonPos = new Vector3(-175, -25, 0);
foreach (GameObject gamePlayer in allGamePlayers)
{
GamePlayer gamePlayerScript = gamePlayer.GetComponent<GamePlayer>();
GameObject gamePlayerHandButton = Instantiate(opponentHandButtonPrefab);
gamePlayerHandButton.transform.SetParent(UnitMovementUI.GetComponent<RectTransform>(), false);
buttonPos.y -= 50f;
gamePlayerHandButton.GetComponent<RectTransform>().anchoredPosition = buttonPos;
gamePlayerHandButton.GetComponentInChildren<Text>().text = gamePlayerScript.PlayerName + " Hand";
OpponentHandButtonScript gamePlayerHandButtonScript = gamePlayerHandButton.GetComponent<OpponentHandButtonScript>();
gamePlayerHandButtonScript.playerHandConnId = gamePlayerScript.ConnectionId;
gamePlayerHandButtonScript.playerHandOwnerName = gamePlayerScript.PlayerName;
gamePlayerHandButtonScript.FindOpponentHand();
opponentHandButtons.Add(gamePlayerHandButton);
GameObject gamePlayerDiscardButton = Instantiate(opponentDiscardButtonPrefab);
gamePlayerDiscardButton.transform.SetParent(UnitMovementUI.GetComponent<RectTransform>(), false);
buttonPos.y -= 50f;
gamePlayerDiscardButton.GetComponent<RectTransform>().anchoredPosition = buttonPos;
gamePlayerDiscardButton.GetComponentInChildren<Text>().text = gamePlayerScript.PlayerName + " Discard";
opponentHandButtons.Add(gamePlayerDiscardButton);
gamePlayerHandButton.SetActive(false);
gamePlayerDiscardButton.SetActive(false);
}
gamePlayerHandButtonsCreated = true;
}

CreateGamePlayerHandButtons does the following:
- Find all GamePlayer objects with the “GamePlayer” tag
- creates a “buttonPos” vector3 variable that will be used to position the spawned prefabs
- iterates through every GamePlayer object
- Gets the Gameplayer Script of the GamePlayer object
- instantiates the opponentHandButtonPrefab object as gamePlayerHandButton
- Sets the parent object of gamePlayerHandButton to the UnitMovementUI panel. It’s important that the parent object is the RectTransform of the UnitMovementUI panel so that the button spawns correctly in the scene and UI canvas
- decreases the y value of buttonPos by -50
- Sets the anchored position of gamePlayerHandButton to the value of buttonPos
- Changes the text of gamePlayerHandButton to include the GamePlayer’s player name
- Gets the OpponentButtonScript of gamePlayerHandButton
- Sets the playerHandConnId of gamePlayerHandButton
- Sets the playerHandOwnerName of gamePlayerHandButton
- Calls FindOpponentHand on gamePlayerHandButton
- FindOpponentHand will use the GamePlayer information that was just set to find the PlayerHand object associated with gamePlayerHandButton and then set gamePlayerHandButton’s myPlayerHand value
- Adds gamePlayerHandButton to the opponentHandButtons list
- Goes through and instantiates and positions the gamePlayerDiscardButton for the associated GamePlayer.
- Deactivates both of the instantiated buttons
- Sets gamePlayerHandButtonsCreated to true
I forgot to mention gamePlayerHandButtonsCreated before. Make sure that is created up top with the other variables in GameplayManager.cs

Now CreateGamePlayerHandButtons will need to be called. I decided to do it from ActivateUnitMovementUI so that the buttons are created when the rest of the UnitMovement UI is spawned. The gamePlayerHandButtonsCreated boolean variable will be used to make sure it is only spawned once.
if (!gamePlayerHandButtonsCreated)
CreateGamePlayerHandButtons();

Save all the scripts. Save the Gameplay scene in the Unity Editor. Go back to the TitleScreen scene, build and run. When you get to the Unit Movement phase in the game, you should see in your hierarchy that the gamePlayerHand buttons have spawned.

While the game is running you can go into the hierarchy and activate and deactivate buttons as necessary. If you deactivate the showPlayerHandButton and showPlayerDiscardPile buttons, and activate hideOpponentCards, gamePlayerHandButton(Clone), and gamePlayerDiscardButton(Clone), you should see the buttons in the correct positions in the UI

Viewing the Opponent’s Cards
All the UI necessary to view the opponent cards should be created. Now, it’s time to actually display the cards to the player. To start out, two functions will be added to GameplayManager.cs to Show and Hide the Opponent Hand UI elements.
First, the code for ShowOpponentHandHideUI:
public void ShowOpponentHandHideUI(GameObject buttonClicked)
{
endUnitMovementButton.SetActive(false);
resetAllMovementButton.SetActive(false);
showPlayerHandButton.SetActive(false);
unitMovementNoUnitsMovedText.gameObject.SetActive(false);
MouseClickManager.instance.ClearUnitSelection();
foreach (GameObject opponentHandButton in opponentHandButtons)
{
if (opponentHandButton != buttonClicked)
opponentHandButton.SetActive(false);
}
hideOpponentCardButton.SetActive(false);
}

ShowOpponentHandHideUI does the following:
- Takes a GameObject buttonClicked as a parameter
- This will be passed to ShowOpponentHandHideUI by the button that was clicked
- Deactivates some Unit Movement UI
- Deselects and selected units with ClearUnitSelection
- Iterates through all of the opponentHandButtons in opponentHandButtons
- If the button does not match the buttonClicked object, deactivate the button
- Deactivate the hideOpponentCardButton button
Next is the code for HideOpponentHandRestoreUI:
public void HideOpponentHandRestoreUI()
{
endUnitMovementButton.SetActive(true);
if (!LocalGamePlayerScript.ReadyForNextPhase)
{
if (haveUnitsMoved)
{
resetAllMovementButton.SetActive(true);
}
else if (!haveUnitsMoved)
{
unitMovementNoUnitsMovedText.gameObject.SetActive(true);
}
}
foreach (GameObject opponentHandButton in opponentHandButtons)
{
opponentHandButton.SetActive(true);
}
hideOpponentCardButton.SetActive(true);
}

HideOpponentHandRestoreUI will restore most of the UI that had been deactivated by ShowOpponentHandHideUI.
The last thing to add to GameplayManager.cs are two “Player Status” variables that will be used to track when the player is viewing an opponent hand, and what hand they are viewing.
[Header("Player Statuses")]
public bool isPlayerViewingOpponentHand = false;
public GameObject playerHandBeingViewed = null;

Displaying the Opponent’s Cards
OpponentHandButtonScript.cs will now be used to actually display the opponent’s cards. A function called DisplayOpponentHand will be created that will execute when the buttons is clicked. The code is shown below:
public void DisplayOpponentHand()
{
bool isEscMenuOpen = false;
try
{
isEscMenuOpen = EscMenuManager.instance.IsMainMenuOpen;
}
catch
{
Debug.Log("Can't access EscMenuManager");
}
PlayerHand myPlayerHandScript = myPlayerHand.GetComponent<PlayerHand>();
if (!myPlayerHandScript.isPlayerViewingTheirHand && !isEscMenuOpen)
{
GameplayManager.instance.isPlayerViewingOpponentHand = true;
GameplayManager.instance.playerHandBeingViewed = myPlayerHand;
this.gameObject.GetComponentInChildren<Text>().text = "Hide " + playerHandOwnerName + " Hand";
GameplayManager.instance.ShowOpponentHandHideUI(this.gameObject);
myPlayerHandScript.ShowPlayerHandOnScreen();
}
else if (myPlayerHandScript.isPlayerViewingTheirHand && !isEscMenuOpen)
{
GameplayManager.instance.isPlayerViewingOpponentHand = false;
myPlayerHandScript.HidePlayerHandOnScreen();
GameplayManager.instance.HideOpponentHandRestoreUI();
GameplayManager.instance.playerHandBeingViewed = null;
}
}

DisplayOpponentHand does the following:
- Checks if the EscMenuManager is open by storing IsMainMenuOpen from EscMenuManager in a local variable, isEscMenuOpen
- Gets the PlayerHand script of myPlayerHand
- If the EscMenu is NOT open, and if isPlayerViewingTheirHand from myPlayerHand is false:
- Set isPlayerViewingOpponentHand to TRUE in GameplayManager
- Set GameplayManager’s playerHandBeingViewed to the myPlayerHand object
- update the text of this button
- Call ShowOpponentHandHideUI from GameplayManager and provide this button as the argument
- Call ShowPlayerHandOnScreen from the myPlayerHand object to display the cards
- If isPlayerViewingTheirHand is true (meaning the opponent cards are currently displayed)
- Set isPlayerViewingOpponentHand to FALSE in GameplayManager
- Call HidePlayerHandOnScreen from the myPlayerHand object
- Call HideOpponentHandRestoreUI from GameplayManager
- Set GameplayManager’s playerHandBeingViewed to null
Back in the Unity Editor, DisplayOpponentHand from OpponentHandButtonScript will need to be added as the On Click function on the gamePlayerHandButton prefab. You can add the OpponentHandButtonScript attached to the prefab to onclick by dragging and dropping the Script object in the inspector to On Click (that sounds very confusing and I don’t know how to word it better, sorry)

Then, select the DisplayOpponentHand function as the On Click function.

Showing and Hiding All the Opponent Buttons
Last but certainly not least, you need a way to actually show the opponent hand buttons and hide them. In GameplayManager, create two new functions, ShowOpponentCards and HideOpponentCards. Code below:
public void ShowOpponentCards()
{
if (!EscMenuManager.instance.IsMainMenuOpen && !LocalGamePlayerScript.myPlayerCardHand.GetComponent<PlayerHand>().isPlayerViewingTheirHand)
{
showPlayerHandButton.SetActive(false);
showPlayerDiscardButton.SetActive(false);
showOpponentCardButton.SetActive(false);
hideOpponentCardButton.SetActive(true);
foreach (GameObject opponentHandButton in opponentHandButtons)
{
opponentHandButton.SetActive(true);
}
}
}
public void HideOpponentCards()
{
if (!EscMenuManager.instance.IsMainMenuOpen && !LocalGamePlayerScript.myPlayerCardHand.GetComponent<PlayerHand>().isPlayerViewingTheirHand)
{
showPlayerHandButton.SetActive(true);
showPlayerDiscardButton.SetActive(true);
showOpponentCardButton.SetActive(true);
hideOpponentCardButton.SetActive(false);
foreach (GameObject opponentHandButton in opponentHandButtons)
{
opponentHandButton.SetActive(false);
}
}
}

Save GameplayManager.cs and go back to the Unity Editor and open the Gameplay Scene. Select the showOpponentCards button and add the GameplayManager and the ShowOpponentCards function to its On Click.

Do the same with hideOpponentCards but use the HideOpponentCards function instead.

Save everything, build and run the game, and voila! You can view your opponent’s cards just like in this video!
Last Minute Cleanup and other stuff
One thing I want to make sure is that the UI doesn’t get too messed up when transitioning between phases/turns while players have cards open or whatever. So, in GameplayManager’s ActivateUnitMovementUI function, I added a low of checks to make sure all the UI gets reset correctly and to check if the player is viewing any cards. If they are, stop viewing them! The code is shown below:
void ActivateUnitMovementUI()
{
Debug.Log("Activating the Unit Movement UI");
if (!UnitMovementUI.activeInHierarchy && currentGamePhase == "Unit Movement")
UnitMovementUI.SetActive(true);
if (!unitMovementNoUnitsMovedText.gameObject.activeInHierarchy)
unitMovementNoUnitsMovedText.gameObject.SetActive(true);
if (!endUnitMovementButton.activeInHierarchy)
endUnitMovementButton.SetActive(true);
if (endUnitMovementButton.activeInHierarchy)
endUnitMovementButton.GetComponent<Image>().color = Color.white;
if (resetAllMovementButton.activeInHierarchy)
resetAllMovementButton.SetActive(false);
//if (hidePlayerHandButton.activeInHierarchy && !PlayerHand.instance.isPlayerViewingTheirHand)
//hidePlayerHandButton.SetActive(false);
if (hidePlayerHandButton.activeInHierarchy)
hidePlayerHandButton.SetActive(false);
if (!showPlayerHandButton.activeInHierarchy)
showPlayerHandButton.SetActive(true);
if (!showPlayerDiscardButton.activeInHierarchy)
showPlayerDiscardButton.SetActive(true);
if (!showOpponentCardButton.activeInHierarchy)
showOpponentCardButton.SetActive(true);
if (hideOpponentCardButton.activeInHierarchy)
hideOpponentCardButton.SetActive(false);
if (LocalGamePlayerScript.myPlayerCardHand.GetComponent<PlayerHand>().isPlayerViewingTheirHand)
{
LocalGamePlayerScript.myPlayerCardHand.GetComponent<PlayerHand>().HidePlayerHandOnScreen();
}
// When the movement phase begins, save the land occupied by the unit to be used in movement resets
SaveUnitStartingLocation();
if (!gamePlayerHandButtonsCreated)
CreateGamePlayerHandButtons();
if (opponentHandButtons.Count > 0)
{
foreach (GameObject opponentHandButton in opponentHandButtons)
{
opponentHandButton.SetActive(false);
}
}
if (isPlayerViewingOpponentHand && playerHandBeingViewed != null)
{
playerHandBeingViewed.GetComponent<PlayerHand>().HidePlayerHandOnScreen();
playerHandBeingViewed = null;
isPlayerViewingOpponentHand = false;
}
}

Next in MouseClickManager I wanted to add some checks to make sure player’s wouldn’t accidentally select units while view cards or after they clicked that they were “ready” for the next phase/turn. First, MouseClickManager will need to find the LocalGamePlayer. The following variables need to be added:
[Header("GamePlayers")]
[SerializeField] private GameObject LocalGamePlayer;
[SerializeField] private GamePlayer LocalGamePlayerScript;
Next, a GetLocalGamePlayer function will need to be created.
void GetLocalGamePlayer()
{
LocalGamePlayer = GameObject.Find("LocalGamePlayer");
LocalGamePlayerScript = LocalGamePlayer.GetComponent<GamePlayer>();
}
And then GetLocalGamePlayer needs to be called in the Start function.
Next, within the Update function, a bunch of boolean variables will be created in reference to cards being viewed and the player’s ready status.
bool playerViewingHand = false;
try
{
playerViewingHand = LocalGamePlayerScript.myPlayerCardHand.GetComponent<PlayerHand>().isPlayerViewingTheirHand;
}
catch
{
Debug.Log("Can't access PlayerHand");
}
bool playerViewingOpponentHand = false;
try
{
playerViewingOpponentHand = GameplayManager.instance.isPlayerViewingOpponentHand;
}
catch
{
Debug.Log("Can't access GameplayManager");
}
bool playerReadyForNextPhase = false;
try
{
playerReadyForNextPhase = LocalGamePlayerScript.ReadyForNextPhase;
}
catch
{
Debug.Log("Can't access LocalGamePlayer");
}

Then, all these bool variables can be used in the if (rayHitUnit.collider.gameObject.GetComponent().hasAuthority)
statement. The new statement will look like this:
if (rayHitUnit.collider.gameObject.GetComponent<NetworkIdentity>().hasAuthority && !playerViewingHand && !playerViewingOpponentHand && !playerReadyForNextPhase)
Then, finally, in EscMenuManager, some new checks will be made to make it so that hitting esc will also hide opponent cards. The Update function can be updated to the following:
void Update()
{
if (GameplayManager.instance.currentGamePhase == "Unit Placement")
{
if (Input.GetKeyDown(KeyCode.Escape))
{
Debug.Log("Opening the ESC menu");
IsMainMenuOpen = !IsMainMenuOpen;
escMenuPanel.SetActive(IsMainMenuOpen);
}
}
else if (GameplayManager.instance.currentGamePhase == "Unit Movement")
{
if (Input.GetKeyDown(KeyCode.Escape) && LocalPlayerHandScript.isPlayerViewingTheirHand == false && GameplayManager.instance.isPlayerViewingOpponentHand == false)
{
Debug.Log("Opening the ESC menu");
IsMainMenuOpen = !IsMainMenuOpen;
escMenuPanel.SetActive(IsMainMenuOpen);
}
else if (Input.GetKeyDown(KeyCode.Escape) && LocalPlayerHandScript.isPlayerViewingTheirHand == true && GameplayManager.instance.isPlayerViewingOpponentHand == false)
{
GameplayManager.instance.HidePlayerHandPressed();
}
else if (Input.GetKeyDown(KeyCode.Escape) && LocalPlayerHandScript.isPlayerViewingTheirHand == false && GameplayManager.instance.isPlayerViewingOpponentHand == true)
{
GameplayManager.instance.isPlayerViewingOpponentHand = false;
GameplayManager.instance.playerHandBeingViewed.GetComponent<PlayerHand>().HidePlayerHandOnScreen();
GameplayManager.instance.HideOpponentHandRestoreUI();
GameplayManager.instance.playerHandBeingViewed = null;
}
}
}

Back in GameplayManager.cs, I want to add some calls to MouseClickManager.instance.ClearUnitSelection(); to make sure units are deselected during phase/turn transitions and stuff. First, add the following to StartUnitMovementPhase:
if (MouseClickManager.instance.unitsSelected.Count > 0)
MouseClickManager.instance.ClearUnitSelection();

Then add the same again in UpdateReadyButton in the Unit Placement check

That…should be everything? Save, build and run, and test it out!
Next Steps…
Now I need to really start making new content for the game. Next step I think is for the game to detect when “battles” should occur. When the Unit Movement phase ends and two players moved units to the same tile, the game needs to detect that and initiate the “battle” phase. Sounds like fun!
Smell ya later nerds