UnrealScript Language Reference/States
States
Overview of States
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 which 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 all of the regular UnrealScript commands plus several special functions known as "latent functions". A latent function is a function which executes "slowly", 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 is an example of states from the TriggerLight script:
// Trigger turns the light on. state() TriggerTurnsOn { function Trigger( actor Other, pawn EventInstigator ) { Trigger = None; Direction = 1.0; Enable( 'Tick' ); } } // Trigger turns the light off. state() TriggerTurnsOff { function Trigger( actor Other, pawn EventInstigator ) { Trigger = None; Direction = -1.0; Enable( 'Tick' ); } }
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:
auto state MyState { Begin: Log( "MyState has just begun!" ); Sleep( 2.0 ); Log( "MyState has finished sleeping" ); goto( 'Begin' ); }
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 has passed. Latent functions can only be called from within state code, and not from within functions. Latent functions let you manage complex chains of events which 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" function (similar to the C/C++/Basic goto) within a state causes the state code to continue executing at a different label.
- The special Goto('') 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.
Here is an example of the state concepts discussed so far:
// This is the automatic state to execute. auto state Idle { // When touched by another actor? function Touch( actor Other ) { log( "I was touched, so I?m going to Attacking" ); GotoState( 'Attacking' ); Log( "I have gone to the Attacking state" ); } Begin: log( "I am idle?" ); sleep( 10 ); goto( 'Begin' ); } // Attacking state. state Attacking { Begin: Log( "I am executing the attacking state code" ); //... }
When you run this program and then go touch the actor, you will see:
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.
Here is an example of all the overriding rules:
// Here is an example parent class. class MyParentClass extends Actor; // A non-state function. function MyInstanceFunction() { log( "Executing MyInstanceFunction" ); } // A state. state MyState { // A state function. function MyStateFunction() { Log( "Executing MyStateFunction" ); } // The "Begin" label. Begin: Log("Beginning MyState"); }
// Here is an example child class. class MyChildClass extends MyParentClass; // Here I?m overriding a non-state function. function MyInstanceFunction() { Log( "Executing MyInstanceFunction in child class" ); } // Here I?m redeclaring MyState so that I can override MyStateFunction. state MyState { // Here I?m overriding MyStateFunction. function MyStateFunction() { Log( "Executing MyStateFunction" ); } // Here I?m overriding the "Begin" label. Begin: Log( "Beginning MyState in MyChildClass" ); }
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, which resolves 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 extend 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) which have a lot of functionality in common. In this case you could declare a base Attacking state as follows:
// Base Attacking state. state Attacking { // Stick base functions here... } // Attacking up-close. state MeleeAttacking extends Attacking { // Stick specialized functions here... } // Attacking from a distance. state RangeAttacking extends Attacking { // Stick specialized functions here... }
A state can optionally use the "ignores" specifier to ignore functions while in a state. The syntax for this is:
// Declare a state. state Retreating { // Ignore the following messages... ignores Touch, UnTouch, MyFunction; // Stick functions here... }
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.
Whever 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 which your state may require.
Prev Page: /Program Structure – Section 6 of 9 – Next Page: /Language Functionality