Skip to main content
GameDev.net gamedev.net

PRO Tired of ads? Read GameDev.net ad-free and help keep the community independent with GameDev Pro — $3/month.

State Changes

State Changes

Greedy Goblin
Greedy Goblin
The Berg · · 5 min read
5,061 0

Games usually (if not always) require some way to manage state changes... and I'm sure most of you (if not all of you) know far more about State Machines than I do. And I'm certain that I could learn a heck of lot from reading up about the subject to build a state machine that works beautifully and makes my code look amazing etc etc.

Pfft.. never mind all that... I'm building this game 'off the cuff' as it were, making it up as I go along and following the principle of 'I build what I need when I need it and only insofar that it adequately fulfils the requirements at that time'. I don't try to plan ahead (not in any granular sense anyway), I'm not building a reusable one-size-fits-all game engine, I'm not trying to make the code beautfiul, or win any awards or even make any money from the darn thing. It just needs to perform well enough for what I want it to do.

So my immediate requirement is that I have a way to manage the player switching from walking to running to whatever. If I can use it elsewhere for other things then great... and I'll be honest, I do like reusable code so I tend to naturally sway toward that. What I'm trying to avoid is getting myself stuck in a rut, spending weeks/months deliberating over the smallest details because it's got to be 'perfect' and then realising I've still got 99.5% of the game to build! Quick and dirty is OK in my world.

I often approach things from a top-down perspective. This boils down to:

'How do I want to instruct the computer to do x, y or z?'

So for this particular requirement, how do I want to instruct the game that the player can change from walking to running and running to walking, or walking/running to falling (assuming I make that a player state - which I do), but not from sleeping to running for example? Hell, I don't even know all the states that I want yet, but these are the ones I have a feel for so far:

  • Walking
  • Running
  • Skiiing
  • Driving
  • Falling
  • Drowning
  • Sleeping
  • Eating

Introducing 'When'

I thought it might be nice to be able to write something like this in my player setup:

// Configure valid player state transitions
When( this.playerState ).changes().from( PLAYER_STATES.WALKING ).to( PLAYER_STATES.RUNNING ).then( function () { } );
When( this.playerState ).changes().from( PLAYER_STATES.RUNNING ).to( PLAYER_STATES.WALKING ).then( function () { } );
When( this.playerState ).changes().from( PLAYER_STATES.WALKING ).to( PLAYER_STATES.SKIING ).then( function () { } );
When( this.playerState ).changes().from( PLAYER_STATES.SKIING ).to( PLAYER_STATES.WALKING ).then( function () { } );
When( this.playerState ).changes().from( PLAYER_STATES.WALKING, PLAYER_STATES.RUNNING, PLAYER_STATES.SKIING ).to( PLAYER_STATES.FALLING ).then( function () { } );

There's probably a library for something like this out there, but heck, where's the fun in that?!


So I create a new 'Stateful' object that represents a state (in this case the playerState) and it's allowed transitions and a 'When' function so I can write the code exactly as above:

