Log In  

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

  1. 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.

  1. 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.

Cart #basic_sm-0 | 2021-08-04 | Code ▽ | Embed ▽ | No License

platform-tut

A simple platformer demo with 5 states: intro, tut_movement, tut_jump, play, and gameover.

Cart #platform_tut_sm-0 | 2021-08-04 | Code ▽ | Embed ▽ | No License

P#95647 2021-08-04 09:55

1

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:

Cart #jemopunina-2 | 2021-08-04 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
1

(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...)

P#95650 2021-08-04 12:16 ( Edited 2021-08-04 12:59)
2

@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.

P#95665 2021-08-04 20:57

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-28 20:17:12 | 0.020s | Q:21