Gah - a solution with more questions. – EntropicLqd

Legacy:State

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

This topic will probably get linked from both mapping pages and scripting pages...

For mapping see InitialState, perhaps.

Overview[edit]

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[edit]

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[edit]

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[edit]

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
}

Since InitialState is simply a name variable, you can use the property yourself. For example, you might want to reset the actor:

  GoToState(InitialState);

Extending[edit]

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[edit]

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[edit]

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 determined by special state scoping rules – see below. This can be overriden by the Super and Global Special UnrealScript Keywords.

Functions declared only within a state are local to that state. You cannot call them from outside the state since they 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.)

Note that you can cause a nasty crash using functions that exist only in states if you are not careful. The compiler will allow you do execute state-specific functions after a call to GotoState, which might suggest that the state change is delayed. This is not true. For instance:

state A
{
  function C();
 
  // This crashes, because there is no C() function in state B.
  // The compiler, however, allows it, even though it is not
  // technically permissable.
  function Tick( float Delta )
  {
    GotoState( 'B' );
    C();
  }
}
 
state B
{
}
 
// Calls the E state's F() function, not our own.
state D
{
  function F();
 
  function Tick( float Delta )
  {
    GotoState('E');
    F();
  }
}
 
state E
{
  function F();
}

State code[edit]

State code is code that is within the state block, but outside of any functions. It is usually divided up into sections with labels.Adding ":" after the name declares it as a lable. 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[edit]

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[edit]

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[edit]

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.

SuperApe: Is there a way to find out if a particular state exists? Or, if I try to send an object to a state that doesn't exist, how can I recover?

SuperApe: I seem to remember reading somewhere that there is an Event called when an object has recovered from a bad state (or was it bad state code?). Anyway, if an object is sent to a non-existant state, it will revert to a state of it's class Name. One method to recover this is:

// Some object
GotoState('DoesntExist');
if ( IsInState( Name ) )
     GotoState('DoesExist');

Example[edit]

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.

State Scoping[edit]

The concept of states adds an extra complication to the question: which version of a function does an actor call? Here's the answer:

from UnrealScript Language Reference/States

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.


Basically, if the actor is in state Foo, when it looks for a function, it searches within code declared as within that state as much as possible.

Example[edit]

Suppose we have the following functions defined (using non-standard notation to indicate states):

  • Monster.Trigger
  • Monster.Idle.Trigger
  • BugEyedMonster.Trigger (a child class of Monster)

Now BugEyedMonster is in state Idle, and there is a call of Trigger. What should it do?

  1. there is no BugEyedMonster.Idle.Trigger
  2. look higher up the class tree...
  3. there is a Monster.Idle.Trigger. Execute that.

Now suppose we have:

  • Monster.UnTrigger
  • BugEyedMonster.UnTrigger (a child class of Monster)
  1. there is no BugEyedMonster.Idle.UnTrigger
  2. look higher up the class tree...
  3. there is no Monster.Idle.UnTrigger
  4. Monster is the "eldest" class that defines a state Idle. So we have to give up looking in that state. Switch to looking for non-state version, back at the current class, BugEyedMonster
  5. there is a BugEyedMonster.UnTrigger. Execute that.

Related topics[edit]

Tutorials[edit]

Comments[edit]