State
This topic will probably get linked from both mapping pages and scripting pages...
For mapping see InitialState, perhaps.
Overview
For a good overview of the concept of states, see the opening of UnrealScript Language Reference/States.
There are two fairly different strategies for state programming. On the one hand are actors such as Pawn (UT) that change their state in-game according to what they are doing. Other actors such as Trigger and Mover and SpecialEvent use segregated states to essentially create several different types of behaviour in one class. The Mover class, for example, only changes its own state to go dormant if Mover.bTriggerOnceOnly is set.
The term State code means code within states, in labels outside of functions.
Note that while you can declare states in classes not descendent from Actor or Console (in UT) or Interaction (in UT2003), they simply won't work there.
Declaring States
A state is declared as a block of code in the class. States can contain the Ignores statement, functions and/or state code, but they don't have to.
<optional state modifiers> State[()] <state name> [extends <parent state name>] { Ignores <function name>, <function name>, ... ; <state functions> Begin: <state code> }
State modifiers
Note: These modifiers only have an effect when used in an actor subclass. They don't have any effect in other classes states can be used in.
- auto
- The state should be the initial state of the object. This can be overridden by the InitialState variable in Actors.
- simulated
- State code can be simulated like functions. See Simulated Function.
Configurable states
Just like the var keyword, the state keyword can be followed by a pair of parentheses. This makes the state available in the Object → InitialState property in UnrealEd for a placed instance of the actor. Actors like Trigger and Mover use this technique to expose several different behaviours to mappers with just one control. The value specified for InitialState will of course override any states declared with the Auto keyword.
state() MyState { // rest of state block }
Extending
The extends <parent state name> part is optional for new states and may not be used for states which override a state with the same name in the parent class.
See Extending States. There's sort of an example of this on Scripting movers.
Ignores statement
The ignores statement effectively does the same as overriding functions with empty functions, just more efficient. This is how it should work, but unfortunately doesn't. If you absolutely do not want a function to execute while an Actor is in a particular state, you must override it:
function DontExecuteMe() { log( "ignore keyword isn't reliable" ); } state MyState { function DontExecuteMe(); }
Ignore effectively does a Disable() on the specified function, which is meaningless to anything but native events, and even then most events don't respect the probing status properly. When all else fails, just override the function and then write an email to Tim and tell him to fix his code.
Functions
Functions are defined within a state just as they would be elsewhere in the class. Functions of the same name can exist outside state blocks, and in each state block.
Which version of a function is called is controlled by the Super and Global Special UnrealScript Keywords.
State code
State code is code that is within the state block, but outside of any functions. It is usually divided up into sections with labels. When the actor is put into a particular state, a destination label can be specified (see State Flow below), or the code at the special Begin: label is executed.
Note that labels don't stop execution of state code. Unless you explicitely put a Stop command at the end of the code following the "Open" label in the example below, the code following the "Close" will also be executed:
Open: // some code Close: // some more code
State code is only inherited as a whole. If a subclass puts at least one line of state code in its own version of that state, the parent class' state code will not be used. This one line of code could be just a "Begin:" label, which basically removes all executable state code.
State Flow
Movement between states are controlled by the following functions:
- GotoState (state name, optional label name)
- This call not only places the object into the state requested but starts code execution at the label specified. If the second parameter is omitted, then code at the Begin: label is executed, if it exists. The BeginState() function is called in all instances, from within the GotoState function, ie before the state code is executed.
- Goto (label name)
- This call simply continues code execution at the label specified within the current state.
Example
An example of how state flow can have unexpected results
function Foo() { if( bTest ) GotoState('FirstState'); GotoState('SecondState'); }
This actor will always end up in SecondState, because the second GotoState() is always executed. The BeginState() and EndState() of state FirstState will be called.
Interrogation
The following functions allow the interrogation of objects to determine things about their state:
- GetStateName ( )
- This function returns the name of the current state the object is in.
- IsInState (state name)
- This function returns true if the object is in the state specified, and false if not.
Note however, that this will also return true if the object is in a state that is extended from "state name". (Bug or behaviour?) If this is going to cause a problem, use GetStateName() == 'desiredStateName' instead.
Example
Some extremely contrived example code:
function DrinkBeer() //but don't drive afterwards!!! { if ( IsInState('Quenched') ) { // Second parameter not passed so "Begin" label is used GotoState('DryAsABone'); } else if ( GetStateName() == 'Drinking' ) { GotoState('Quenched'); } return; } state DryAsABone { Begin: PurchaseBeer('Guinness'); GotoState('Drinking','FullPint'); } state Drinking { FullPint: DrinkBeer(); } state Quenched { Begin: // Code }
As a general rule of thumb you should place all of your labels together at the end of your state block after the function declarations.
Note that instead of using IsInState('Quenched') to branch within the function DrinkBeer(), we could have made a versions of the function within each state.
Related topics
Tutorials
Comments
UsAaR33: Should latent functions be added here?
Tarquin: There might be a list on Actor (UT)/Functions, but I don't think they're explained in detail. Here could be a good place.
AlphaOne: When I declare functions inside a state and then try to call them the complier says that a function does not exitst. I was able to get around that problem by declaring the function outside of states first. Is that the only way to do it?
Mychaeel: Functions declared only within a state are local to that state. You cannot call them from outside the state since they, as the compiler says, do not exist there. (A possible alternative behavior of the language could be to treat functions exclusively declared in a certain state as empty functions in any other state; but that's not how it works.)