Sunday, June 12, 2011

State Design Pattern in Unity with C#

Following up on my initial investigation into game engines, I decided that the best way to get to know Unity would be to build something with it. A few years ago, I wrote a clone of Every Extend called EEClone, which is described in this JERIC paper (ACM DL). I figured a new spin on EEClone would give me a good point of comparison, so I set out to recreate it in Unity.

As I mentioned earlier, one of the strengths of Unity is that it supports a variety of scripting languages, including C#. This intrigued me the more I thought about it, since it meant that it should be straightforward for me to apply my knowledge of Java design patterns in Unity with just a few syntactic changes. Specifically, I decided to dive into the state design pattern, which time and again I have found to be useful in practice. Just as with Java, a quick search of the Web for advice on how to implement state machines in Unity comes up with the classic procedural approach. The costs and benefits of the OO and procedural approaches are documented in the aforementioned paper; for this post, I will focus on the mechanics rather than justification.

Without further ado, this is what the game looks like. The video shows two player-initiated explosions followed by one obstacle collision.



The goal was to explore how the pattern is reified within Unity and C#, not to faithfully recreate EEClone, and so I went with a slightly simpler state diagram than in the Java version:


The astute reader will sure notice where I have cut corners. For example, PlayingState transitions directly to AwaitingRespawnState after the player sets off the bomb, without waiting for the explosion chain to terminate.  Clever handling of explosion chains and scoring was beyond the scope of this example.

The state diagram can be converted into an implementation following the Gang of Four approach, yielding this structure:

Within Unity, this entire structure (modulo the external MonoBehavior) is contained in one source file, StatefulPlayer.cs. The class diagram reveals the two defining characteristics of the state design pattern:
  1. Each state is represented as an object. In this case, each is defined as a unique class, with an abstract class holding common behaviors and implementations, the result of refactoring.
  2. States are responsible for their own transitions.
To illustrate these points, consider AwaitingRespawnState, the point of which is simply to wait for three seconds to elapse before going back to SpawningState:

class AwaitingRespawnState : AbstractState {
    public AwaitingRespawnState(StatefulPlayer p) : base(p) {}
    
    private float elapsedTime;
    
    public override void Update() {
        elapsedTime += Time.deltaTime;
        if (elapsedTime > 3) {
            player.transform.renderer.enabled = true;
            player.EnterState(new SpawningState(player));
        }
    }
    
    public override void OnEnter() {
        elapsedTime=0;
    }
}

The constructor passes its argument to the superclass' constructor, where that reference is kept in a protected field, player. The Update method overrides the default empty implementation from AbstractState and is responsible for keeping track of elapsed time. You can clearly see that when this accumulated time exceeds three seconds, we go back to a new SpawningState.

There is one point where I had to "cheat" a bit, and that is with animation termination. The transition from SpawningState to PlayingState should be triggered by the end of the spawn-in animation. However, I could not find a way in Unity for a general-purpose end-of-animation observer. I could have set a timer and hardcoded it to go off at the same time as the animation, but this would clearly be fragile to change. My solution was to manually insert an event at the end of the animation, and to have this event call the AnimationFinished method of the player—indeed, that is its raison d'ĂȘtre. This method is also used at the end of the death animation, as shown in the state diagram above.

It is laudable that Unity promotes rapid prototyping with its asset-centric development model, which allows you to go in and fiddle with values with very few restriction. Yet, I find it frustrating to have to deal with literals rather than expressions: without such abstraction, one is left thirsty for more refactoring. My approach for handling animation termination is effective, but it does mean that the entire state model has a dependency on two animations, a dependency that cannot be determined by static analysis of the C# code itself. This is an architectural weakness, but it is the cost one pays for using tools like Unity that prevent one from having to hand-code each and every animation.

No comments:

Post a Comment