Extending States
States can be extended in the same way as classes. A state that extends another inherits functions of its parent state, and can of course override them or add new ones.
Extending states is probably most useful within a single class. With a child class, there is already inheritance: the Child's state Foo inherits the Parent's state Foo anyway. Extending states is useful if you have a state Foo and you also want a state FooSpecialCase, perhaps.
Example
(needs to be rewritten, as is overly complex...)
Suppose we have a class Monster and a subclass BugEyedMonster.
Monster has the following states:
- Idle
- Attacking
BugEyedMonster inherits these states. We can do the following:
Add to an Inherited State
e.g. Add a function RollEyes to the Attacking state. (though I'm not sure what happens to label code outside of functions. help!)
AFAIK label code outside of functions are simply executed when control is passed there via the Goto() command. Epic pretty much always have a set of function calls after the label which invariably change the state of the object to make it do more stuff. When defining a state they place all of the functions within the state first and then have a series of labels after the functions that do appropriate "stuff". In subclasses it looks like you can redefine labels to "override them" but you can never call up to the label defined in the superclass once it has been overidden in the subclass's state. – EntropicLqd
Add a New State
e.g. RunningAway (because BugEyedMonsters are very cowardly)
This state will inherit functions from BugEyedMonster's null state
Extend a State
e.g. ScratchingButt extends Idle. We'd want most of the functionality of Idle, but with an extra occasional animation. We'd also have to add to our inherited Idle state to sometimes switch to ScratchingButt depending on circumstances.
I've probably got it all wrong, but if it makes Mych laugh enough he may fix it. – Tarquin
So if we consider a function present in Monster, eg Trigger, we now have this in several flavours:
In Monster we have:
- Trigger() in null state
- Idle.Trigger() inherits null.Trigger unless we override
- Atacking.Trigger() inherits null.Trigger unless we override
In BugEyedMonster we have
- Trigger() in null state, inherits Monster.Trigger unless we override
- Idle.Trigger() – this is the confusing one. see below
- Atacking.Trigger()
- ScratchingButt.Trigger() inherits Idle.Trigger() unless we override
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.
Examples
Taken from a Discussion on UnrealScript Language Test:
Wormbo: What I mean with "super states" is:
class SomeClass extends Actor; // Super() compiler bug: function A() { // super call to sibling class function that doesn't exist in this or the super class Super(DestroyableTrigger).SpawnEffects(); // compiles and even executes! (spawns some visual effects) } // Super states: // new function, does not exist in Actor function X() { log("SomeClass global X"); } // new state, does not exist in Actor state BaseState { function X() { log("SomeClass BaseState X"); // Super.X() -> "Error, Unknown Function 'X' in 'Class Engine.Actor'" Global.X(); // logs the same } } state ExtendedState extends BaseState { function X() { log("SomeClass ExtendedState X"); Super.X(); // "SomeClass BaseState X" Global.X(); // "SomeClass global X" } } state AnotherExtendedState extends ExtendedState { function X() { log("SomeClass AnotherExtendedState X"); Super.X(); // "SomeClass ExtendedState X" // it's not possible to call "SomeClass BaseState X" directly through a Super(Something).X() construction: // Super(BaseState).X() -> "Error, Bad class name 'BaseState'" // Super(SomeClass.BaseState).X() -> "Error, Missing ')' in 'super(classname)'" Global.X(); // "SomeClass global X" } }
If the object is in state AnotherExtendedState, it will log the following when X is called:
SomeClass AnotherExtendedState X SomeClass ExtendedState X SomeClass BaseState X SomeClass global X SomeClass global X SomeClass global X
The calling structure looks like this:
AnotherExtendedState.X() +- ExtendedState.X() | +- BaseState.X() | | +- global X() | +- global X() +- global X()
OlympusMons: Ahh yes thats very, very clever wormbo How did you ever come up with that concept, might be handy for some AI or something.
Wormbo: PlayerController already uses the concept of extending states within the sme class with BaseSpectating being the base state for Spectating, AttractMode and WaitingForPawn. The Bot class makes extensive use of extending states as well:
MoveToGoal +- MoveToGoalWithEnemy | +- Fallback | | +- Retreating | +- Charging | +- VehicleCharging | +- Hunting +- MoveToGoalNoEnemy +- Roaming NoGoal +- RestFormation
Base state function overriding and extended states
There's a catch with behaviour of extended states when a base state function in subclass is overriden. Suppose we have class like this one:
00001 class SbA extends Actor; 00002 00003 function Test() 00004 { 00005 Log("A" @GetStateName() ,name); 00006 } 00007 00008 function PostBeginPlay() 00009 { 00010 Test(); 00011 GotoState('SBase'); Test(); 00012 GotoState('SExtended'); Test(); 00013 Destroy(); 00014 } 00015 00016 state SBase 00017 { 00018 function Test() 00019 { 00020 Log("A.Base" @GetStateName() ,name); 00021 } 00022 00023 } 00024 00025 state SExtended extends SBase 00026 { 00027 } 00028 00029 /* 00030 Log: 00031 SbA: A SbA 00032 SbA: A.Base SBase 00033 SbA: A.Base SExtended 00034 */
This actor behaves as expected.
Lets override the test function in a subclass:
00001 class SbB extends SbA; 00002 00003 state SBase 00004 { 00005 function Test() 00006 { 00007 Log("B.Base" @GetStateName() ,name); 00008 } 00009 } 00010 00011 state SExtended 00012 { 00013 } 00014 00015 /* 00016 Log: 00017 SbB: A SbB 00018 SbB: B.Base SBase 00019 SbB: A.Base SExtended 00020 */
Notice that in SExtended state SbB.SBase.Test() wasn't called.
Workarounds:
Duplicate the function so it's in SbB.SBase and SbB.SExtended?
Related Topics
Discussion
Category To Do – Revise tutorial fill in with uscript examples.