UnrealScript States

Overview

Historically, game programmers have been using the concept of states ever since games evolved past the "pong" phase. States (and what is known as "state machine programming") are a natural way of making complex object behaviour manageable. However, before UnrealScript, states have not been supported at the language level, requiring developers to create C/C++ "switch" statements based on the object's state. Such code was difficult to write and update.

UnrealScript supports states at the language level.

In UnrealScript, each actor in the world is always in one and only one state. Its state reflects the action it wants to perform. For example, moving brushes have several states like "StandOpenTimed" and "BumpOpenTimed". Pawns have several states such as "Dying", "Attacking", and "Wandering".

In UnrealScript, you can write functions and code that exist in a particular state. These functions are only called when the actor is in that state. For example, say you're writing a monster script, and you're contemplating how to handle the "SeePlayer" function. When you're wandering around, you want to attack the player you see. When you're already attacking the player, you want to continue on uninterrupted.

The easiest way to do this is by defining several states (Wandering and Attacking), and writing a different version of "Touch" in each state. UnrealScript supports this.

Before delving deeper into states, you need to understand that there are two major benefits to states, and one complication:

Benefit: States provide a simple way to write state-specific functions, so that you can handle the same function in different ways, depending on what the actor is doing.

Benefit: With a state, you can write special "state code", using the entire regular UnrealScript commands plus several special functions known as "latent functions". A latent function is a function that executes "slowly" (i.e. non-blocking), and may return after a certain amount of "game time" has passed. This enables you to perform time-based programming -- a major benefit which neither C, C++, nor Java offer. Namely, you can write code in the same way you conceptualize it; for example, you can write a script that says the equivalent of "open this door; pause 2 seconds; play this sound effect; open that door; release that monster and have it attack the player". You can do this with simple, linear code, and the Unreal engine takes care of the details of managing the time-based execution of the code.

Complication: Now that you can have functions (like Touch) overridden in multiple states as well as in child classes, you have the burden of figuring out exactly which "Touch" function is going to be called in a specific situation. UnrealScript provides rules which clearly delineate this process, but it is something you must be aware of if you create complex hierarchies of classes and states.

Here you are declaring two different states (TriggerTurnsOn and TriggerTurnsOff), and you're writing a different version of the Trigger function in each state. Though you could pull off this implementation without states, using states makes the code far more modular and expandable: in UnrealScript, you can easily subclass an existing class, add new states, and add new functions. If you had tried to do this without states, the resulting code would be more difficult to expand later.

A state can be declared as editable, meaning that the user can set an actor's state in UnrealEd, or not. To declare an editable state, do the following:

state() MyState
{
...
}

To declare a non-editable state, do this:

state MyState
{
...
}

You can also specify the automatic, or initial state that an actor should be in by using the "auto" keyword. This causes all new actors to be placed in that state when they first are activated:

auto state MyState
{
...
}

State Labels and Latent Functions

In addition to functions, a state can contain one or more labels followed by UnrealScript code. For example:

The above state code prints the message "MyState has just begun!", then it pauses for two seconds, then it prints the message "MyState has finished sleeping". The interesting thing in this example is the call to the latent function "Sleep": this function call doesn't return immediately, but returns after a certain amount of game time elapses. Latent functions can only be called from within state code, and not from within functions. Latent functions let you manage complex chains of events that include the passage of time.

All state code begins with a label definition; in the above example the label is named "Begin". The label provides a convenient entry point into the state code. You can use any label name in state code, but the "Begin" label is special: it is the default starting point for code in that state.

There are three main latent functions available to all actors:

Sleep( float Seconds ) pauses the state execution for a certain amount of time, and then continues.

FinishAnim() waits until the current animation sequence you're playing completes, and then continues. This function makes it easy to write animation-driven scripts, scripts whose execution is governed by mesh animations. For example, most of the AI scripts are animation-driven (as opposed to time-driven), because smooth animation is a key goal of the AI system.

FinishInterpolation() waits for the current InterpolationPoint movement to complete, and then continues.

