This guide is a follow-up to the article about building a real-time game character selection system using socket.io

After the back-end is ready, you must implement the client-side code based on the server's events. This part will describe how a proper UI was implemented for a game character selection in one of our real-time multiplayer games made with Unity.

For ease, let's split the content into 2 parts.

  1. Overview: introduction to the character selection user interface
  2. Deep Into the Code: looking at the overall structure and example methods

Overview of the User Interface

The character selection has a dummy character image and a character buttons holder that helps players interact and pick a character. It's a simple interface with one image and thumbnails of images of the available characters.

Here's an overall structural view of the UI as it would look like on a 2D scene in Unity.

Blueprint of the Game Character Selection User Interface

The rounded rectangle that is a character image is a Unity UI Image, and the character buttons are generated from the character manager class we have in our project. So these are all the elements needed for the UI.

Into the Code

We start by creating a class named CharacterSelectionHandler, which is in charge of handling all the UI interactions. It holds references to the UI elements and events. The class is attached to an empty GameObject. The script has references to the following public properties that you can give reference from the Unity Editor.

CharacterSelectionHandler Class

  1. Character Index: an integer type reference to the index for a player selecting the character. This index is later used to get the previously saved character and set it
  2. Prefab Holder: a GameObject type reference that holds the prefab of the character button that will be instantiated
  3. Main Character: the image where the currently selected image comes up
  4. Item Index: an integer type reference used to get the index of selected characters from the character manager
  5. Character Holder: a rectTransform that is used as a parent for the generated character buttons
  6. Activated Sprite: a stroked image sprite used to highlight the selected character
  7. Deactivated Sprite: a sprite used to highlight the unavailable characters

Generating the Characters

First, we will generate all the characters from our selection manager and place them in the avatar holder, where players can select a character from various characters.

  void GenerateCharacters()
        {
            for (int i = 0; i < _advCharManager.items.Count; i++)
            {
                int index = i;
                GameObject charcHolder = Instantiate(prefabHolder, avatarholder.position, Quaternion.identity); //generate character prefab
                charcHolder.GetComponent<Button>().interactable = true;
                charcHolder.GetComponent<Button>().onClick.AddListener(() =>
                {
                    OnSwitchedCharacter(index);
                });
                charcHolder.transform.SetParent(avatarholder,false); //set the parent to avatar holder and worldPositionStays to false
                charcHolder.transform.GetChild(0).GetComponent<Image>().sprite= _advCharManager.GetAvatarSprite(i); // set the avatar of image to avatar sprite at i
                charactersList.Add(charcHolder); //add the character to characters list
                _highlighters.Add(charcHolder.GetComponent<ButtonHighlighter>());

            }
        }
GenerateCharacters() Function

In the code above, we first create a loop that runs until the end of all the items count present in our adventure character manager. Then, we create an int index where we save the value of i . We do this because if we directly pass the i to a delegate function, it will create issues and won't get updated.

After saving the index, we generate a charcHolder GameObject using instantiate. We pass our GameObject to be generated, that is prefabHolder, set its transform to the character holder and its rotation to identity. After generating the character, we get the GameObject button component and add a listener to its button. We also set the parent of the generated button to the avatar holder transform.

We set the image of the button according to the index of the character manager. Then we add the generated character to the characters list and also add its highlighters to the highlighters list.

In the code above, you can see the OnSwitchedCharacter function. The function looks like this:

void OnSwitchedCharacter(int i)
{
    itemIndex = i;
    mainCharacter.sprite = _advCharManager.GetMainSprite(i); //get the main character sprite from adventurecharacter manager
    SaveSelectedCharacter();
    _characterValidator.SendCurrentSelectedCharacterNameToServer();
}
OnSwitchedCharacter function 

In this function, we take an integer i as a parameter. Later the index is set as the itemIndex. This function is called every time the user presses a character button or tries switching a character.

So we update the preview image of the character to the character manager's sprite at the index i. We then save the character and send the character for validation.

public void UpdateSelectionCharacters(int[] index)
{
    int myIndex = 0;
    for (int i = 0; i < MultiplayerStatus.CurrentRoomInfo.players.Length; i++)
    {
        if (MultiplayerStatus.Player == MultiplayerStatus.CurrentRoomInfo.players[i])
        {
            myIndex = i;
            break;
        }
    }
    for (int i = 0; i < charactersList.Count; i++)
    {
        
      charactersList[i].GetComponent<ButtonHighlighter>()
          .SelectImageHighlight(false, activatedSprite, deActivatedSprite);
      charactersList[i].transform.DOScale(new Vector3(1f, 1f, 1f), 0.3f);
      
    }
    
    for (int i = 0; i < index.Length; i++)
    {
            Debug.Log("Characters index is " + index[i]);
            if (index[i] != -1)
            {
                if (myIndex == i)
                {
                    charactersList[index[i]].transform.DOScale(new Vector3(1.2f, 1.2f, 1.3f), 0.3f);
                }
                _highlighters[index[i]].SelectImageHighlight(true,activatedSprite,deActivatedSprite);
            }
    }


}
UpdateSelectionCharacters() Function

This function handles all our network updates. It takes an array of integers as parameters because the server passes us an index of selected characters. We use our index in the first loop because we need to differentiate the selected character from other users.

We review all the character lists in the second loop and highlight the selected character. Then, we go through a loop of the array of integers given to us by the server and check if it's not less than -1 (which means unselected). We now get access to the previous highlighters list, where we saved all our highlighters and highlighted them.

For example: If we get the index as [0,1], we highlight both of the characterButtons.

private void Awake()
{
    _characterValidator = FindObjectOfType<OnlineCharacterValidator>();
    _characterValidator.OnCharacterSelectionUpdated += UpdateSelectionCharacters;
}
UpdateSelectionCharacter function

We need to add the above function as a listener to the character validator so that we can listen to the messages sent to us by the server and update selections every time we receive a message from the server stating that the character is changed.

Highlighting the Characters

public class ButtonHighlighter : MonoBehaviour
{
   [SerializeField] private GameObject highlightObj;
   public void SelectButton(bool val)
   {
      highlightObj.SetActive(val);
   }

   public bool IsSelected()
   {
      return highlightObj.activeInHierarchy;
   }

   public void ToggleHighlight()
   {
      highlightObj.SetActive(!IsSelected());
   }

   public void SelectImageHighlight(bool val , Sprite activatedSprite , Sprite deactivateSprite)
   {
      highlightObj.GetComponent<Button>().interactable = !val;
      if (val)
      {
         highlightObj.GetComponent<Image>().sprite = activatedSprite;
      }
      else
      {
         Debug.Log("Clearing everything");
         highlightObj.GetComponent<Image>().sprite = deactivateSprite;
         
      }
   }
}
ButtonHighlighter MonoBehavior Class

The button highlighter is attached to the prefab of the character. This class is used to highlight a GameObject when the SelectImageHighlight function is called. This class changes the sprite of the highlight obj image and toggles between highlighted and non-highlighted.

That's basically it. We now have a simple user interface that allows the users to select a character of their choice.

Thank you for reading this blog! Subscribe for more.