UDN
Search public documentation:
UnrealScriptStates
日本語訳
中国翻译
한국어
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
中国翻译
한국어
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
UnrealScript States
Overview
- 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.
// 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' ); } }
state() MyState { ... }
state MyState { ... }
auto state MyState { ... }
State Labels and Latent Functions
auto state MyState { Begin: `log( "MyState has just begun!" ); Sleep( 2.0 ); `log( "MyState has finished sleeping" ); goto('Begin'); }
"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 "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.
// 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" ); ... }
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
State inheritance and scoping rules
- 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 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" ); }
- 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
// 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... }
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... }
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.state FirstState { function Myfunction() { doSomething(); PushState('SecondState'); // this will be executed immediately since we're inside of a function (no latent functionality) JustPushedSecondState(); } Begin: doSomething(); PushState('SecondState'); // this will be executed once SecondState is popped since we're inside of a state code block (latent functionality) JustPoppedSecondState(); } state SecondState { event PushState() { // we got pushed, push back PopState(); } }
state BaseState { ... } state ExtendedState extends BaseState { ... }
ExtendedState
then IsInState('BaseState') will return false. Ofcourse calling IsInState('BaseState', true) will return true if BaseState is on the stack.