diff --git a/RollbackStateMachines/RollbackStateMachines.resource_order b/RollbackStateMachines/RollbackStateMachines.resource_order index 0eb3a75..0bd41a3 100644 --- a/RollbackStateMachines/RollbackStateMachines.resource_order +++ b/RollbackStateMachines/RollbackStateMachines.resource_order @@ -1,10 +1,10 @@ { "FolderOrderSettings":[ + {"name":"Example","order":5,"path":"folders/Example.yy",}, {"name":"RollbackStateMachines","order":1,"path":"folders/KazanGames/RollbackStateMachines.yy",}, {"name":"Core","order":2,"path":"folders/KazanGames/RollbackStateMachines/Core.yy",}, - {"name":"Objects","order":3,"path":"folders/Objects.yy",}, - {"name":"Rooms","order":4,"path":"folders/Rooms.yy",}, - {"name":"Sprites","order":1,"path":"folders/Sprites.yy",}, + {"name":"Objects","order":1,"path":"folders/Example/Objects.yy",}, + {"name":"Rooms","order":2,"path":"folders/Example/Rooms.yy",}, ], "ResourceOrderSettings":[ {"name":"objPlayer","order":2,"path":"objects/objPlayer/objPlayer.yy",}, diff --git a/RollbackStateMachines/RollbackStateMachines.yyp b/RollbackStateMachines/RollbackStateMachines.yyp index 02ba924..0fd3921 100644 --- a/RollbackStateMachines/RollbackStateMachines.yyp +++ b/RollbackStateMachines/RollbackStateMachines.yyp @@ -10,12 +10,13 @@ }, "defaultScriptType":1, "Folders":[ + {"$GMFolder":"","%Name":"Example","folderPath":"folders/Example.yy","name":"Example","resourceType":"GMFolder","resourceVersion":"2.0",}, {"$GMFolder":"","%Name":"KazanGames","folderPath":"folders/KazanGames.yy","name":"KazanGames","resourceType":"GMFolder","resourceVersion":"2.0",}, {"$GMFolder":"","%Name":"RollbackStateMachines","folderPath":"folders/KazanGames/RollbackStateMachines.yy","name":"RollbackStateMachines","resourceType":"GMFolder","resourceVersion":"2.0",}, {"$GMFolder":"","%Name":"Core","folderPath":"folders/KazanGames/RollbackStateMachines/Core.yy","name":"Core","resourceType":"GMFolder","resourceVersion":"2.0",}, - {"$GMFolder":"","%Name":"Objects","folderPath":"folders/Objects.yy","name":"Objects","resourceType":"GMFolder","resourceVersion":"2.0",}, - {"$GMFolder":"","%Name":"Rooms","folderPath":"folders/Rooms.yy","name":"Rooms","resourceType":"GMFolder","resourceVersion":"2.0",}, - {"$GMFolder":"","%Name":"Sprites","folderPath":"folders/Sprites.yy","name":"Sprites","resourceType":"GMFolder","resourceVersion":"2.0",}, + {"$GMFolder":"","%Name":"Objects","folderPath":"folders/Example/Objects.yy","name":"Objects","resourceType":"GMFolder","resourceVersion":"2.0",}, + {"$GMFolder":"","%Name":"Rooms","folderPath":"folders/Example/Rooms.yy","name":"Rooms","resourceType":"GMFolder","resourceVersion":"2.0",}, + {"$GMFolder":"","%Name":"Sprites","folderPath":"folders/Example/Sprites.yy","name":"Sprites","resourceType":"GMFolder","resourceVersion":"2.0",}, ], "IncludedFiles":[], "isEcma":false, diff --git a/RollbackStateMachines/objects/objController/objController.yy b/RollbackStateMachines/objects/objController/objController.yy index 47a0556..b910ebc 100644 --- a/RollbackStateMachines/objects/objController/objController.yy +++ b/RollbackStateMachines/objects/objController/objController.yy @@ -9,7 +9,7 @@ "overriddenProperties":[], "parent":{ "name":"Objects", - "path":"folders/Objects.yy", + "path":"folders/Example/Objects.yy", }, "parentObjectId":null, "persistent":false, diff --git a/RollbackStateMachines/objects/objPlayer/Create_0.gml b/RollbackStateMachines/objects/objPlayer/Create_0.gml index 475d794..af18b13 100644 --- a/RollbackStateMachines/objects/objPlayer/Create_0.gml +++ b/RollbackStateMachines/objects/objPlayer/Create_0.gml @@ -22,7 +22,7 @@ dashAngle = 0; enableMovement = true; -// =============================== +// =============================================== // Create State Machine fsmChar = new StateMachine("Idle"); characterState = 0; // trackable state variable (for rollback netcode sync) @@ -82,12 +82,16 @@ fsmChar.AddFreeState(function() { // Idle fsmChar.AddState("Idle", { onStep : function() { - + //show_debug_message("calling Idle onStep"); }, onEnter : function() { sprite_index = sprPlayerIdle; image_speed = 0.25; + }, + onExit : function() { + + } }); // Walk @@ -137,6 +141,7 @@ fsmChar.AddState("Dash", { } }); + // Create Transitions fsmChar.AddTransition("Idle", "Walk", function() { return (inputH != 0); @@ -161,3 +166,29 @@ fsmChar.AddTransition("Dash", "Idle", function() { }); +// -------------------------- +// add trigger states [optional / test] +/*fsmChar.AddState("IdleTrigger1", { + onStep : function() { + //show_debug_message("calling IdleTrigger1 onStep"); + }, + onEnter : function() { + show_debug_message("Idle Tg Enter"); + }, + onExit : function() { + show_debug_message("Idle Tg Exit"); + }, +}); +fsmChar.AddState("IdleTrigger2", { + onEnter : function() { + show_debug_message("Idle Tg2 Enter"); + }, +}); + +fsmChar.AddTrigger("Idle", "IdleTrigger1"); +//fsmChar.AddTrigger("Idle", "IdleTrigger2"); + +//fsmChar.OverrideState("IdleTrigger1", "Walk"); +//fsmChar.OverrideStateFunction("Walk", fsmChar.GetState("IdleTrigger1").onStep); +*/ + diff --git a/RollbackStateMachines/objects/objPlayer/Step_0.gml b/RollbackStateMachines/objects/objPlayer/Step_0.gml index 08b4b10..0a56fac 100644 --- a/RollbackStateMachines/objects/objPlayer/Step_0.gml +++ b/RollbackStateMachines/objects/objPlayer/Step_0.gml @@ -1,28 +1,25 @@ +// run state machine behaviour fsmChar.Update(id, "characterState"); +// =================================== - -// ---------------------------- /* if keyboard_check_pressed(ord("N")) { characterState = 0; } - if keyboard_check_pressed(ord("M")) { characterState = 1; } - if keyboard_check_pressed(ord("J")) { - fsmChar.SetState(0); + fsmChar.SetState(0); // does not work here if you are using transitions!!! } - if keyboard_check_pressed(ord("K")) { fsmChar.SetState(1); } - */ -if keyboard_check_pressed(ord("K")) { + +if keyboard_check_pressed(ord("P")) { fsmChar.SetStateFromHistory(1); } diff --git a/RollbackStateMachines/objects/objPlayer/objPlayer.yy b/RollbackStateMachines/objects/objPlayer/objPlayer.yy index 09c7d07..a817469 100644 --- a/RollbackStateMachines/objects/objPlayer/objPlayer.yy +++ b/RollbackStateMachines/objects/objPlayer/objPlayer.yy @@ -12,7 +12,7 @@ "overriddenProperties":[], "parent":{ "name":"Objects", - "path":"folders/Objects.yy", + "path":"folders/Example/Objects.yy", }, "parentObjectId":null, "persistent":false, diff --git a/RollbackStateMachines/objects/objSolid/objSolid.yy b/RollbackStateMachines/objects/objSolid/objSolid.yy index f10cb62..ba6e517 100644 --- a/RollbackStateMachines/objects/objSolid/objSolid.yy +++ b/RollbackStateMachines/objects/objSolid/objSolid.yy @@ -7,7 +7,7 @@ "overriddenProperties":[], "parent":{ "name":"Objects", - "path":"folders/Objects.yy", + "path":"folders/Example/Objects.yy", }, "parentObjectId":null, "persistent":false, diff --git a/RollbackStateMachines/rooms/rmGame/rmGame.yy b/RollbackStateMachines/rooms/rmGame/rmGame.yy index fdd6d4b..efff11e 100644 --- a/RollbackStateMachines/rooms/rmGame/rmGame.yy +++ b/RollbackStateMachines/rooms/rmGame/rmGame.yy @@ -30,7 +30,7 @@ "name":"rmGame", "parent":{ "name":"Rooms", - "path":"folders/Rooms.yy", + "path":"folders/Example/Rooms.yy", }, "parentRoom":null, "physicsSettings":{ diff --git a/RollbackStateMachines/scripts/__fsm_StateMachine/__fsm_StateMachine.gml b/RollbackStateMachines/scripts/__fsm_StateMachine/__fsm_StateMachine.gml index 2d45783..0579e73 100644 --- a/RollbackStateMachines/scripts/__fsm_StateMachine/__fsm_StateMachine.gml +++ b/RollbackStateMachines/scripts/__fsm_StateMachine/__fsm_StateMachine.gml @@ -10,14 +10,15 @@ /// @method StateMachine(initialState) /// @param {String,Real} initialState The initial state index. function StateMachine(_initialState=undefined, _executeEnter=true) constructor { - freeState = undefined; - states = []; + freeStates = []; // array of functions + freeStatesSize = 0; + states = []; // array of structs state = undefined; statesIds = {}; // "idle" : 0 | "walk" : 1 (used to finding state index by name) statesCurrentId = 0; - transitions = {}; // "idle" : {destinations: {"name" : function}} previousState = state; __stateTracked = undefined; + transitions = {}; // "idle" : {destinations: {"name" : function}} initialState = _initialState; initialStateExecuteEnter = _executeEnter; instanceId = other.id; @@ -33,6 +34,59 @@ function StateMachine(_initialState=undefined, _executeEnter=true) constructor { array_delete(history, 0, 1); } } + + /// @ignore + static __callEnter = function() { + gml_pragma("forceinline"); + var _st = states[state]; + if (_st.onEnter != undefined) { + method(instanceId, _st.onEnter)(); // (runs on the INSTANCE context) + } + } + + /// @ignore + static __callStep = function() { + gml_pragma("forceinline"); + var _st = states[state]; + if (_st.onStep != undefined) { + method(instanceId, _st.onStep)(); // (runs on the INSTANCE context) + } + } + + /// @ignore + static __callExit = function() { + gml_pragma("forceinline"); + var _st = states[state]; + if (_st.onExit != undefined) { + method(instanceId, _st.onExit)(); // (runs on the INSTANCE context) + } + } + + /// @ignore + static __callTriggers = function(_type) { + var _triggers = states[state].triggers; // array of state (ids) + if (_triggers != undefined) { + var _funcName; + if (_type == 0) { + _funcName = "onStep"; + } else + if (_type == 1) { + _funcName = "onEnter"; + } else + if (_type == 2) { + _funcName = "onExit"; + } + var i = 0, isize = array_length(_triggers), _triggerState = undefined, _triggerFunc = undefined; + repeat(isize) { + _triggerState = states[_triggers[i]]; + _triggerFunc = _triggerState[$ _funcName]; + if (_triggerFunc != undefined) { + method(instanceId, _triggerFunc)(); + } + ++i; + } + } + } #endregion #region Public Methods @@ -46,15 +100,19 @@ function StateMachine(_initialState=undefined, _executeEnter=true) constructor { states[statesCurrentId][$ "onEnter"] ??= undefined; states[statesCurrentId][$ "onStep"] ??= undefined; states[statesCurrentId][$ "onExit"] ??= undefined; + states[statesCurrentId][$ "triggers"] = undefined; statesIds[$ _name] = statesCurrentId; statesCurrentId += 1; + return self; } /// @desc Add a free state (maximum one). This state runs every frame. /// @method AddFreeState(stateFunction) /// @param {Function} stateFunction The function to run. static AddFreeState = function(_stateFunction) { - freeState = _stateFunction; + freeStates[freeStatesSize] = _stateFunction; + freeStatesSize += 1; + return self; } /// @desc Adds a transition to the State Machine. While it is in the state of the first parameter, it executes the condition function, if it returns true, it goes to the destination state. @@ -69,12 +127,7 @@ function StateMachine(_initialState=undefined, _executeEnter=true) constructor { }; } transitions[$ _from].destinations[$ _destination] = _condition; - } - - /// @desc Verify if State Machine has any states. - /// @method HasStates() - static HasStates = function() { - return (array_length(states) > 0); + return self; } /// @desc This function defines which state to run. @@ -94,20 +147,16 @@ function StateMachine(_initialState=undefined, _executeEnter=true) constructor { // set "previous state" to current state previousState = state; - // get current state struct and run their "onExit" function - var _curState = states[state]; - if (_curState.onExit != undefined) { - method(instanceId, _curState.onExit)(); // (runs on the INSTANCE context) - } + // run "exit" function before going to the new state + __callExit(); + __callTriggers(2); // change current state to new state state = _state; // get new state struct and run "enter" function from new state - var _newState = states[_state]; - if (_newState.onEnter != undefined) { - method(instanceId, _newState.onEnter)(); // (runs on the INSTANCE context) - } + __callEnter(); + __callTriggers(1); // save state on historic (if enabled) if (historyEnable) { @@ -115,35 +164,6 @@ function StateMachine(_initialState=undefined, _executeEnter=true) constructor { } } - /// @desc Enable or disable states history. - /// @method HistorySetEnable(enable) - /// @param {Bool} enable Enable or disable. - static HistorySetEnable = function(_enable) { - historyEnable = _enable; - } - - /// @desc Clear states history. - /// @method HistoryClear() - static HistoryClear = function() { - array_resize(history, 0); - } - - /// @desc Enable or disable states history. - /// @method HistorySetMaxSize(size) - /// @param {Real} size Maximum size. - static HistorySetMaxSize = function(_size) { - historyMaxSize = _size; - } - - /// @desc This function changes the current state to a previous state (from history). - /// @method SetStateFromHistory() - /// @param {Real} position The historical position with previous states. - /// @returns {undefined} - static SetStateFromHistory = function(_position) { - var _end = array_length(states)-1; - SetState(clamp(_end-_position, 0, _end)); - } - /// @desc This function performs the "Free State" in addition to the current state; /// @method Update(instanceId, trackStateVariable) /// @param {Id.Instance} instanceId @@ -164,7 +184,10 @@ function StateMachine(_initialState=undefined, _executeEnter=true) constructor { previousState = state; if (initialStateExecuteEnter) { var _newState = states[state]; - if (_newState.onEnter != undefined) method(instanceId, _newState.onEnter)(); + if (_newState.onEnter != undefined) { + method(instanceId, _newState.onEnter)(); + } + __callTriggers(1); } if (historyEnable) { __historyAdd(); @@ -175,19 +198,24 @@ function StateMachine(_initialState=undefined, _executeEnter=true) constructor { initialState = undefined; } - // run free state function - if (freeState != undefined) freeState(); + // run free state functions + var fi = 0; + repeat(freeStatesSize) method(instanceId, freeStates[fi++])(); // only execute if there are states if (state == undefined) exit; + var _currentState = states[state]; // struct - // run current state function (on the INSTANCE context) - if (states[state].onStep != undefined) { - method(instanceId, states[state].onStep)(); + // call state's onStep function + if (_currentState.onStep != undefined) { + method(instanceId, _currentState.onStep)(); } + // execute triggers onStep() + __callTriggers(0); + // run transitions destinations conditions - var _tr = transitions[$ states[state].name]; + var _tr = transitions[$ _currentState.name]; if (_tr != undefined) { var _destinations = _tr.destinations; // struct var _names = variable_struct_get_names(_destinations); @@ -216,6 +244,114 @@ function StateMachine(_initialState=undefined, _executeEnter=true) constructor { } } + /// @desc Verify if State Machine has any states. + /// @method HasStates() + static HasStates = function() { + return (array_length(states) > 0); + } + + /// @desc Enable or disable states history. + /// @method HistorySetEnable(enable) + /// @param {Bool} enable Enable or disable. + static HistorySetEnable = function(_enable) { + historyEnable = _enable; + return self; + } + + /// @desc Clear states history. + /// @method HistoryClear() + static HistoryClear = function() { + array_resize(history, 0); + return self; + } + + /// @desc Enable or disable states history. + /// @method HistorySetMaxSize(size) + /// @param {Real} size Maximum size. + static HistorySetMaxSize = function(_size) { + historyMaxSize = _size; + return self; + } + + /// @desc This function changes the current state to a previous state (from history). + /// @method SetStateFromHistory() + /// @param {Real} position The historical position with previous states. + /// @returns {undefined} + static SetStateFromHistory = function(_position) { + var _end = array_length(states)-1; + SetState(clamp(_end-_position, 0, _end)); + } + + /// @desc Add a State as a trigger for another. In other words, its functions will be executed according to the defined state. + /// @method AddTrigger(state, triggerState) + /// @param {String,Real} state The state to add the trigger to. + /// @param {String,Real} triggerState The trigger state to be added. + static AddTrigger = function(_state, _triggerState) { + // get state id from string + if (is_string(_state)) { + _state = statesIds[$ _state]; + } + if (is_string(_triggerState)) { + _triggerState = statesIds[$ _triggerState]; + } + // do not add trigger if the state is the same + if (_triggerState == _state) { + __fsm_trace($"Can't add child \"{_triggerState}\" to itself \"{_state}\"", 1); + exit; + } + + // get origin state struct + var _parentState = states[_state]; + + // create triggers array + if (_parentState[$ "triggers"] == undefined) { + _parentState[$ "triggers"] = []; + } + + // add trigger state to parent state + array_push(_parentState[$ "triggers"], _triggerState); + return self; + } + + /// @desc Replaces functions from one state to another, keeping the same state name. + /// @func OverrideState(from, to, onStep, onEnter, onExit) + /// @param {String,Real} from The state to copy from. + /// @param {String,Real} from The state to copy to. + /// @param {Bool} replaceOnStep If true, will replace the onStep function. + /// @param {Bool} replaceOnEnter If true, will replace the onEnter function. + /// @param {Bool} replaceOnExit If true, will replace the onExit function. + static OverrideState = function(_from, _to, _replaceOnStep=true, _replaceOnEnter=true, _replaceOnExit=true) { + // get state id from string + if (is_string(_from)) { + _from = statesIds[$ _from]; + } + if (is_string(_to)) { + _to = statesIds[$ _to]; + } + // replace functions + if (_replaceOnStep) states[_to][$ "onStep"] = states[_from][$ "onStep"]; + if (_replaceOnEnter) states[_to][$ "onEnter"] = states[_from][$ "onEnter"]; + if (_replaceOnExit) states[_to][$ "onExit"] = states[_from][$ "onExit"]; + return self; + } + + /// @desc Replaces specific functions from a state. + /// @func OverrideStateFunction(state, onStep, onEnter, onExit) + /// @param {String,Real} state The state to override functions. + /// @param {Function,Undefined} onStep If defined, replaces the onStep function with a new function/method. + /// @param {Function,Undefined} onEnter If defined, replaces the onEnter function with a new function/method. + /// @param {Function,Undefined} onExit If defined, replaces the onExit function with a new function/method. + static OverrideStateFunction = function(_state, _onStep=undefined, _onEnter=undefined, _onExit=undefined) { + // get state id from string + if (is_string(_state)) { + _state = statesIds[$ _state]; + } + if (_onStep != undefined) states[_state][$ "onStep"] = _onStep; + if (_onEnter != undefined) states[_state][$ "onEnter"] = _onEnter; + if (_onExit != undefined) states[_state][$ "onExit"] = _onExit; + return self; + } + /// @desc Get state struct. /// @method GetState(state) /// @param {Real} state The state index (integer). diff --git a/RollbackStateMachines/sprites/sprMaskPlayer/sprMaskPlayer.yy b/RollbackStateMachines/sprites/sprMaskPlayer/sprMaskPlayer.yy index b0dd490..b07e674 100644 --- a/RollbackStateMachines/sprites/sprMaskPlayer/sprMaskPlayer.yy +++ b/RollbackStateMachines/sprites/sprMaskPlayer/sprMaskPlayer.yy @@ -26,7 +26,7 @@ "origin":7, "parent":{ "name":"Sprites", - "path":"folders/Sprites.yy", + "path":"folders/Example/Sprites.yy", }, "preMultiplyAlpha":false, "resourceType":"GMSprite", diff --git a/RollbackStateMachines/sprites/sprMaskSolid/sprMaskSolid.yy b/RollbackStateMachines/sprites/sprMaskSolid/sprMaskSolid.yy index bd141ce..bb74403 100644 --- a/RollbackStateMachines/sprites/sprMaskSolid/sprMaskSolid.yy +++ b/RollbackStateMachines/sprites/sprMaskSolid/sprMaskSolid.yy @@ -26,7 +26,7 @@ "origin":0, "parent":{ "name":"Sprites", - "path":"folders/Sprites.yy", + "path":"folders/Example/Sprites.yy", }, "preMultiplyAlpha":false, "resourceType":"GMSprite", diff --git a/RollbackStateMachines/sprites/sprPlayerIdle/sprPlayerIdle.yy b/RollbackStateMachines/sprites/sprPlayerIdle/sprPlayerIdle.yy index 99be912..83c92d8 100644 --- a/RollbackStateMachines/sprites/sprPlayerIdle/sprPlayerIdle.yy +++ b/RollbackStateMachines/sprites/sprPlayerIdle/sprPlayerIdle.yy @@ -30,7 +30,7 @@ "origin":7, "parent":{ "name":"Sprites", - "path":"folders/Sprites.yy", + "path":"folders/Example/Sprites.yy", }, "preMultiplyAlpha":false, "resourceType":"GMSprite", diff --git a/RollbackStateMachines/sprites/sprPlayerJump/sprPlayerJump.yy b/RollbackStateMachines/sprites/sprPlayerJump/sprPlayerJump.yy index 0166239..1720436 100644 --- a/RollbackStateMachines/sprites/sprPlayerJump/sprPlayerJump.yy +++ b/RollbackStateMachines/sprites/sprPlayerJump/sprPlayerJump.yy @@ -27,7 +27,7 @@ "origin":7, "parent":{ "name":"Sprites", - "path":"folders/Sprites.yy", + "path":"folders/Example/Sprites.yy", }, "preMultiplyAlpha":false, "resourceType":"GMSprite", diff --git a/RollbackStateMachines/sprites/sprPlayerWalk/sprPlayerWalk.yy b/RollbackStateMachines/sprites/sprPlayerWalk/sprPlayerWalk.yy index 724fe50..25896d5 100644 --- a/RollbackStateMachines/sprites/sprPlayerWalk/sprPlayerWalk.yy +++ b/RollbackStateMachines/sprites/sprPlayerWalk/sprPlayerWalk.yy @@ -31,7 +31,7 @@ "origin":7, "parent":{ "name":"Sprites", - "path":"folders/Sprites.yy", + "path":"folders/Example/Sprites.yy", }, "preMultiplyAlpha":false, "resourceType":"GMSprite",