# StateMachine.js
*Written by Aidan Wolf - Last updated March 8th, 2024*
StateMachine is a standardized scriptable building block for creating emergent yet consistent functionality, fusing interaction, animation, physics, and GG Networking features into one. StateMachine is definitively the best way to rapidly create robust scriptable objects in LS.
# Concepts
#### States
States contain an ordered list of functions to be executed left ro right. Only functions in an active state are executing, and functions are self-terminating. As the name implies a state represents the state of an object (for example `idle:` and `walk:`)
#### Functions
Functions sit inside States like so `walk:anim("walk"),moveFwd(4).` By default functions will target the Camera (Player) for movement, direction, etc. You can view available functions in the API below, however documentation is still sparse and WIP.
#### Execution Order
States are read left to right so functions execute in that order. Functions can block the call order meaning a `delay(0.5),...` will hold up the state for half a second. This is useful for interactions like `tap` which will wait for a tap to occur. States will end on the right and wait. All functions will immediately stop executing when the state is switched which is very useful.
#### State Naming
Name your state by adding `stateName:` in front of the functions. This will enable you to call `onStateName` to switch to the state and `onStateNameDeep` to switch states including all children with that state available. You can also call `doDeep("stateName")` to trigger a state change on all children but not the parent, or `doUpAndDeep("stateName")` which finds the top-most parent with the state, and then calls it on all its children recursively.
#### Init State
By default the first state row is the initialization state, named or not. This is called automatically. You can optionally name it as `stateName:` if you need to call it from another state. In rare cases you may need to toggle on `Init Immediately`. This is helpful if you need to immediately `hide` `disable` `alpha(0)` an object.
#### Parameters
Paranetheses and paramaters are optional but you can use explore the params to tweak the default values. i.e. `tap` is the same as `tap()`. The functions are written to let you fine tune them and get robust results with parameters but there is always default behavior.
#### Variables
Variables can be created using `setVar("key",value)` Variables can be accessed using `getVar("key")` or `"$key"` which is useful for in strings (ex: `text("I have $KEY apples!")`)
#### StateMachine Nesting
StateMachines work on their own or together when nested in the same object heirarchy. This enables you to call functions such as `doUpAndDeep("state")` from a child to change the state of every other child object under the same parent, and `doDeep("state")` to change the state of every child object.
IMPORTANT: Child states can only be changed when each object in the hierarchy has that same state name.
`onStateDeep` -> `[]` -> `[state:yay]` ❌
`onStateDeep` -> `[state:]` -> `[state:yay]` ✅
State Names stay pretty consistent within a heirachy as they represent a literal state of how the object should be given a condition. A good rule of thumb is if you have a lot of different state names on child objects that aren't in your parent you're probably doing something wrong.
In the case you're parenting newly instantiated objects using `spawn()` and expect to be able to make nested calls, the object heirarchy needs to be manually refreshed which is done easily using `refreshHierarchy` after `spawn` or `parent`
#### Function Nesting
By design, functions can only be nested once but not more. `text(getVar('health'))` would display health points for example, but `text(getVar(getVar('pointer')))` will not work. If you are trying to attempt more than one nest you are probably using it wrong.
#### Variable Nesting
For convenience, the state machine will attempt to find a variable value wherever its available in the parent/child hierachy. For example, if you have a parent with `setVar("hp",3)` any child object could retreive it just with `$hp` or `getVar("hp")`. Important is it will get the closest available value for that variable including itself, so be careful of storing variables.
`parent.hp = 3`
` |_child...`
` . |_child(n) (StateMachine) log(getVar("hp"))`
`log outputs 3`
# Examples
#### Chase
`init:lookAt(3),moveFwd(5)`
*moves towards the player at a speed of 5*
#### Go to a yummy food
Target `init:id("yummyfood")`
Foody `init:target("yummyfood"),lookAt(3),moveFwd(5)`
*foody moves towards the yummy food at a speed of 5*
#### Tap Interaction
Target `init:id("yummyfood")`
Foody `init:tap,target("yummyfood"),lookAt(3),moveFwd(5)`
#### State Change
`init:onIdle`
`spin:rotate(10),delay(1),onIdle`
`idle:bob,delay(1.5),onSpin`
#### Variables
`init:setVar("hp",3),updateVar("hp+=1"),log($hp)`
output: 4
`init:getVar("coins",0),updateVar("coins+=1"),storeVar("coins")`
1... 2... 3... coin count would increment 1 every session
#### Nesting 101
Parent
`init:onHamburger`
`hamburger:yay`
Child
`init:`
`hamburger:yay`
⚠️ **Parent would say "yay" but child would not say "yay"**
---
Parent
`init:onHamburgerDeep`
`hamburger:yay`
Child
`init:`
`hamburger:yay`
✅ **Parent and child would both say "yay"**
---
Parent
`init:doDeep("hamburger")`
`hamburger:yay`
Child
`init:`
`hamburger:yay`
⚠️ **Only child would say "yay"**
---
Parent
`init:`
`hamburger:yay`
Child
`init:doUpAndDeep("hamburger")`
`hamburger:yay`
✅ **Parent and child would say "yay"**
---
Parent
`init:onHamburgerDeep`
`hamburger:yay`
Child
`init:`
Child of Child
`init:`
`hamburger:yay`
⚠️ **Only parent would say "yay" because child doesn't have `hamburger:` state**
---
Parent
`init:onHamburgerDeep`
`hamburger:yay`
Child
`init:`
`hamburger:`
Child of Child
`init:`
`hamburger:yay`
✅ **Parent and child of child would say "yay" because middle child also has hamburger state, even if empty..**
---
# API
#### Test Function
|function|description|usage|
|-----|-----|-----|
| yay | says 'yay' in console, useful for sanity checks | yay
| log | log a message in console | log(message:string)
#### State Sequencing
|function|description|usage|
|-----|-----|-----|
|delay|delay state executation for X seconds|delay(seconds:number)
|focus|if next function is blocking optional, focus forces function to execute completely|focus,scale(1,1),... will wait until scale completes
#### Sensing
|function|description|usage|
|-----|-----|-----|
id|gives an object a unique ID that can be accessed from other objects in functions like `target`|id(id:string)
target|set active target to an object. `CAMERA` is default, representing the player|target(id:string)
nearest|set active target to the nearest object with ID or name|nearest(delimiter:string)
groundCheck|block until object reaches the ground|groundCheck(checkSteps:number)
onscreen|block until object is on screen|onscreen(padding:number)
offscreen|block until object is off screen|offscreen(padding:number)
proximity|block until in proximity to target|proximity(distance:number, changeToState:string)
fov|blocks until target is in field of view. default cone is 0.4|fov(cone:number, distance:number, changeToState:string)
distance|blocks until target is close or far a certain distance |distance(distance:number, operator:string, changeToState:string)
findMesh|interacts with `WORLD_MESH` or `WORLD_FLOOR` to find a position in the world, good for spawning things|findMesh(rayStart:vec3, rayEnd:vec3, once:bool)
findFloor|convenience function to find a floor position some distance in front of player|findFloor(distance:number)
findWall|convenience function find a wall position some distance in front of player. Only works with `WORLD_MESH`|findWall(distance:number)
#### Instantiate
|function|description|usage|
|-----|-----|-----|
spawn (from SpawnManager)|spawns a prefab at the location of this object (ignore the extra params like x,y,z, etc)|spawn(prefabIndex:int) spawn(categoryName:string, prefabIndex: int)
spawnAndRefresh|spawns a prefab at the location of this object, parents it, and refreshes the stateMachine hierarchy|`spawnAndRefresh(prefabIndex:int)`, `spawnAndRefresh(prefabIndex:int, parentName:string)`, `spawnAndRefresh(categoryName: string, prefabIndex:int)`, `spawnAndRefresh(categoryName: string, prefabIndex:int, parentName:string)`,`spawnAndRefresh(prefabIndex:int, parentName:string, copyTransform: bool)`,`spawnAndRefresh(categoryName: string, prefabIndex:int, parentName:string, copyTransform: bool)`
#### Transform
|function|description|usage|
|-----|-----|-----|
move|move to target, or an offset away, either instantly or over time|move move(speed:number) move(x:number, y:number, z:number) move(x:number, y:number, z:number, speed:number)
rotate|rotate around euler angles at speed, and optional step|rotate(xSpeed:number, ySpeed:number, zSpeed:number, step:number)
scale|scale to a size instantly or over time|scale(size:number) scale(size:number, time:number)
lookDir|look in a direction towards target|lookDir(speed:number)
lookAt|look exactly towards target|lookAt(speed:number)
lookFwd|look in the direction of movement|lookFwd(speed:number)
moveFwd|move direction object is looking|moveFwd(speed:number)
follow|move towards target position smoothly|follow(speed:number)
orbit|orbit around target|orbit(radius:number,speed:number)
bob|bob up and down and all around smoothly|bob bob(amount:number) bob(amount:number, speed:number) bob(x:number, y:number, z:number, speed:number, offset:number)
placeOnFloor|instantly places object on ground below|placeOnFloor, placeOnFloor(maxDistance:number, offset:number)
stayGrounded|keeps object on ground, useful for shadows|stayGrounded
fakeFly|make an object fall with fake gravity, very useful!!|fakeFly fakeFly(x:number, y:number, z:number,g ravity:number)
hop|make object hop up and down|hop
avoid|make object move to avoid target|avoid(speed:number)
moveLocal|like move but in the occassional world space/local space edge case issue
hopTurn|like `lookAt` or`lookDir` but hops & turns in an animated way|hopTurn(minAngle:number,time:number)
lookAway
lookDirOnce
lookAtOnce
hopTurn
copyTransform
#### Create Target Anchors
|function|description|usage|
|-----|-----|-----|
cameraAnchor|creates an anchor along the forward axis of the camera and sets as target|cameraAnchor cameraAnchor(distance:number) cameraAnchor(distance:number, upOffset:number, rightOffset:number)
fwdAnchor|creates an anchor in front of the camera a distance and sets as target|fwdAnchor fwdAnchor(distance:number) fwdAnchor(distance:number, upOffset:number, rightOffset:number)
#### Object
|function|description|usage|
|-----|-----|-----|
enable|enable the object|enable
disable|disable the object|disable
parent|parent to object, default target|parent parent(parentNameOrId:string, recenter:bool)
unparent|unparent from parent|unparent
refreshHierarchy|if parenting event occurred, child statemachines may have not been gathered in time missing state change events so call this to gather them|refreshHierarchy
destroy|destroy object|destroy
scaleAndDestroy|scale object down to zero and destroy|scaleAndDestroy(time:number)
#### Rendering
|function|description|usage|
|-----|-----|-----|
flash|cool flash effect on object, great for hit effects|flash flash(brightness:number, time:number) flash(brightness:number, time:number, r:number, g:number, b:number)
color|change object's color instantly or over time|color(r:number, g:number, b:number, a:number, time:number)
texture (uses TextureManager)|set or swap the texture of the object by texture asset name|texture(name:string)
alpha| change object's opacity instantly or over time|alpha(value:number, time:number)
material (uses MaterialManager)|change object's material|material(name:string)
texProp|change scriptable material property|texProp(propName:string, value:any)
hide|hide renderer of object|hide
unhide|unhide renderer of object|unhide
#### Animation
|function|description|usage|
|-----|-----|-----|
anim|play an AnimationMixer animation by name|anim(animName:string, loop:number/bool, speed:number)
blendshape|play a blendshape animation by name|blendshape(animName:string, time:number, endValue:number, startValue:number, loop:bool)
ease|set ease mode `easeLinear, easeOut, easeIn, easeInOut, easeInExpo, easeOutExpo, easeInOutExpo, easeInQuint, easeOutQuint, easeInOutQuint, easeInCubic, easeOutCubic, easeInOutCubic, easeOutBack, easeInBack, easeInOutBack, easeInQuart, easeOutQuart, easeInOutQuart, easeInSine, easeOutSine, easeInOutSine, easeInElastic, easeOutElastic, easeInOutElastic, easeInBounce, easeOutBounce, easeInOutBounce`|ease(name:string) call before a function you want to lerp (ex: `move`)
wobble|fun wobbly rotation animation|wobble
bouncy|fun bouncy scaling animation|bouncy
pulse|fun pulsing scaling animation|pulse
blendMode
blend
resumeAnim
pauseAnim
deepWobble
hitStop
mesh
hitWobble
wind
animTex
stopEffect
#### Audio
|function|description|usage|
|-----|-----|-----|
sfx (from AudioManager)|play a sfx by asset name|sfx(audioName:string, loop:number/bool, parent:bool, indestructible:bool)
#### Broadcast Messages
|function|description|usage|
|-----|-----|-----|
broadcast|broadcast a message|broadcast(trigger:string)
listen|listen for a message|listen(trigger:string)
#### Physics
|function|description|usage|
|-----|-----|-----|
physics|enables physics with default box collider|physics(colliderType:string, size:number)
hitter|makes physics objects activate state on another physics objects, useful for hits. By default calls `hit:` on other and `hitted:` on self|hitter(layerFilter:number OR nameFilter:string, otherState:string, selfState:string)
collide|detect a collision on this object and change state|collide(layerFilter:number OR nameFilter:string, callback:string)
uncollide|detect when a collision exits on this object and change state|uncollide(layerFilter:number OR nameFilter:string, callback:string)
dynamic|ensure affects of rigidbody and gravity|dynamic (call before `physics` for best results)
undynamic|remove affects of rigidbody collisions and gravity|undynamic (call before `physics` for best results)
force|apply force|force force(up:number) force(x:number, y:number, z:number) force(x:number, y:number, z:number, axis:string) force(x:number, y:number, z:number, randomVector:bool)
tangible|make tangible|tangible (call before `physics` for best results)
intangible|make intangible|intangible (call before `physics` for best results)
debug|show physics colliders|debug
#### Variables
|function|description|usage|
|-----|-----|-----|
setVar|create or overwrite a variable within object|setVar(key:string,value:any)
getVar|get the value of a variable with backup value option. use `alwaysRefresh` when retrieving the newest value from persistent storage|getVar(key:string,defaultValue:any,alwaysRefresh:bool)
updateVar|update an existing variable with an evaluatable operation like `variableName+=1`|updateVar(evaluatable:string)
storeVar|store variable in `global.persistentStorageSystem.store`|storeVar(key:string)
passVars|call before `spawn` `spawnItem` `spawnCharacter` spawnEntity` to pass variable values of object to spawned object|passVars(var1:string, var2:string, var3:string, ...)
r|generate a random number|r(max:number) r(min:number, max:number)
passStates|call before `spawn` `spawnItem` `spawnCharacter` `spawnEntity` `plane` to pass states to spawned object|passStates(state1:string, state2:string, state3:string, ...)
#### Logic
|function|description|usage|
|-----|-----|-----|
is|equivalent of if/then statement as an evaluatable string like `variableName>=1`|is(evaluatable:string)
or|equivalent of else statement, used after is|or, ...
valueInParent|use to get a variable value in the closest parent with the variable present|valueInParent(key:string)
#### Text
|function|description|usage|
|-----|-----|-----|
text|set the value of a TextComponent, usable with dialog|text
keyboard|turn TextComponent into input field with automated keyboard interface|keyboard
#### Interaction
|function|description|usage|
|-----|-----|-----|
tap|blocks until user taps anywhere on screen. Also works with Spectacles 2021+ air tap!|tap
screenTap|blocks until user taps object|screenTap
#### Interstate
|function|description|usage|
|-----|-----|-----|
doUpAndDeep|finds the top most parent with state and triggers that state on itself and every child|doUpAndDeep(stateName:string)
doDeep|siwtches state on all children but itself|doDeep(stateName:string)
#### Debug
|function|description|usage|
|-----|-----|-----|
simulator|blocks if not editor simulator|simulator,...
#### GG SDK Functions
|function|description|usage|
|-----|-----|-----|
|spawnItem|spawn a GG item by its ID|spawnItem(itemId:string)
|spawnCharacter|spawn a GG character by its ID|spawnCharacter(characterId:string)
|spawnEntity|spawn a GG entity by its ID|spawnEntity(entityId:string)
|remoteTex|retrieves and applies a remote texture|remoteTex(textureName:string)
|remoteSfx|retrieves and plays a remote sfx|remoteTex(audioName:string, loop:bool, parent:bool, indestructible:bool)
transfer|transfer item to inventory|transfer(itemId:string, amount:number)
burn|remove item from inventory|burn(itemId:string, amount:number)
collect|add item to inventory and destroy scene object|collect
|setPlayerVar|set variable on player|setPlayerVar(key:string, value:any)
|updatePlayerVar|update an existing player variable with an evaluatable operation like `variableName+=1`|updatePlayerVar(evaluatable:string)
|getPlayerVar|blocks, gets value from player, stores on object to be retrieved with `getVar` or `$VARIABLE`|getPlayerVar(key:string)
|setServerVar|set variable server-wide|setPlayerVar(key:string, value:any)
|updateServerVar|update an existing variable with an evaluatable operation like `variableName+=1`|updateServerVar(evaluatable:string)
|getServerVar|blocks, gets value from server, stores on object to be retrieved with `getVar` or `$VARIABLE`|getServerVar(key:string)
|questProgress|trigger progress on quest|questProgress(questId:string)
This documentation will be updated in due time