const Stateful = function () { }
Stateful.isStateful = function ( obj ) {
    return obj.constructor && obj.constructor.name === Stateful.name;
}
Stateful.areEqual = function ( v1, v2 ) {
    return v1.equals ? v1.equals( v2 ) : v1 == v2;
}
Stateful.prototype = {
    constructor: Stateful,
    set: function ( v ) {
        let newState = typeof ( v ) === "function" ? new v() : v;

        for ( let i = 0; i < this.transitions.length; i++ ) {
            let transition = this.transitions[i];
            if ( transition && typeof ( transition.callback ) === "function" ) {
                let fromMatch = Stateful.areEqual( transition.vFrom, this );
                let toMatch = Stateful.areEqual( transition.vTo, newState );

                if ( fromMatch && toMatch ) {
                    // We can only change to the new state if a valid transition exists.
                    this.previousState = Object.assign( Object.create( {} ), this );
                    Object.assign( this, newState );

                    transition.callback( this.previousState, this );
                }
            }
        }
    },
    transitions: Object.create( Object.assign( Array.prototype, {
        from: function ( vFrom ) {
            this.vFrom = typeof ( vFrom ) === "function" ? new vFrom() : vFrom;
            return this;
        },
        to: function ( vTo ) {
            this.vTo = typeof ( vTo ) === "function" ? new vTo() : vTo;
            return this;
        },
        remove: function ( fn ) {
            this.vFrom = this.vFrom === undefined ? { equals: function () { return true; } } : this.vFrom;
            this.vTo = this.vTo === undefined ? { equals: function () { return true; } } : this.vTo;

            for ( let i = 0; i < this.length; i++ ) {
                let transition = this[i];
                let fromMatch = Stateful.areEqual( this.vFrom, transition.vFrom );
                let toMatch = Stateful.areEqual( this.vTo, transition.vTo );
                let fnMatch = fn === undefined ? true : transition.callback == fn;
                if ( fromMatch && toMatch & fnMatch ) {
                    delete this[i];
                }
            }
        }
    } ) )
}
function When( statefulObj ) {
    if ( !Stateful.isStateful( statefulObj ) ) {
        throw "Argument must be a Stateful object";
    }

    return {
        changes: function () {
            return {
                from: function ( ...vFrom ) {
                    this.vFrom = vFrom;
                    return this;
                },
                to: function ( ...vTo ) {
                    this.vTo = vTo;
                    return this;
                },
                then: function ( fn ) {
                    if ( typeof ( fn ) === "function" ) {
                        this.vFrom = this.vFrom === undefined ? [true] : this.vFrom;
                        this.vTo = this.vTo === undefined ? [true] : this.vTo;

                        for ( let i = 0; i < this.vFrom.length; i++ ) {
                            for ( let j = 0; j < this.vTo.length; j++ ) {
                                statefulObj.transitions.push( {
                                    vFrom: typeof ( this.vFrom[i] ) === "function" ? new this.vFrom[i]() : this.vFrom[i],
                                    vTo: typeof ( this.vTo[j] ) === "function" ? new this.vTo[j]() : this.vTo[j],
                                    callback: fn
                                } );
                            }
                        }
                    } else {
                        throw "Supplied argument must be a function";
                    }
                }
            };
        }
    }
}

I drop the aforementioned 'When' statements into my Player setup and remove the old 'If' statements that were previously controlling changes between walking and running and insert the new playerState.set() calls where appropriate.

e.g.

"run": ( pc, keyup ) => {
  if ( keyup ) {
    _this.player.playerState.set( PLAYER_STATES.WALKING );
  } else {
    _this.player.playerState.set( PLAYER_STATES.RUNNING );
  }
}

And it seems to work! (Yes I was actually surprised by that) ?


TheBerg-StateChanges.mp4

p.s. I've switched to using Bandicam for screen capture as it seems far superior to what I was using previously.

Recommended resources

3D Art

See full guide
3D Environment Design with Blender 5 cover
Editor pick

3D Environment Design with Blender 5

Amazon · Book

Based on Blender 5, this updated second edition simplifies the complexities by guiding you through clear, practical workflows to overcome those challenges. You’ll start by addressing common modeling and proportion mistakes, then move on to realistic texturing and unwrapping to create stunning scenes based on actual references.

GameDev.net may earn a commission if you purchase through these links. This helps fund the site at no extra cost to you.

Low Poly 3D Modeling in Blender cover
Editor pick

Low Poly 3D Modeling in Blender

Amazon · Book

Step into the world of low poly 3D art with Low Poly 3D Modeling in Blender—your entry point into Blender and mastering the fundamentals of 3D art. This beginner-friendly guide ensures that you’re fully prepared for the creative adventure that follows. Through a step-by-step learning process starting with the principles of low poly art, this book gradually immerses you in the intricacies of modeling. As you progress, you’ll gain hands-on experience creating diverse projects ranging from designing a simple 3D crate to rendering complete low poly scenes. The book covers a wide spectrum of topics as you navigate Blender's interface, mastering essential modeling tools and exploring both basic and advanced modeling techniques. Advancing to the final chapters, you’ll find ways to breathe life into your models with material creation and gain practical insights into modeling a variety of low poly objects. From end-to-end scene construction to configuring Blender for rendering high-quality images, you’ll be equipped with the foundational skills to propel your career in 3D modeling and explore the boundless creative possibilities that Blender offers. By the end of this book, you'll have a solid understanding of Blender, 3D modeling, low poly methodologies, material design, 3D rendering techniques, and the broader world of 3D art.

GameDev.net may earn a commission if you purchase through these links. This helps fund the site at no extra cost to you.

GameDev.net may earn a commission if you purchase through these links. This helps fund the site at no extra cost to you.

Discussion

Loading comments...