This document is about: QUANTUM 2
SWITCH TO

Character Selection


Available in the Gaming Circle and Industries Circle
Circle

Overview

The Fighting Sample implements a Character Selection mode inside Quantum itself to:

  1. Simplify the transition from Character Selection to Gameplay; and,
  2. Enforce character selection rules in a completely deterministic manner.

N.B.: This approach can be used for any game with rule based character selection (e.g.: MOBA, hero based shooters, etc…). It can also be adjusted for map selection votes at the end of a round.

Character Selection

The character selection’s visual feedback and rule set are implemented separately. The former is done in Unity and the latter in Quantum. This page will go through the different aspects of the character selection setup and how they relate to each other.

Unity

The game scene in Unity includes the following parts of the character selection:

  • The selection UI;
  • Character Selection entity prototypes; and,
  • Callbacks for visual feedback reflecting the selection decisions made by the player.

All these elements can be found in the Scene under the CharacterSelect object (Side Bar and UI Camera > Camera > Foreground UI Canvas > CharacterSelect).

CharacterSelect Parent Object
CharacterSelect object location in Scene Hierarchy.

Selection UI

The character selection is presented to the player using character profile pictures. These are composed of:

  • a circle mask
  • a square picture of the available characters
  • a colored circle to highlight the current selection

The selection UI is completely passive. The selection order and available characters it represents is driven by the Character Select prototypes and the selection highlighting is handled by the CharacterSelectCallback scripts on the Character Select Callback object.

Character Selection Prototype

The character selection UI P1 and P2 in Unity are purely visual and do not impact the underlying logic found in the CharacterSelectSystem on the Quantum side. The information needed by the CharacterSelectSystem is held by the Character Select Prototypes P1 and P2 respectively.

The Character Select Prototype is made of only two components:

  • Entity Prototype; and,
  • CharacterSelect component.

The Entity Prototype component is required for any and all types of entity prototypes, while the CharacterSelect component is specifically designed to hold the information about the selectable fighters and audio-visual feedback tied to the selection interactions.

CharacterSelect Entity Prototype Composition
Character Select Entity Prototype Composition.

Note: The Character Select Prototype is used to create an entity that will only ever be used to hold a game state in Quantum and thus does not need any visual representation in Unity; as such it does have an EntityView associated with it.

The fields configurable in the editor are:

  • Fighter Index: the PlayerRef which will be able to control that Character Select Prototype. In the Fighting Sample the indices 0 and 1 are mapped to players 1 and 2 respectively. For more information on how PlayerRefs are assigned to players, please read the documentation in Manual > Player.
  • Fighter Choices: This field holds a list of FighterDataAsset. Each FighterDataAsset represents a selectable character. N.B.: The order in which they are listed is the selection order used by the CharacterSelectSystem in Quantum. Since the Selection UI is decoupled, the order of the character profiles in the Selection UI and the FighterDataAssets in the CharacterSelect component need to be matched manually!
  • Toggle Left / Right SFX: The SFX triggered when moving the selection left / right.
  • Select SFX: The SFX triggered when a player confirms their character selection.

All other fields are set and manipulated by the CharacterSelectSystem in the quantum simulation.

Visualization of Selection

The CharacterSelectCallback script handles all player selection visualization feedback. It inherits from the SubscriptionBehaviour base class which is a simple utility parent class that handles the interfacing with the Quantum Event and Callback (un)subscription.

UI Elements Setup

Before the CharacterSelectCallback script can get to work, it needs to know of the UI elements it can / should manipulate at runtime.
First of all, the amount of CharacterSelectSets needs to be set; in the Fighting Sample it is set to 2 since it is meant for 2 players by default. Each CharacterSelectSet takes a Main Object which is the object under which the selectable character pictures placed - P1 and P2 in the case at hand - and a list of Selection Components which is the highlighting circle placed in the form of a panel alongside the character picture.

Character Select Callback Setup
Character Select Callback Setup.

N.B.: The character picture highlighting panels have to be sorted in the same as the Fighter Choices in the CharacterSelect component!

Finally, the CharacterSelectCallback requires a reference to the Main Header and Spectating Text which will be displayed while the selection is ongoing.

  • The Main Header will be presented until both players have finalized their selection.
  • The Spectating Text will be displayed after the local player has confirmed their character selection and while they are waiting for the remote player to confirm theirs.
Logic

In the case of CharacterSelectCallback, it is registering and listening to the CallbackUpdateView callback. This particular callback is triggered whenever the simulation (Quantum) hands over control to the view (Unity). At this moment, the script grabs the latest verified Frame from Quantum.Game. The frame can then be used to filter components, iterate over the resulting collection and poll their information.

N.B.: It is possible to poll from the frame state. It is, however, NOT allowed to write to it from Unity. This would be indeterministic and result in simulation desynchronisation.

Based on the information polled from the frame, the CharacterSelectCallback script can then enable or disable the necessary UI elements.

C#

