Comments

On skill checks

In the 15 year build up to No Truce With The Furies, one of our main gripes with RPG-s has always been non-combat skill use. In RPGs – even the story-heavy ones – combat is lavished with tactical tension, skill use produces cool chunky animations, you get pulse-raising rewards and punishments, the logos are colourful. Sound effects go “Tring-trang!” and “Pow!”, there are intricate beautiful systems for you to delve into.

Most of this is missing from non-combat skill use. Talking and exploring gets a simplified, non-competitive version of the combat rules. Usually this comes in the form of passive dialogue options: have this much of that required skill and you’ll be able to say this thing. Even the games we truly admire – Planescape: Torment, Mask of the Betrayer, Fallout – have little going on in the rules department when it comes to dialogue. Ditto for most tabletop pen-and-paper role playing systems. The tactical depth of using arguments, employing logic, original thinking, empathy – the skill use that covers 95% of our actual lives – makes up 5% of the rule system. Yet my experience tells me thinking is the ultimate game. It’s nerve-wrecking, conversations are filled with hidden doubts; we struggle to trust each other, manipulate each other, stay sane. There is great strategic depth and tactical tension that goes into talking that games haven’t really – for me – begun to represent yet.

So that’s the first thing we set out to create: a truly in depth non-combat skill system. We have four stats and under each stat there are 6 skills. That gives us 24 skills – all 24 have critical non-combat use. In fact, No Truce With The Furies (the first implementation of our role playing system) will cover their non-combat use almost exclusively. (In the future we want every skill to be a two-faced Janus with somewhat unsymmetrical and unexpected uses in combat and outside it).

I’ll show off the individual skills in a future post. But first I want to talk about how the skills are used in No Truce With The Furies. That is – about skill checks.

Reference used to create the look of our dialogue system

Reference used to create the look of our dialogue system

In role-playing games the check is the moment the system “checks” if a character has enough points in a skill to perform an action. It’s a “you have to be at least this tall to ride the rollercoaster” kind of deal. Of course there are exceptions and interesting ideas around, but this is how RPGs usually handle skill checks: your character is talking to someone, that someone lies, if your character has 5 INTELLIGENCE you get a dialogue option that says: “You’re not telling me the truth”. Saying that will make the guy admit he lied. This type of check is called passive because you’re doing nothing. Some hours ago you put two points in “seeing through lies skill” and now the software affirms your choice. There’s not a lot of game in there. And certainly not a lot of literature.

When designing our skill checks in dialogues we had two goals:

  1. Make dialogue more like literature – rethink passive checks
  2. Make dialogue more like a game – add active checks

PASSIVE CHECKS

In literature dialogues are interspersed with thoughts, emotions, alterior motives and physical phenomenon taking place within the characters while they talk. This comes in the form of parenthesis, streams of consiousness, author interjections etc. A whole plethora of literary devices. We wanted to do that in game form. To depict what’s below the surface: the moment an idea forms, the sense of self delusion, secretly laughing because you came up with a stupid joke. Then trying to figure out if you should say it out or not…

It was surprisingly easy to achieve – your skills talk to you. When we use passive checks they are not dialogue options but extra “characters” who silently interject. Only you, the main character can hear them because they are your thoughts, your sensations. Our passive checks are souffleurs in a play.

Let’s look at a sample situation from the game. And remember: every time the main character speaks they have options to say something else. (I have simplified the choice part of the dialogue for the sake of this example).

You come upon a loitering teenage girl kneeling on the ice with a tape recorder in hand. You approach her, question her, then this happens:

You:  “What’s that device you have there?”
Acele:  “This? It’s a portable recording device. It’s for field recording. Low quality, but still.”
You:  “And the wires?”
Acele:  “Actually just one wire, I picked on it ’til the braiding came loose. The wire leads to a contact microphone.”
You:  “What is a “contact microphone”?”
Acele:  “A contact mic is a microphone that records sounds from inside things. Like this ice.”
TRIVIA (difficult success):  Your mangled brain would like you to know there is a boxer called Contact Mike.
You:  What am I supposed to do with this?
TRIVIA:  No idea.
You:  “Does this have anything to do with Contact Mike?”
Acele:  “Uh…” She’s confused. “Yeah, I record stuff with it.”
You:  “No, I mean the boxer Contact Mike.”
Acele:  “Ah! No. This is a *contact microphone*, it’s for recording *inside* solid objects. Contact Mike just beats people up.”
You:  “You know, Contact Mike doesn’t “just beat people up”. Contact Mike is a role model.”
Acele:  “Um…”
You:  “On second thought, screw Contact Mike. He’s no true champion – you are! Look at you here in front of a saggy tent, picking your nose to drug-addict music. The world of sports is in awe of your faith and dedication!”
Acele:  “Man, you are one weird cop.”
You:  “This isn’t about me. This is about your lack of respect for one of boxing’s greats – and for *yourself*.”

This dialogue could have gone differently if you didn’t have a ridiculously detailed (and mostly useless) factual memory. Even then you could have ignored the little connection your mind made, but in this situation the player chose to go off on a tangeant.

What happened was

  1. First you had a high enough Trivia skill.
  2. Then your Trivia told you an “interesting” fact.
  3. Then you had a little conversation with that part of your memory.
  4. Then you reached a hub of questions to Acele where in addition to normal, situation-appropriate ones you had “Does this have anything to do with Contact Mike?”.

This line we call a black check. It’s a line of dialogue fed to you by a passive check. It’s the closest we have to a “have this much skill to get dialogue option” type of affair, but 1) it’s covert, often you don’t even understand where an idea came from 2) we always have the conception of an idea first: the skill talks to you and then sometimes you can use this idea on whoever you’re talking to. If you choose to. Keeping the tidbit to yourself produces effects down the line too, since we consider all dialogue options seen by the player to be ideas circulating in the character’s psyche. Some just remain unspoken.

On some occasions the passive check just makes little observations that lead to more things later, but remain one-liners for now.

So this is how we’ve re-thought passive checks. The versatility of this simple system – let me just repeat it one more time: YOUR SKILLS TALK TO YOU – is pretty incredible. It is hard for us to imagine writing the game without it already. We can do really weird stuff. Like Half Light – the skill that controls your adrenaline gland and your prey drive – can railroad you into a rage spiral where you hound an innocent suspect on something they clearly didn’t do. And it takes another skill’s intervention for you to get out of it. The next moment a skill can wildly expand the options you have avalable, for example: Drama whispers insane method acting ideas into your ear. Or your Pain Threshold tells you to stab yourself in the hand to make a point. Whatever you do – don’t. Pain Threshold is an unstable masochist. It will only leave you screaming with your hand nailed to the table. And then – while screaming with your hand nailed to the table – Rhetoric to the rescue! Make a political point out of this. Tell them you’re a victim of your own macho mentality. Tell them (with your hand still nailed to the table) that years of chauvinism have led you to this low point in your life.

Now, I just made this situation up because I didn’t want to spoil any more of the game, but you get the point. If “Years of chauvinism have led me to this point!” was just a dialogue option it would come out of the blue. But it’s different to hear the thought form in your head out of great physical discomfort and then be able to converse with it. Should I say that? Do I really mean that? You sometimes let these ideas out, sometimes you carry on. We have a game where you might have to start censoring yourself.

contact-mike-gif7

Current version of our dialogue engine. Notice the sexy yet subtle animation cues!

Next time I will talk about active skill checks – our gamey, number crunching, min-maxing counterbalance to the literature-wonk of passive checks.

Til then!

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.