Building an User Interface for Game Character Selection in Unity

After the backend implementation this part shows the UI design of our multiplayer character selection.

This guide is a follow-up on the article about building a real-time game character selection system using socket.io. After the back-end is ready, you will have to implement the client-side based on the server's events. This part will mostly describe how a proper UI is 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 basically has a dummy character image and a character buttons holder that helps a player interact and pick a character of their choice. It's a pretty 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


You can see that the rounded rectangular which I call a character image is an 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 the references to the UI elements and events . The class is attached to a empty Game Object. 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 player that is 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 which is to be instantiated.
  3. Main Character – the image where the currently selected image comes up
  4. Item Index – an integer type reference that is 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 highlight the selected character
  7. Deactivated Sprite – a sprite used highlight the unavailable characters

Generating the Characters

First of all we will be generating all the characters from our selection manager and place them in the avatar holder from where player will be able to select a character from a variety of 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>());

            }
        }

So in the above code, we first create a loop that runs until the end of all the items count present in our adventure character manager . Then we create a 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 some issues and won’t get updated. After saving the index we generate a charcHolder gameobject using instantiate where we pass our gameobject to be generated that is prefabHolder , set its transform to the character holder and set it’s rotation to identity . After generating the character, we get the gameobject button component and add a listener to it’s button. We also set the parent of the generated button to the avatar holder transform. After that, we set the image of the button according to the index of character manager. We then add the generated character to the characters list and also add the its highlighters to the highlighters list.

In the above code you can see 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();
}

In this function we take an integer i as an parameter. Later the index is set as the itemIndex. This function is called every time the user presses on a character button or tries switching a character. So we update the preview image of the character to the character manager’s sprite at 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);
            }
    }


}

This function handles all our network updates and it takes an array of integers as parameters because the server passes us an index of selected characters . So in the first loop what we are doing is, we are our index because we need to differentiate the selected character with other users. In the second loop we go through all the character list 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) and then we get access to the previous highlighters list where we saved all our highlighters and highlight them. For eg: If we get the index as [0,1] we highlight both of the characterButtons.

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

We need to add the above function UpdateSelectionCharacter 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 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;
         
      }
   }
}

Button highlighter is attached to the prefab of character. This class is used to highlight a gameobject when the SelectImageHighlight function is called. All this class does is it changes the sprite of highlight obj image and toggle between highlighted and non-highlighted.

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