The Pawn class defines several important latent functions for actions such as navigating through the world and short-term movement. See the separate AI docs for descriptions of their usage.

Three native UnrealScript functions are particularly useful when writing state code:

The "Goto('LabelName')" function (similar to the C/C++/Basic goto) within a state causes the state code to continue executing at the specified label.

The special "Stop" command within a state causes the state code execution to stop. State code execution doesn't continue until you go to a new state, or go to a new label within the current state.

The "GotoState" function causes the actor to go to a new state, and optionally continue at a specified label (if you don't specify a label, the default is the "Begin" label). You can call GotoState from within state code, and it goes to the destination immediately. You can also call GotoState from within any function in the actor, but that does not take effect immediately: it doesn't take effect until execution returns back to the state code.

I am idle...
I am idle...
I am idle...
I was touched, so I'm going to Attacking
I have gone to the Attacking state
I am executing the attacking state code

Make sure you understand this important aspect of GotoState: When you call GotoState from within a function, it does not go to the destination immediately, rather it goes there once execution returns back to the state code.

State inheritance and scoping rules

In UnrealScript, when you subclass an existing class, your new class inherits all of the variables, functions and states from its parent class. This is well-understood.

However, the addition of the state abstraction to the UnrealScript programming model adds additional twists to the inheritance and scoping rules. The complete inheritance rules are:

A new class inherits all of the variables from its parent class.

A new class inherits all of its parent class's non-state functions. You can override any of those inherited non-state functions. You can add entirely new non-state functions.

A new class inherits all of its parent class's states, including the functions and labels within those states. You can override any of the inherited state functions, and you can override any of the inherited state labels, you can add new state functions, and you can add new state labels.

When you have a function that is implemented globally, in one or more states, and in one or more parent classes, you need to understand which version of the function will be called in a given context. The scoping rules that resolve these complex situations are:

If the object is in a state, and an implementation of the function exists somewhere in that state (either in the actor's class or in some parent class), the most-derived state version of the function is called.

Otherwise, the most-derived non-state version of the function is called.

Advanced state programming

If a state doesn't override a state of the same name in the parent class, then you can optionally use the "extends" keyword to make the state expand on an existing state in the current class. This is useful, for example, in a situation where you have a group of similar states (such as MeleeAttacking and RangeAttacking) that have a lot of functionality in common. In this case you could declare a base Attacking state as follows:

You can tell what specific state an actor is in from its "state" variable, a variable of type "name".

It is possible for an actor to be in "no state" by using GotoState(''). When an actor is in "no state", only its global (non-state) functions are called.

Whenever you use the GotoState command to set an actor's state, the engine can call two special notification functions, if you have defined them: EndState() and BeginState(). EndState is called in the current state immediately before the new state is begun, and BeginState is called immediately after the new state begins. These functions provide a convenient place to do any state-specific initialization and cleanup that your state may require.

State Stacking

With normal state changing you go from one state to the other without being able to return to the previous state as it was left. With state stacking this is possible. Calling the function PushState will change to a new state putting it on top of the stack. The current state will be frozen. When PopState is called the previous state will be restored and continue it's execution from the point where PushState was called. PushState will act as a latent function when possible (only inside of state code), so code execution behavior is different if you call PushState from within a function. Calling it from a function will not interrupt code execution (much like GotoState from within a function), whereas calling it from within state code will pause execution until the child state is popped (again, similar to GotoState from within state code).

A state can be put on the stack only once, trying to push the same state on the stack a second time will fail. PushState works just like GotoState, it takes the state name and an optional label for the state's entry point. The new state will receive an PushedState event, the current state receives a PausedState event. After calling PopState the current state receives a PoppedState event and the new state (the one that was next on the stack) will receive ContinuedState.

Using the function IsInState you will be able to check if a certain state is on the stack. This function only checks the name of the states and therefor can not be used to check on parent states. For example:

state BaseState
{
...
}
state ExtendedState extends BaseState
{
...
}

If the active state is ExtendedState then IsInState('BaseState') will return false. Ofcourse calling IsInState('BaseState', true) will return true if BaseState is on the stack.