States System
|
- See also: Stateful Object Spec Oracle
The States System works with the $STATES system node to provide centralized knowledge of the current state of the dynamic objects in an area, such as doors that are opened or closed, levers that have been pulled or not, and other stateful objects.
Overview
HeroEngine allows for the creation of extremely complex environments and game objects that are capable of dynamic changes, which can be triggered by game events or player actions. Each of these changes modifies the "state" of an area. The States System communicates an area's current state, and the $STATES system node provides for a centralized knowledge of an area's state, as well as the actions (code) that process when a state transitions from one value to another.
The power of the states system is that it allows game objects to communicate with each other without needing to know how an object does what it does. As more of the game systems are tied into the States System, more complex interactions are possible. Furthermore, once your technical team has created the appropriate states for your game design, state setup can be embedded as commands in the Asset Library so that World Builders can simply drop actions in and use simple point-n-click operations to "wire" the stateful actions of various objects together.
States can be either abstract in nature, meaning they lack a physical representation in the game, or they may be concrete game objects which are known as "Stateful" objects. States and StatefulObjects may also trigger state changes in other states and stateful objects.
For example:
A (stateful) lever may be wired to trigger the activation of a door. The (stateful) door may then transform its rotation from a closed to opened position, unblock a pathfinding node, and play a sound of metal scraping across stone.
All of which is accomplished without requiring the lever know how the door actually works. The lever merely set the state for the door from "closed" to "opened".
Components
The states system is composed of these major components:
- A system node ($STATES) which implements the core of the system
- A states editor allowing for creation and editing of existing states via a GUI.
- A Stateful Object Specification Oracle, supporting a template which the Library may use when it instantiates objects to make them stateful objects.
- A stateful object specification editor allowing for the creation and editing of specifications via a GUI.
What problem(s) does this solve?
- Allows for complex but loosely coupled interaction between game objects and systems.
- Communication of state information to all connected clients as well as clients that subsequently connect to the area.
- Saving state data to be used subsequently to restore an area to a "save" spot. For example: spinning up a quest or mission area in the proper 'state' where it was left off.
What problem(s) does this not solve?
- The States System does not eliminate the need to write code for your systems, it merely provides a mechanism by which an event (a transition from one state to another) may trigger another system's code. In the end, once these game-specific systems are developed, your world builders can use GUIs to implement many things, without resorting to any HSL.
Concepts
States have Discrete Values
A state has a current value which is the absolute for the state at a particular moment in time. That is to say, states have one and only one value at any time.
States Reside In One Local GOM
States are nodes that reside in the Local GOM for the area in which they are created. In other words, each area knows only about the states created in that area.
Elements of a State
States are composed of three major components:
- Values
- ActionLists
- Transitions
Values
A state has both a default value and a current value, and other possible valid values.
When first created, any state has two values: NOTSET and * (wildcard).
The NOTSET value is a special one used when the state object initializes during area spinup and player logon. It allows for transitions from a noninitialized state (notset) to a default value. It will be rare that you would have reason to explicitly set a state to NOTSET.
The *(wildcard) value is used to create a transition where it should always happen when transitions to (or from depending where you place the wildcard) a particular value. Note that the wild card does not mean match on any value, rather it allows for a definition where you do not care what one of the values is.
Action Lists
Action lists define a set of actions that should occur when the action list is "played" (the result of a transition occurring). An action list may be used by 0...N transitions, though an action list that is used by no transitions will never execute in the normal course of the state system's operation.
Each action list is composed of any number of "state actions". By default, the states system includes the following actions:
- CALLSCRIPT
- PLAYFX
- PLAYSOUND
- SETFIELD
- SETPROPERTY
- SETSTATE
- TRANSFORM
- UNMARSHAL
You may extend the state system to include new, custom state actions for your own needs.
Transitions
Transitions define a fromValue, toValue and an action list to execute. Whenever a state is set to a new value, the states system checks for three potential transitions and executes any that are found. The transitions checked are:
- currentValue --> *(wildcard)
- currentValue --> newValue
- *(wildcard) --> newValue
For each of those transitions, if there is an action list defined it will be "played" executing each of its state actions.
Objects of the State System are Assigned Unique Keys
While the states system is capable of using strings for all of its operations, internally all objects created by the states system are assigned unique IDs, which are used whenever one object refers to another. This capability is used to solve the problem of renaming objects, it also allows for the IDs to be used in script rather than hard coding a string value that could be later be changed to display a different string.
All of which means that while you may have script code that sets the value of some state by string, it is safer and more resistant to later changes to use the unique key assigned to a given string value.
The Area States System is a Concrete Implementation
The clean engine area states system is a concrete implementation of the states system. The system was designed as abstract classes, allowing you to freely use them to create other types of stateful systems leveraging all of the generic code and extending/overriding it as desired.
For most things, the concrete implementation included in clean engine (area states) should be more than adequate but the capability to extend it is there should it be needed.
Usage
The creation of a new state is achieved through a series of steps. It is possible to navigate those steps either with a GUI, in HSL, or via templates created using the stateful object spec system. Creation of states using the stateful object system is somewhat specialized and will not be included in the general usage, instead it will be in its own section.
- Create a New State
- Add Values to the State
- Add ActionLists to the State
- Define Actions for an ActionList
- Define Transitions
Invoking the State Editor GUI
The States Editor may be invoked using the Hotspot menu (control-->shift-->left-click in the top left corner of the viewport), and it is found under the menu option in the Tools Tab (left). If other types of state systems have been created using the abstract classes of the States System, it will be necessary to add new menu options to invoke their editors.
Additionally, objects created by the stateful object spec system have a right-click menu option available (while in "edit mode") for editing their state (right).
Create a New State
States may be created:
- By Hand using the State Editor GUI
- HSL Scripts
Create: State Editor GUI
Clicking the "Add" button on the states system editor will cause a new state object to be created assigning a default name to it. New states automatically have the NOTSET and * values added to their values list.
The name assigned to the state may be changed by doubleclicking the new state and using the Properties Tab of the State Editor GUI to change the _StateDisplayName field. It is considered a "best practice" to make the name for your state unique for a given area, but the state system functions internally on unique IDs to it is not a requirement.
Create: HSL
Creation of a new state using HSL requires you find the "StateObjects" node for a particular system and calling the create method on it. The StateObjects node acts as a collection of States for a given states system like the area states. In the case of name collisions, the states system automatically makes the states name unique by adding an incremented integer to the specified name.
Information on adding new states systems can be found in the advanced usage section.
// Find the stateObjects node, clean engine currently has one states system _areaStates // areaStates as NodeRef of Class _StateObjects = $STATES._GetStatesNodeByClass( "_areaStates") // Specify a name for the new state name as String = "Foo" // Create the AreaState node var state = areaStates._CreateStateObject( name )
Add State Values
States may only be set to values that have been added to the state as valid ones. IE You may not set a state to "bob" unless bob has previously been added as a potential value. State values should express discrete concepts such as a door is OPEN or CLOSED or a monster spawner is ACTIVE, TRIGGERED or INACTIVE. Restricting your values to discrete concepts in this manner ensures that transitions make conceptual sense and helps clearly express what happens when a state transitions from one value to the next.
Add Value: State Editor GUI
A new state value may be added by opening the state editor for a particular state (double clicking on the state in the states editor gui), swapping to the Values Tab and clicking the Add button (right). This will cause the states system to add a new value to the state with a default name.
Double clicking (or clicking the edit button) on the new value invokes a node property editor for the state value allowing the name of the value to be updated to a more appropriate one (left).
Add Value: HSL
Assuming you have a reference to a state, creating a new value requires a two lines of code.
// Retrieve the values collection node var values = state._GetStateValidValuesRoot() // Add a new value by string values._CreateStateValidValueForValue( "OPENED" )
Add Action Lists
Action lists act as a collection of actions to "play" when a transition occurs. A given action list may be used by 0...N transitions. However, an action list that is used by no transitions will never execute its actions through the normal processes of the states system.
Add Action List: States Editor GUI
Using the states editor, adding a new action list requires but a single click on the Add button located on the ActionLists Tab of the GUI. The new action list will be assigned a default name which you may change by editing the action list (either by double-clicking it or by selecting it and hitting the edit button).
Add Action List: HSL
Action lists are organized by the ActionListsRoot node, assuming you have a reference to the state you may create a new action list by retrieving the root node and calling a method (which functions as a Singleton).
// Assuming you have a reference to the state var actionLists = state._GetStateActionListsRoot() // Create the actionList, the method is implements the Singleton Pattern var barList = actionLists._GetStateActionListByName( "bar" )
Add Actions
State actions represent well defined behaviors that utilize some amount of parameterized data to perform their tasks. For example, one type of state action is TRANSFORM which has the ability to transform the position/rotation/scale of a specified node. Most state actions have a "location" in which they are to be executed, this refers to the local GOM which will be affected (client, server or both client and server).
State actions include logic to perform their actions appropriately when a player enters the area to sync up that player's view of the world without rerunning code on the server. The practical implication of this is that state actions will not attempt to remodify a field (SETFIELD action) or recall a script (CALLSCRIPT) on the server when a player logs in, but they will perform those actions if they are set to be performed on the client. The reasoning for this behavior is that the actions already performed their duties on the server so it would not be appropriate to reexecute them there, whereas actions that need to be performed on the client need to be performed when a player logs in to synchronize them with how other players in the area see things.
- State Action Types
- CALLSCRIPT
- The callscript action calls a specified script at a shared function _OnDoStateAction in the specified local GOMs (client, server or client and server). Arbitrary data is passed in a string field _StateActionData and the script to be called is stored in the string field _StateActionToScript.</td>
- PLAYFX
- Plays the specified FX (created using the clean engine _fxSpecOracle system) stored in the field _stateActionPlayFxSpecKey , with targets and caster set as specified in the state action's _stateActionPlayFxTarget and _stateActionPlayFxCaster fields.</td>
- PLAYSOUND
- Plays the specified sound targeting the position of the target node with the sound's size indicated by the soundSize enumeration.
- SETFIELD
- Sets the field on the specified node located in the specified local GOMs (client, server or client and server) to a value. This action only works for scalar fields (id, int, float, vector3, string, noderef, and boolean) that can be autoconverted to/from string.</td>
- SETPROPERTY
- Sets a property on the specified node located in the local client GOM.
- SETSTATE
- Sets the current value of a state to another specified value. Internally this action stores unique state object keys for the state and values so that subsequent name changes do not cause problems. The GUI automatically translates these keys into nice human readable strings.
- TRANSFORM
- Transforms the target node's position/rotation/scale in the local client GOM by offsets (from the saved server value). This results in the local view of an object's position/rotation/scale changing. To return the object to its original value you simply provide offsets of (0,0,0).
- UNMARSHAL
- Unmarshals a string marshaled using the marshalUtils:MarshalNodeWithClass functions to the specified target node in the specified location (client, server or both client and server local GOMs). The marshal string must be in the format expected by the marshalUtils utility script for this state action to function. Primarily useful in initializing a node located in a local Client GOM with classes and fieldValues dicated by the server.
Add Actions: Action List Editor GUI
Using the new state action chooser, select the type of state action you would like to have added to the action list. The states system will create a state action object of the appropriate type with its parameters initialized some whatever makes sense. The action list will update itself displaying the added state action which you may now double-click or select its row and click the editor button to open a node property editor for the state action.
CallScript
The CallScript state action will call a script specified at a shared function _OnDoStateAction passing in the state action node (or a copy of the node in the cast of scripts called in a local GOM other than the area server's). The state action node supports the inclusion of arbitrary string data in a field _StateActionData, additional behaviors and data could be GLOMmed on should a system require additional functionality beyond what is possible calling an arbitrary script.
PlayFx
The PlayFx state action plays a special effect created using clean engine system _FxSystem using the target and caster specified by the state action. In practice, this is only useful for FXs whose targets are static.
For example you might use this type action to execute an FX that has dust and particulate matter drifting to the ground in conjunction with the "opening" of an ancient door. Since the door is a static object (ie is always present in the area's local GOM) it is possible to set the door's ID as the caster/target for the FX.
PlaySound
The PlaySound state action will attempt to set the specified sound located at a given target node. There is no capability to error check this the sound specified actually exists, so when debugging problems with this state action double check the path/sgt specified is correct..
SetProperty
The SetProperty state action will attempt to set the specified property for a given target node to the specified value. This state action relies on autoconversion, as such the value string must be in an appropriate format for the target property. There is no capability to error check this value, so when debugging problems with this state action double check the format of your value string.
SetField
The SetField state action sets the specified field on the target node to a specified value. This state action relies on autoconversion, consequently you may only use this state action for the scalar fields that are handled by autoconversion.
The node's field may be set in one or more local GOMs as specified by the _stateactionexecutionlocation, supporting setting the field in the client, server, or client and server local GOMs.
SetState
The SetState state action sets the specified state to the specified value for that state. This capability is the major feature facilitating the loosely coupled interaction of systems and items using the states system. Under the hood, the string values displayed by the GUI are actually unique _stateObjectKeys allowing for states or their values to be renamed without affecting functionality of the system.
Transform
The Transform state action will transform the position/rotation/scale of a specified target node by offsets from the server's saved value for those fields. This state action is useful for "animating" objects such as a door that opens by rotating -45 on its X axis. Transformations are performed in the local Client GOM.
Since transformations are performed as offsets from a server position, setting the offsets to (0,0,0) returns the transformed node to the state the server has as its "normal/saved" state.
The reverseable flag causes the transformation to do a reverse transition as soon as the transformation target value is reached, returning the object to its original values.
UnmarshalData
The UnmarshalData state action unmarshals a string created using the MarshalUtils functions (MarshalNodeWithClass and MarshalNodeWithClassSpecifiedFields). The state action will unmarshal the string stored in its _stateActionData string using the MarshalUtils script's unmarshal functions, GLOMming on classes as necessary (provided they exist in the target local GOM).
As a result of the data being assembled as XML, it is problematic to create one of these state actions by hand. It is however, reasonable to tweak the string once it is created by altering the _stateActionData. Care must be taken to avoid disturbing the structure of the XML string.
![]() |
It is possible as we refine our reflection solution for server-client nodes that this state action will be for most situations obselete. |
This state action is typically used to initialized a node located in the local Client GOM, adding classes and field values to the node when a player enters the area.
Add Actions: HSL
There are different methods for creating a new state action, each with a signature that specifies the parameters it needs for the action to function.
CallScript
The callscript action calls a specified script at a shared function _OnDoStateAction in the specified local GOMs (client, server or client and server). Arbitrary data is passed in a string field _StateActionData and the script to be called is stored in the string field _StateActionToScript.
// Assuming you have a reference to an action list // // unique method _StatesObjectCreateStateActionCallScript( statesObjectClass as String, scriptToCall as String, // data as String, location as Enum _StateActionExecutionLocations ) as NodeRef of Class _StateAction // var callScript = $STATES._StatesObjectCreateStateActionCallScript( "_areaStates", somScript, Data, CLIENT ) someActionList._AddStateActionToActionList( callScript )
PlayFX
Plays the specified FX (created using the clean engine _fxSpecOracle system) stored in the field _stateActionPlayFxSpecKey , with targets and caster set as specified in the state action's _stateActionPlayFxTarget and _stateActionPlayFxCaster fields.
For example you might use this type action to execute an FX that has dust and particulate matter drifting to the ground in conjunction with the "opening" of an ancient door. Since the door is a static object (ie is always present in the area's local GOM) it is possible to set the door's ID as the caster/target for the FX.
// Assuming you have a reference to someActionList // // unique method _StatesObjectCreateStateActionPlayFx( statesObjectClass as String, // fxKey as id, caster as _target, target as _target, location as // Enum _StateActionExecutionLocations ) as NodeRef of Class _StateAction // var PlayFx = $STATES._StatesObjectCreateStatePlayFx( "_areaStates", fxKey, caster, target ) someActionList._AddStateActionToActionList( PlayFx )
PlaySound
Plays the specified sound segment located at the position of the target node.
For example you might use this type action to play a sound of a creaking door in conjunction with the "opening" of an ancient door. Since the door is a static object (ie is always present in the area's local GOM) it is possible to set the door's ID as the target of the sound
// Assuming you have a reference to someActionList // // unique method _StatesObjectCreateStateActionPlaySound( statesObjectClass as String, // targetID as ID, sound as String, soundSize as enum soundSizes, location as // Enum _StateActionExecutionLocations ) as NodeRef of Class _StateAction // var PlaySound = $STATES._StatesObjectCreateStatePlaySound( "_areaStates", targetID, sound, soundSize ) someActionList._AddStateActionToActionList( PlaySound )
SetField
Sets the field on the specified node located in the specified local GOMs (client, server or client and server) to a value. This action only works for scalar fields (id, int, float, vector3, string, noderef, and boolean) that can be autoconverted to/from string.
// Assuming you have a reference to someActionList // // unique method _StatesObjectCreateStateActionSetField( statesObjectClass as String, // targetID as ID, fieldName as String, data as String, // location as Enum _StateActionExecutionLocations ) as NodeRef of Class _StateAction // var setField = $STATES._StatesObjectCreateStateSetField( "_areaStates", targetNodeID, "someField", "someValueAsString", SERVER ) someActionList._AddStateActionToActionList( setField)
SetProperty
Sets a property for the specified node located in the local client GOM to a specified value. The property specified must be of a type for which autoconversion from string works.
This action is only valid in the CLIENT Local GOM so there is no opportunity to pass the execution location through the signature.
// Assuming you have a reference to someActionList // // unique method _StatesObjectCreateStateActionSetProperty( statesObjectClass // as String, targetID as ID, PropertyName as String, data as String ) // as NodeRef of Class _StateAction // var setProperty = $STATES._StatesObjectCreateStateSetField( "_areaStates", targetNodeID, "someProperty", "someValueAsString" ) someActionList._AddStateActionToActionList( setProperty )
SetState
Sets the current value of a state to another specified value. Internally this action stores unique state object keys for the state and values so that subsequent name changes do not cause problems. The GUI automatically translates these keys into nice human readable strings.
// Assuming you have a reference to someActionList // // unique method _StatesObjectCreateStateActionSetState( statesObjectClass as String, // stateKey as ID, stateValueKey as ID ) as NodeRef of Class _StateAction // var setState = $STATES._StatesObjectCreateStateActionSetState( stateKey, stateValueKey ) someActionList._AddStateActionToActionList( setState )
Transform
Transforms the target node's position/rotation/scale in the local client GOM by offsets (from the saved server value). This results in the local view of an object's position/rotation/scale changing. To return the object to its original value you simply provide offsets of (0,0,0).
// assuming you have a reference to the openActionList, this transform would rotate the node "theNode" // by 45 degrees over 5 seconds // // unique method _StatesObjectCreateStateActionTransform( statesObjectClass as String, target as NodeRef, // positionOffset as Vector3, rotationOffset as Vector3, scaleOffset as Vector3, duration as TimeInterval, // location as Enum _StateActionExecutionLocations ) as NodeRef of Class _StateAction // var openTransForm = $STATES._StatesObjectCreateStateActionTransform( "_areaStates", theNode, "0,0,0", "-45,0,0", "0,0,0", 0:00:05.000, CLIENT ) openActionList._AddStateActionToActionList( openTransForm )
Unmarshal
Unmarshals the marshal string of a node marshalled with the marshalUtils script onto a target node, GLOMming on classes as appropriate to the target node to hold the data from the original node (assuming the target GOM has those classes defined).
![]() |
It is possible as we refine our reflection solution for server-client nodes that this state action will be for most situations obselete. |
This state action is typically used to initialized a node located in the local Client GOM, adding classes and field values to the node when a player enters the area.
// Assuming you have a reference to someActionList // // unique method _StatesObjectCreateStateActionUnmarshalData( statesObjectClass as String, targetID as ID, // data as String, location as Enum _StateActionExecutionLocations ) as NodeRef of Class _StateAction // var unmarshalAction = $STATES._StatesObjectCreateStateActionUnmarshalData( "_areaStates", targetID, marshalledString, CLIENT ) someActionList._AddStateActionToActionList( unmarshalAction )
Add Transitions
Transitions define a fromValue, toValue and an action list to execute. Whenever a state is set to a new value, the states system checks for three potential transitions and executes any that are found. The transitions checked are currentValue --> *(wildcard), currentValue --> newValue, and *(wildcard) --> newValue. For each of those transitions, if there is an action list defined it will be "played" executing each of its state actions.
When an area is spun up, or a player logs into an area, the NOTSET-->DefaultValue and DefaultValue-->CurrentValue(if default is different from the current) transitions play. This allows for the construction of initialization transitions using the NOTSET value.
Add Transitions: State Editor GUI
Adding a new transition requires a single click on the Add button located under the Transitions Tab of the State Editor. The states system will add a new transition with some default values to the transitions list. Double-clicking the new transition in the transitions list or selecting it and hitting the Edit button invokes the transition editor which may be used to set the default values to those defining a particular transition.
Add Transitions: HSL
Creating a transition via HSL requires only a couple method calls assuming you have a reference to the state itself.
// Assuming you have a reference to the state already, and that state has the values NOTSET, OPENED // and an actionlist named OPENING defined // // Get the transitions root for the state var transitions = state._GetStateTransitionsRoot() // Add a Transition var trans = transitions._CreateStateTransitionByValues( "NOTSET", "OPENED", "OPENING" )
Set a State's Default Value
A states default value is important because it defines the starting point for a state during area spinup or player logon, during these events the NOTSET-->Default Value transition runs (if defined). After the default, then the Default Value-->Current Value transition runs (if defined and the default/current values are different).
Set a State Default Value: State Editor GUI
Setting the default value for a state using the state editor GUI is accomplished using the dropdown box for the field _stateDefaultValueID located on the Properties Sheet of the editor. Once a default value is choosen, click the Ok button to submit the changes to the server.
Set a State Default Value: HSL
The state's default value may be set either by String or by the _stateObjectKey for a value. Regardless of which method you use, the value must be one that is "valid" for the state. That is to say, the value must already have been declared using the _CreateStateValidValueForValue method.
The preferred method for setting a state to a new value is using the _SetStateByValueKey which uses the unique object key assigned to the objects of the states system (such the values for a given state). Using the _statefulObjectKey avoids a host of problems associated with using strings.
Set Default Value by String
This method is used to set a state's default value to a new value based on a string. The value must already have been declared as a valid one for the state by _CreateStateValidValueForValue.
// Assuming you have a reference to the state state._SetStateDefaultValueByValue( "CLOSED" )
Set Value by _statefulObjectKey
![]() |
This is the preferred method for setting a default value. Using the object keys avoids a host of problems associated with renaming. |
This method is used to set a state's default value to a new value using the unique object key generated for objects of the states system. The value must already have been declared as a valid one for the state by _CreateStateValidValueForValue.
// Assuming you have a reference to the state, and a valid _statefulObjectKey for some value state._SetStateDefaultValueByValueKey( key )
Setting a State's Current Value
Once a state is set up, eventually you will want to set its current value to a new value. Setting a new state value can be done via GUI, HSL and a Right-Click Menu (stateful objects).
Setting a State's Current Value: States Editor GUI
Setting the current value for a state using the state editor gui is done using the dropdown box for the field _stateValueID located on the Propery Sheet of the editor GUI. After selecting the desired value, the Apply State Value button to apply the new value to the state.
Resultant to the application of the new value, (appropriate) state transitions will occur.
Setting a State's Current Value: HSL
The state's value may be set either by String or by the _stateObjectKey for a value. Regardless of which method you use, the value must be one that is "valid" for the state. That is to say, the value must already have been declared using the _CreateStateValidValueForValue method.
The preferred method for setting a state to a new value is using the _SetStateByValueKey which uses the unique object key assigned to the objects of the states system (such the values for a given state). Using the _statefulObjectKey avoids a host of problems associated with using strings.
Set Value by String
This method is used to set a state's current value to a new value based on a string. The value must already have been declared as a valid one for the state by _CreateStateValidValueForValue.
// Assuming you have a reference to the state state._SetStateByValue( "CLOSED" )
Set Value by _statefulObjectKey
![]() |
This is the preferred method for setting a state to a value. Using the object keys avoids a host of problems associated with renaming. |
This method is used to set a state's current value to a new value using the unique object key generated for objects of the states system. The value must already have been declared as a valid one for the state by _CreateStateValidValueForValue.
// Assuming you have a reference to the state, and a valid _statefulObjectKey for some value state._SetStateByValueKey( key )
Setting a State's Current Value: Right-Click Menu (Stateful Objects)
Stateful objects have a built-in right-click menu for manipulating their states, this includes setting the current value of the state to one of the values declared as valid for the state.
Wiring States Together
The power of the states system lies in its ability to enable loosely coupled connections between systems and stateful objects. This loose coupling requires neither side of the equation to know or understand how the other works so long as they talk to each other by setting states from one value to another.
As your developers create new systems/objects using the states system, they can immediately leverage that loose coupling to connect old (stateful) systems to the new system/objects.
The act of wiring one state to another is nothing more than the addition of a SETSTATE state action to the action lists that occur when the first state goes to a particular value.
For example: Take our stateful lever example, it has two values ON and OFF. If I want the lever to open a door whenever it is set to ON, then I simply add a SETSTATE action to any actionlist that plays as a result of a transition from any Value-->ON that sets the door's state to OPEN. Likewise, if I want the door to close whenever the lever's state is set to a value of OFF, I add a SETSTATE action to any actionList that plays as a result of a transition from OFF-->any Value.
Wire States: Using the StatefulObject Right-Click Menu
Assuming you have a statefulObject or one that implements the appropriate context menu item shared functions for wiring states, wiring a stateful object to set another state's value is trivial. While in edit mode in HeroBlade, right-click on the statefulObject and use the menu that appears to wire the values of that objects state to set values for other states.
There is no right-click menu for states that exist without a physical (graphical) representation in the world.
Note: You must do this for each of the values of your statefulObject's state that you wish to trigger another state. That is to say, if you set ON to set some door's state to OPENED you must ALSO set the OFF to set the door's state to CLOSED (if you want it to function in that manner).
Wire States: State Editor GUI
This is identical to adding a SETSTATE action to the appropriate actionlists.
Wire States: HSL
As with using the GUI, wiring a state to set another state requires nothing more that the addition of a SETSTATE action to the state's action lists.
Advanced Usage
Data Structures of the States System
The Data Structures of the States System are implemented as abstract classes, allowing for the potential for multiple concrete implmentations. Clean Engine currently has only a single concrete implementation, Area States.
States System Node
The system node $STATES acts as the interface to the states system, like most system nodes implemented for the Clean Engine it allows for extension/overriding of certain methods by GLOMming a game-specific class onto the prototype (named STATES).
State Objects Collection
Each concrete implementation of the abstract system has a single state objects node (child of the _stateObjects class) that serves as the anchor for all states in that system. Each state objects node is anchored to the state root node which in turn is anchored to the area root node via a base hard association.
State Object
The state object is the fundamental unit for the states system. Each states object is made up of three node collections; transitions, values, and action lists. Action Lists in turn are node collections of actions.
Listening For State Value Changes
The State Object is a child of the ObsSubject class and passes events using those methods. When the state's current value changes, and event is raised using the eventObject class as the container for the event.
// me is the state object eObj as NodeRef of Class EventObject = CreateNodeFromClass("EventObject") eObj.eventType = "setstatevalue" eObj.eventCausedByID = oldValueKey // old value _stateObjectKey eObj.eventAffectsID = me._StateValueID // new value _stateObjectKey me.setChanged() me.NotifyListeners( eObj )
Listening to for this event simply requires the creation of a listener (child of the obsListener class) and adding it to the subject (in this case the state object node).
// Create a node listener that will receive the event notification // var listener = state.createNodeListener( notifyNodeID, false ) state.addListener( listener )
Adding a New States System
![]() |
This is a very advanced topic which may be documented at some later date, should the need for such documentation be expressed. |
The states system is an abstract system, for which the area states system is the concrete implementation. It is possible to make another concrete implementation, but it is very complex.
Currently, as there is no pressing need nor is there currently any plan by any licensee to use this capability, we are going to leave this undocumented.
Reference
The Stateful Object Spec Oracle
The stateful object spec oracle is used in conjunction with the Library's ability to run a /command during instantiation of objects from the library to create a state for those object(s).
Stateful Object Specification Oracle
Creation of a Sample State in HSL
// assume aDoor is a noderef to an asset that is a door we wish to make stateful // statefulObject as noderef = aDoor areaStates as NodeRef of Class _StateObjects = $STATES._GetStatesNodeByClass( "_areaStates") areaStateName as String = "Sample" // Create the AreaState node var state = areaSTates._CreateStateObject( areaStateName ) // GLOM on the stateful object class or one of its children, this supports right-click menus for wiring // finding the object's state and editing the state GLOMClass("_statefulObject", statefulObject ) // Change base_hard_association to link it to the statefulObject, allowing simultaneous deletes state._SwapStateBaseHardAssociationToTarget( statefulObject ) var values = state._GetStateValidValuesRoot() // Set valid values to use for transitions values._CreateStateValidValueForValue( "OPENED" ) values._CreateStateValidValueForValue( "CLOSED" ) // default state, ie all doors start closed state._SetStateDefaultValueByValue( "CLOSED" ) var actionLists = state._GetStateActionListsRoot() // Create the actionList var openActionList = actionLists._GetStateActionListByName( "open" + areaStateName ) var closeActionList = actionLists._GetStateActionListByName( "close" + areaStateName ) var initOpenActionlist = actionLists._GetStateActionListByName( "initOpen" + areaStateName ) var initClosedActionList = actionLists._GetStateActionListByName( "initClose" + areaStateName ) var transitions = state._GetStateTransitionsRoot() // Add Transitions var initOpen = transitions._CreateStateTransitionByValues( "NOTSET", "OPENED", "initOpen" + areaStateName ) var open = transitions._CreateStateTransitionByValues( "CLOSED", "OPENED", "open" + areaStateName ) var close = transitions._CreateStateTransitionByValues( "OPENED", "CLOSED", "close" + areaStateName ) var initClose = transitions._CreateStateTransitionByValues( "NOTSET", "CLOSED", "initClose" + areaStateName ) // Add actions to actionlists var openTransform = $STATES._StatesObjectCreateStateActionTransform( "_areaStates", switchID, "0,0,0", "-45,0,0", "0,0,0", 0:00:05.000, CLIENT ) var closeTransForm = $STATES._StatesObjectCreateStateActionTransform( "_areaStates", switchID, "0,0,0", "0,0,0", "0,0,0", 0:00:05.000, CLIENT ) closeActionList._AddStateActionToActionList( closeTransForm ) initClosedActionList._AddStateActionToActionList( closeTransform ) initOpenActionList._AddStateActionToActionList( openTransform ) openActionList._AddStateActionToActionList( openTransform ) // Initialize to the default value state._SetStateByValue( "CLOSED" )