protected override void SubscriptionDispatch(CallbackUpdateView result){
   var frame = result.Game.Frames.Verified;
   var charSelect = frame.Filter<CharacterSelect>();
   bool isSelecting = false;
   bool isSpectating = false;

   while (charSelect.Next(out var e, out var cs)) {
       var obj = characterSelectSets[cs.fighterIndex];
       obj.mainObject.SetActive(cs.isSelecting);

       for (int i = 0; i < obj.selectionComponents.Length; i++){
    obj.selectionComponents[i].SetActive(cs.highlightedCharacterIndex == i && frame.Number % 10 < 5);
       }

       if (cs.isSelecting != true) continue;
       if (result.Game.PlayerIsLocal(cs.playerRef)){
           isSelecting = true;
       } else {
           isSpectating = true;
       }
   }
   mainHeader.SetActive(isSelecting);
   spectatingText.SetActive(isSpectating && !isSelecting);
}

Quantum

On the quantum side, the character selection process takes place in the CharacterSelectSystem:

  1. Players are added to a list of available players;
  2. The first two players in the list are allowed to chose their characters; and,
  3. The loser is replaced with the next available player in the list.

Player assignment

The reception of a player’s RuntimePlayer data triggers the ISignalOnPlayerDataSet signal.

C#

public void OnPlayerDataSet(Frame f, PlayerRef player){
   var list = f.ResolveList(f.Global->playerOrderList);
   list.Add(player);

   var filter = f.Filter<CharacterSelect>();
   while (filter.NextUnsafe(out var e, out var cs))   {
       if (cs->isSelecting == true) continue;

       list.Remove(player);
       cs->playerRef = player;
       cs->highlightedCharacterIndex = 0;
       cs->isSelecting = true;
       cs->lastInput = InputFlag.NONE;
       break;
   }
}

The CharacterSelectSystem implements the signal and adds the player who triggered it to the global list playerOrderList held in the frame. If there is an available spot, they are assigned control over a CharacterSelect component and thus granted the ability to choose a character to participate in the next match. If none is available, they will have to wait their turn; see Winner Stays section.

Selection Validation

The CharacterSelectionSystem enforces the character selection rules on the Quantum side.

The system first ensures the selection was made in a valid space by looping the selected character through the list depending on the selection index.

C#

if ((inputFlag & InputFlag.Left) != 0) {
   cs->highlightedCharacterIndex--;
   if (cs->highlightedCharacterIndex < 0)
       cs->highlightedCharacterIndex = cs->fighterChoices.Length - 1;
   f.Events.PlayAudioEffect(FPVector2.Left, cs->toggleLeftSFX);
} else if ((inputFlag & InputFlag.Right) != 0){
   cs->highlightedCharacterIndex++;
   if (cs->highlightedCharacterIndex >= cs->fighterChoices.Length)
       cs->highlightedCharacterIndex = 0;
   f.Events.PlayAudioEffect(FPVector2.Left, cs->toggleRightSFX);
}

Each character has multiple costumes. The costume variant that will be applied to the character depends on the action button used to confirm the character selection.

C#

if        ((inputFlag & InputFlag.LP) != 0) {
   SelectionMade(f, cs, 0);
} else if ((inputFlag & InputFlag.LK) != 0) {
   SelectionMade(f, cs, 1);
} else if ((inputFlag & InputFlag.HP) != 0) {
   SelectionMade(f, cs, 2);
} else if ((inputFlag & InputFlag.HK) != 0) {
   SelectionMade(f, cs, 3);
}

The SelectionMade() method triggers the confirmation events to update the view and fires ISignalOnFighterSelected signal (implemented by the FighterSystem). The ISignalOnFighterSelected instantiates the selected character and sets it up (position, rotation, costume swap in case of identical twins, etc…) and set f.Global->ready to reflect whether both players have made their selection and their characters setup.

Winner Stays

The Fighting Sample implements an arcade style “Winner Stays” match; i.e. the loser relinquishes control and the next player on the spectator / waiting list is given a shot at the crown. The order in which players are chosen is defined by the order in which they joined the game and were added to the f.Global->playerOrderList - see the OnPlayerDataSet method in the CharacterSelectSystem.

This process is handled in the NextMatchState asset. First it removes the losing player from the match by calling CharacterSelectSystem.RemoveFighter; this also resets the CharacerSelect component for that fighter so the new player is granted the ability to choose their own character. Finally, it resets the remaining fighter back to their original position with ResetRoundState.ResetFighters.

As soon as the new player has chosen their character, the next match starts.

Add A New Selectable Character

To add a new selectable character follow these steps:

  1. Increase the size of the fighterChoices array in the CharacterSelect component’s DSL definition - array<asset_ref<FighterData>>[4] fighterChoices;
  2. Add the character’s FighterData asset to the fighterChoices field in the CharacterSelect component on both Character Select P1 & P2 Prototypes.
  3. Add the character portrait to the Selection UI.
  4. Ensure the character order in the Selection UI for P1 and P2 matches the order of the FighterData assets in the CharacterSelect component’s fighterChoices arrays.
Back to top