• Register
Post tutorial Report RSS System for dynamic strings & inline icons [Unity]

Input glyphs. Action icons. Keybind icons. They have many names, but they all refer to the helpful icons you see in games that tell you what buttons to push to do a certain action. They can be as simple as hard-coded bits of text, or as complex as dynamic strings that adjust based on keybinding and the active controller. This article focuses on achieving the latter. Specifically, ones inline with tutorial text.

Posted by on - Intermediate Client Side Coding

The Problem

While making a tutorial system for The Last Cube, our upcoming puzzle-adventure game, I ran into an interesting problem. The tutorials we had designed consisted of icons inline with the actual tutorial text. Now, for some time this was not a problem. I made a simple system that was able to inlay the icon of your choice into the tutorial text. This worked fine until eventually I realised that there was a serious problem: because we wanted to support both keyboard/mouse and controllers, we had to be able to swap the icons during the game, possibly even while the text was visible. Furthermore, how do you display a glyph (icon) for the “Move” input action? Often on a keyboard, it corresponds to the keys W, A, S and D, but on a controller it is by default mapped to the left analog stick. See the problem? The problem was that one action could, depending on the keybindings, correspond to a differing number of glyphs.

Example strings

Note: such icons can be achieved with TextMeshPro’s SpriteAssets, but that is out of scope for this article. Still, the problem of fetching the correct sprite(s) remains.

Now, the above problem could of course by solved by just replacing WASD with four icons of the analog stick pointing in different directions, but that’s not what AAA games do and that’s not something I was willing to settle for. After a few days of thinking about this, and making some prototypes, I landed on a solution that ended up working perfectly for our use-case. I made a recursive system of ScriptableObjects that could be used to build the tutorial strings within the Unity editor.

The motivation for writing this system came from needing dynamic keybinding icons that could be shown within text. In order to achieve such dynamic strings, we must be able to react to

  1. Controllers changing
  2. Keybindings
  3. (Optionally) language

This means that the icons must be able to:

  1. Consist of one or more icons
  2. Attach to specified spots in the source string

I call the system Pluggables — they are ScriptableObjects that can be nested (plugged into each other) to form a decision tree and are used to compose dynamic tutorial strings in the editor. Let’s see how they work.

How it works

Let’s look at the flow of how the movement tutorial gets composed in The Last Cube. The base holds the localization key and any pluggables it is directly using. In this case, the localization key TUTORIAL_CONTROLS_MOVE maps to the English text “Use [] to move.” The object holds the pluggable MovementKeys, which it will recursively parse and plug into the spot specified in the localization string. Note, that in other languages this placeholder “[]” could be put in another position in the text.

Structure of a Pluggables tree

The MovementKeys is a ControllerTypePluggable has been split into four branches: Keyboard, Mouse, Joystick and Custom Controller. These are the main controller types of Rewired, the input system we are using in The Last Cube. This pluggable listens for changes in controller type and refreshes as necessary, parsing content from one of the four child pluggables. In the image above Keyboard and Mouse use the same pluggable, as do Joystick and Custom Controller.

For this example, let’s say we are using a keyboard and mouse. The MovementKeys pluggable will thus look into KbmMovementKeys, which is an ArrayPluggable, capable of returning multiple values at the same time. This is because as opposed to the single sticks often used by controllers, keyboard movement is by default bound to the keys WASD. We will thus have to return four icons instead of the single icon that the controller branch returns. These four child pluggables are of the type ControllerActionPluggable, which in The Last Cube correspond to a Rewired action with its ActionId and Pole. These children could as well point to Unity’s input system’s bindings.

The Inner Workings

Here is the abstract base class of all pluggables. It contains functions for recursively getting the text of this object, as well as adding and removing dependencies (events that should cause this object to refresh). All other pluggables inherit from it.

public abstract class TextPluggableBase : ScriptableObject
{
    public virtual bool IsConstant => false;
    public abstract string GetText();
    public abstract void RegisterDependencies(System.Action Reparse);
    public abstract void UnregisterDependencies();
}

For The Last Cube, we found that four pluggable types were enough for all of our use cases: ArrayPluggable, ConstantTextPluggable, ControllerActionPluggable and ControllerTypePluggable. Out of these, ArrayPluggable and ControllerTypePluggable are for forming decision points, while the other two serve as end points, providing an actual string to be plugged into its parent.

The video below shows the system in action in the game. Notice how the tutorial texts change based on current language (English/Finnish) and control method (Xbox controller/Mouse&keyboard).

It does not make sense to copy paste all of them into this article, but I have provided a Github repository to serve as an example implementation. Note that the code will have dozens of errors if you do not use Rewired and NaughtyAttributes. Use it as a base for your own designs.

The code on Github: Github.com


The game shown above is The Last Cube by Improx Games. You can follow the development of the game here on IndieDB, on social media at ImproxGames and on Reddit at /r/thelastcube. More information at Lastcubegame.com

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: