Fortress Occident Developer Blog

Comments

Better Living Through C# Coding, Part 1: Managing Input

Unity is an awesome development engine. I formed that opinion back in 2012 when I first gave it a try (and during the sleepless night that followed), and I still believe it to be true. It’s not flawless though - sooner or later you discover an aspect or functionality that just confounds you. In contrast to what is normal, things suddenly become… unwieldy, raw, malformed. For me such an aspect is handling player input (keyboard, mouse, and gamepad events).

You see, Unity doesn’t have a nice subsystem here that you’d somehow expect. There’s just a low-level API (Input class with static methods), which appears to have been there since the early Bronze Age, and is about as sophisticated. If you read and follow the documentation, it promptly guides you down the easy road to hell called Bad Design. I mean, multiple component scripts each polling Input status every frame, handling the results independently (and unaware) of each other doesn’t bode well for any non-trivial project.

Almost as if to taunt you, there’s the event-system for working with uGUI (the “new” UI system released with Unity 4.6). Once you break through the initial confusion (as the documentation here suddenly stops being helpful and informative), you’ll find this to be a nice, well designed framework, easy to use for both trivial and advanced input operations alike. Alas, this framework really only covers GUI, leaving you empty-handed with everything else your project needs to handle (WASD/arrows keys for camera movement, mouse clicks to designate target, Escape to bring up game menu, etc.).

So, how does one approach this? One problem at a time of course. Just like eating an elephant.

Let’s start with the lack of awareness between scripts. Say you want a key (Spacebar) to do different things in different situation: if the player character is walking around in the game world, he should stop moving; if he is in a dialogue with an NPC, Spacebar should be the hotkey for Continue button and if you’re typing a text (chat command in a multiplayer game, naming your savegame, etc.), space should not do anything else. Or for example the Escape key: depending on what’s visible on screen, it might close Inventory, bring up game menu, or do something else entirely.

A good solution allows scripts to indicate their interest in a specific Input event, mark the event status (has someone “used up” that event already?) and establish priority order when it comes to choosing who handles the Input event. The natural design pattern for this would include a Singleton input manager class with Observers subscribing to input events. Observers of a specific event can then be ordered by their priority (Chain of Responsibility pattern). In addition to actual Input event data, event parameters can include a flag (boolean) for tracking the “used” status.

GameInputManager.cs

using UnityEngine;
using System.Collections.Generic;
public class GameInputManager : MonoBehaviour {
  #region Singleton pattern
  protected static GameInputManager singleton;
  public static GameInputManager Singleton { 
    get { 
      if (singleton==null) singleton = FindObjectOfType<GameInputManager>();
      return singleton; 
    } 
  }
  #endregion
  #region Input event parameter
  public class EventData {
    public string axis = null;
    public string button = null;
    public KeyCode keyCode = KeyCode.None;
    public bool used = false;
    public float value = 0f;
    public EventData(KeyCode keyCode) { this.keyCode = keyCode; }
    public EventData(string axis, float value) { this.axis = axis; this.value = value; }
    public EventData(string button) { this.button = button; }
  }
  #endregion
  public const int MAX_PRIORITY = 10000;
  #region Public static methods (API)
  /// <summary>Register an axis as one of interest.</summary>
  public static void ObserveAxis(string axis) {
    if (!string.IsNullOrEmpty(axis) && Singleton) Singleton.observedAxes.Add(axis);
  }
  /// <summary>Register a button as one of interest.</summary>
  public static void ObserveButton(string button) {
    if (!string.IsNullOrEmpty(button) && Singleton) Singleton.observedButtons.Add(button);
  }
  /// <summary>Register a keycode as one of interest.</summary>
  public static void ObserveKeyCode(KeyCode keyCode) {
    if (keyCode!=KeyCode.None && Singleton) Singleton.observedKeycodes.Add(keyCode);
  }
  /// <summary>Register a handler method for hotkey event with one above currently highest priority.</summary>
  /// <param name="Action">Handler method that is called when hotkey event triggers. That method has one EventData parameter.</param>
  public static void Register(System.Action<EventData> Action) {
    if (Action!=null && Singleton!=null) Singleton.GetBlock(Singleton.highestPriority + 1).Event += Action;
  }
  /// <summary>Register a handler method for hotkey event with the specified priority.</summary>
  /// <param name="Action">Handler method that is called when hotkey event triggers. That method has one EventData parameter.</param>
  /// <param name="priority">Callbacks are made in order of priority (from the highest to the lowest).</param>
  public static void Register(System.Action<EventData> Action, int priority) {
    if (Action!=null && Singleton!=null) Singleton.GetBlock(priority).Event += Action;
  }
  /// <summary>Unregister a callback method from all Input events.</summary>
  public static void Unregister(System.Action<EventData> Action) {
    if (Action!=null && Singleton!=null) foreach (EventBlock b in Singleton.eventBlocks) b.Event -= Action;
  }
  #endregion
  #region Unity magic methods
  protected void Awake() {
    singleton = this;
  }
  protected void Update() {
    foreach (string a in observedAxes) {
      SendEvent(new EventData(a, Input.GetAxis(a)));
    }
    foreach (string b in observedButtons) {
      if (Input.GetButtonDown(b)) SendEvent(new EventData(b));
    }
    foreach (KeyCode k in observedKeycodes) {
      if (Input.GetKeyDown(k)) SendEvent(new EventData(k));
    }
  }
  #endregion
  #region Internals (under the hood)
  protected class EventBlock : System.IComparable<EventBlock> {
    public int priority;
    public event System.Action<EventData> Event;
    public EventBlock(int p) { priority = p; }
    public void AppendTo(ref System.Action<EventData> deleg) { if (Event!=null) deleg += Event; }
    // Order highest to lowest
    public int CompareTo(EventBlock other) { return -priority.CompareTo(other.priority); }
    public void Invoke(EventData eventData) { if (Event!=null) Event(eventData); }
    public bool IsEmpty { get { return Event==null; } }
  }
  protected List<EventBlock> eventBlocks = new List<EventBlock>();
  protected HashSet<string> observedAxes = new HashSet<string>();
  protected HashSet<string> observedButtons = new HashSet<string>();
  protected HashSet<KeyCode> observedKeycodes = new HashSet<KeyCode>();
  protected EventBlock GetBlock(int priority) {
    foreach (EventBlock b in eventBlocks) if (b.priority==priority) return b;
    EventBlock newBlock = new EventBlock(priority);
    eventBlocks.Add(newBlock);
    eventBlocks.Sort();
    return newBlock;
  }
  protected int highestPriority { 
    get {
      // eventBlocks is always sorted in reversed priority order (i.e., highest to lowest), so first non-empty block is the correct result
      foreach (EventBlock b in eventBlocks) if (b.priority<MAX_PRIORITY && !b.IsEmpty) return b.priority;
      return 0;
    }
  }
  protected void SendEvent(EventData data) {
    System.Action<EventData> callStack = null;
    foreach (EventBlock block in eventBlocks) block.AppendTo(ref callStack);
    if (callStack!=null) callStack(data);
  }
  #endregion
}

 

Observer scripts would then look like this:

DemoInputObserver.cs

using UnityEngine;
public class DemoInputObserver : MonoBehaviour {
  #region Unity magic methods
  protected void OnEnable() {
    GameInputManager.ObserveKeyCode(KeyCode.Space);
    GameInputManager.ObserveKeyCode(KeyCode.Escape);
    GameInputManager.ObserveAxis("Horizontal");
    GameInputManager.Register(OnInputEvent);
  }
  protected void OnDisable() {
    GameInputManager.Unregister(OnInputEvent);
  }
  #endregion
  #region Internals (under the hood)
  protected void OnInputEvent(GameInputManager.EventData data) {
    if (data.used) return;
    if (data.keyCode==KeyCode.Space) {
      Debug.Log("Spacebar was pressed");
      data.used = true;
    } else if (data.keyCode==KeyCode.Escape) {
      Debug.Log("Escape was pressed");
      data.used = true;
    } else if (data.axis=="Horizontal") {
      if (data.value!=0f) {
        Debug.Log("Horizontal axis = " + data.value.ToString());
      }
      data.used = true;
    }
  }
  #endregion
}

 

Note that if you attach this script to Game Objects multiple times, the Console will only show a single entry for each event. By default, priority (order of calling) is determined by the order that Unity enables components in scene (LIFO: last one to register receives highest priority). If you want to explicitly determine priority, the Register method has an appropriate override.

Alright, that’s enough for one post. Next time, I’ll show you what Reflection and Attributes can bring to this party.

Comments

Wor(l)d creation

Video game production gives us an opportunity to take a more detailed look at how reality is layered. What experiences of reality can be depicted in game form, that would be un-expressable in, say, a written story? Science has done its best to prove that something’s existence as a story does not mean anything. We could fantasize whatever, and however much we talk about it, it will not acquire more physical existence than it had before the story was made up. Storytelling is not omnipotent, unlike the popular myths would have it.

