State Machines
A wrote (a couple variations of) a simple state machine manager. The code is on github under an MIT license. There are a couple demos down at the bottom of the page.
- state-machines-du (107 Tokens): each state has its own draw and
update methods - state-machines-st (111 Tokens): each state has setup and teardown
methods which are run only when the state is entered and exited
respectively
Usage
Creating a state machine
To create a new state machine use the 'new' method:
sm = state_machine:new() |
Adding states
After creating the machine itself you need to add states. A state
consists of four things:
- A unique name or identifier: Most likely a string but can be
anything as long as it's unique. - A transition function: This function is called once per cycle
and should return the identifier of the state the machine
should switch to. - An update (or setup) function: The update function is called
once per cycle and should update variables, etc. associated
with the state. For state-machine-st.lua, this is instead a
setup function which is only run once each time the machine
enters this state. - A draw (or teardown) function: The draw function is called
once per cycle and should draw everything relevant to the
state. For state-machine-st.lua, this is instead a teardown
function which is only run once each time the machine exits
this state.
Add a state to the machine using the 'add_state' method:
sm:add_state( -- identifier 'a state', -- transition function function() if btnp(5) then return 'some other state' else return 'a state' end end, -- update function function() if timer then timer += 1 else timer = 0 end end, -- draw function function() print(timer) end ) |
Using the state machine
-
state-machine-du.lua
Once you've created a state machine and added some states using it
is simple: Set the initial state then call the update and draw
methods.
function _init() sm:set_state('a state') end function _update() sm:update() end function _draw() cls() sm:draw() end |
The update method calls the current state's transition function
and changes the current state if necessary and then calls the
current state's update function. The draw method calls the current
state's draw function.
-
state-machine-st.lua
The setup/teardown version is basically the same except there is
no draw method and the update method does a bit more work, so all
you need is this:
function _init() sm:set_state('state 1') end function _update() sm:update() -- whatever other update stuff you need to do. end function _draw() cls() -- whatever draw stuff you need to do. end |
The update method in this version also calls the transition
function. If a state change is necessary, then the current state's
teardown function is called, then the current state is changed,
and finally the new state's setup function is called.
Demos
basic-sm
A very basic state machine with two states. Press X/V to switch from state 1 to state 2, Z/C to switch from state 2 to state 1.
platform-tut
A simple platformer demo with 5 states: intro, tut_movement, tut_jump, play, and gameover.
I don't want to put your work down, but I must confess, I really can't see the Benefit of using a State-Machine especially on PICO-8. It looks "Over Engineered" in my Opinion.
Why not just using Global Variables like UPD and DRAW, and changes these Values on-the-fly? Like:
(Of course, on other Languages and Dev-Environments without such harsh Restrictions like PICO8, or with more Devs working on the same Project, it's a whole different story...)
@Astorek86 No worries, criticism is always welcome. Nothing wrong with having an opinion and I'll freely admit that over-engineering is a problem I'm prone to!
Mostly I was just playing around, made a thing and thought I'd post it in case anyone found it useful. Totally okay if nobody does. Heck, I might not find it useful a week from now. I have a habit of writing stuff like this and then completely forgetting it exists.
I think it's mostly just preference and habit more than anything. I was originally doing essentially what you suggest but having a whole bunch of similarly named functions just kinda...makes me twitch? I mean there's nothing wrong with it objectively speaking it just doesn't make my brain happy. I got around that by stuffing all the functions into a couple tables and then just indexing into them. But at that point you're most of the way to where I am with these implementations anyway and it's just a couple methods to handle the indexing and index switching automatically.
I do like the transition function being its own thing. I feel like it makes adding/removing/modifying states or changing which states transition to which other states easier. Having state changes mucked up into the update functions isn't a big deal for simple cases like the above demos but for something more complicated, removing an entire state (for instance) may or may not be an easy refactor while making sure not to break everything else going on in update. With the separate transition function, just change that and leave update alone and you're done. Easy! But, again, you don't actually need a state machine object to do that.
[Please log in to post a comment]