Let’s think of a woman. Let’s call her Klaasje. We know what she is supposed to look like (blonde hair, silvery jumpsuit, nine-inch heels). Klaasje is a dancer. There’s more to her, but this will have to do for now. And after that… nothing happens. Klaasje has been invented, yes. The literary ingenuity as lauded by authors and readers alike allows us to picture Klaasje exactly how we please. There are a billion Klaasjes and the author is dead, as he should be. (The meme of the author’s death illustrates the impossibility of the author intruding into the reader’s thoughts and dictating how they’ll picture the author’s characters).

So, there’s a potential Klaasje, but nothing happens, because the place where we release her is not a book. No one has told her that her being a dancer means she should be able to manage mere walking with ease and grace. This is why Klaasje just stands there. And when she tries to walk and there’s even a slightest imperfection in her animation, we as viewers *will* notice it for some reason and we will fail to believe she’s a dancer. We will shake with laughter or disgust, depending on the side of the uncanny valley the logic of her movement will throw us. Somebody else will have to do the work usually done by the brain’s motor cortex. It’s an enormous amount of work. The evolution has done it once already, but it won’t be any help to a moving character model or a moving robot. Thus the animator and the programmer find themselves in a world full of problems.

Of course we all know this, it is elementary. Yet naming this elementary does not aid the animator or the programmer either. Inside our heads in a story it is very easy to merge various aspects of a character into a cohesive whole. A few twists and turns of the proverbial quill and the readers have a nice carcass on which they can easily generalize a character. Go on, enjoy the awesome character I just came up with using your heads and imaginations! Suddenly I feel the all-encompassing power of storytelling! All manners of possible worlds are lined up behind the door, patiently waiting for me to give them a shape.

Now let us imagine all the mythopoetic narrators who have utilized their outstanding powers of imagination in assembling the various religions, beliefs, myths and fairy tales we know so well today, let us imagine they had to tell those stories in the form of a video game. All those characters and critters that need to be animated, one by one. What would it be like to animate God? How should he walk? What should the horrible cloud look like under his feet, spitting lightning and coughing up thunder as God addresses his people? What’s its texture like, how large should it be, what level of transparency? What exactly should the damn lightning be like? And oh, did I just say “people”? You have to animate every single one of them now and make them walk properly before they escape the wrath of God (stop fooling around, this is not an epic escape, it’s comical, do it over). And that was one runner, but we have 3500 of them.

And now please construct all of the prehistoric mythoi in a similar, preferrably even more detailed manner.

And now please construct all of the prehistoric mythoi in a similar, preferably even more detailed manner.

Do you think the problem could be solved à la “Thou shalt not make five different variations of your God’s walk cycle!”? No, instead you’ll agree with me here that it is so much more convenient for an author to just tell the story in as much detail as they please and avoid going through the hassle of simplifying so much for the player. It would be so much easier to just trust the reader’s/player’s imagination to do all the heavy lifting for you. Writing won’t make anyone do a reality check. The author will finally see the weak links in the art of storytelling when the story starts manifesting in a new and tangible form – a video game for example. That is when they begin to appreciate the detail level of animating and coding.

Comments

MOTOR LORRY OF THE DAY #2

Time again for the legendary, quasi-legendary, mostly forgotten Lorry of the Week! Last week’s entry had people asking: how does it turn? To that I say: I don’t know. We have forwarded the question to our certified genius / notoriously difficult to work with industrial designer, but he has secluded himself in a forest cabin for the time being. So I wouldn’t hold my breath.

This week we have the Faln A-Z “Tempo”.

Faln-A-Z_360_Fortress-Occident

The Tempo used to be called the “Contemporain” before years of lorryman lingo weathered it down to two syllables. With revision 9 the manufacturer followed suit. This truly iconic vehicle from the late thirties has seen two decades of service and multiple revisions. The Faln A-Z (pronounced “a-zed” in the Revachol region) is a trusted haul and a lifestyle choice for lorrymen, poor people and drunkard artists who need to transport large format paintings.

That adorably awkward boxy salon and those two headlights have become synonymous with roadkill, light fascism, romantic memories of a youth misspent “down at the reservoir” – a mythical place on the outskirts of Revachol where “we used to drive in my brother’s Tempo” – and sadly the occasional rape.