Game state

A friend asked me to give an example of a game state model, I was referring to when I tried to explain him how I develop simple games and touch screen applications. Here I’ll be trying to explain this, by extracting some example code from The secrets of three paintings app.

Be it ActionScript or JavaScript, whenever I’m writing relatively simple games or touch screen applications, I always use these 3:

  1. A game state model
  2. Game state events (signals)
  3. A view class for each element on screen

Game state model

Game state is a basically a single class that implements all game logic, which in many cases represent the flow. For example:

    // excerpt from State class
    function State() {
        this.painting = 0; // selected painting
        this.info = false; // true if inside a paintings info
        this.backEnabled = false; // true if back button is enabled
        this.buttonsEnabled = false; // true if rotate, zoom and info buttons are enabled
        this.signals = new StateSignals(); // game state signals
    }
    State.prototype = {
        // I write a setter method for everything that can change in the app
        setPainting: function(value) {
            // this is what I refer to as game logics:
            if (value != this.painting && // a painting can be selected only if it's not already selected
                 (value === 0 || // and either we are exiting a painting
                 this.painting === 0)) { // or no other painting is selected
                var prevValue = this.painting;
                this.painting = value;
                this.signals.changePainting.dispatch(prevValue); // notify all subscribed view elements
            }
        },
        setZoom: function(value) {
            if (this.zoom != value) {
                this.zoom = value;
                if (value) { // if inside zoom, disable rotate, zoom and info buttons
                    this.setButtonsEnabled(false);
                }
                this.signals.changeZoom.dispatch(this.zoom); // notify of the change all subscribed elements
            }
        },
        setInfo: function(value) {
            if (this.info != value) {
                this.info = value;
                if (value) {
                    this.setButtonsEnabled(false);
                }
                this.signals.changeInfo.dispatch(this.info);
            }
        },
        setButtonsEnabled: function(value) {
            if (this.buttonsEnabled != value) {
                this.buttonsEnabled = value;
                this.signals.changeButtonsEnabled.dispatch(this.buttonsEnabled);
            }
        },
        // it's easy to implement back button logic, all state is in one place
        goBack: function() {
            if (this.painting) { // if inside a painting
                if (this.zoom) { // if in zoom
                    this.setZoom(false); // exit zoom
                }
                else if (this.info) { // if in info
                    this.setInfo(false); // exit info
                }
                else { // else exit current painting
                    this.setPainting(0); // exit painting       
                }
            }
        }
    };

 

Game State Events (signals)

Game state events or signals, because I prefer using Robert Penner’s AS3 Signals or it’s javascript port for this. These are what connect the Game State to the view elements. As you saw before in the Game State source code, there is a signal dispatch for every change. I group all the signals together in a StateSignals class:

    // excerpt from StateSignals class
    function StateSignals() {
        this.changePainting = new signals.Signal();
        this.changeZoom = new signals.Signal();
        this.changeInfo = new signals.Signal();
        this.changeButtonsEnabled = new signals.Signal();
    }

 

View classes

I always try to keep view classes atomic, so that they only care about themselves, and do not access child or parent elements. If a child element needs to implement any view logic or transitions, I create a separate class for it.

    // excerpt from BackButton class
    function BackButton() {
       state.signals.changeBackEnabled.add(this._changeBackEnabled, this);
        this.on("click", this._click, this);
    }
    BackButton.prototype = {
        _changeBackEnabled: function(value) {
            this._enableOrDisable(value);
        },
        _enableOrDisable: function(value) {
            this.mouseEnabled = value;
            if (state.painting) {
                TweenLite.to(this, 0.5, {alpha: value ? 1 : 0});
            }
        }
        _click: function() {
             state.goBack();
        }
    };
    // excerpt from Painting class
    function Painting() {
        this.on("click", this._click, this);
        state.signals.changePainting.add(this._changePainting, this);
        state.signals.changeInfo.add(this._changeInfo, this);
    }
    Painting.prototype = {
        _click: function() {
           state.setPainting(this.painting);
        },
        _changePainting: function(prevValue) {
            if (state.painting === this.painting || prevValue === this.painting) {
                this._removeClickEvent(); // disable click event while inside
                this.parent.setChildIndex(this, this.parent.numChildren - 1); // bring to front
                var self = this,
                    onComplete = function() {
                        if (!state.painting) {
                            self._addClickEvent(); // add click event when outside
                        }
                    };
                if (state.painting) {
                    // transition into painting
                    TweenLite.to(this, 1, {
                        easel: {frame: this.timeline.resolve("in")},
                        ease:Linear.easeNone,
                        onComplete: onComplete
                    });
                }
                else {
                    // transition out of painting
                    TweenLite.to(this, 1, {
                        easel:{frame: this.timeline.resolve("out")},
                        ease:Linear.easeNone,
                        onComplete: onComplete
                    });
                }
            }
        },
        _removeClickEvent: function() {
            while (this._listeners.click) { // remove click event listener
                this.off("click", this._listeners.click[0]);
            }
        },
        _changeInfo: function(value) {
            if (this.painting === state.painting) {
                if (value) {
                    // transition into info
                    TweenLite.to(this, 0.75, {
                        easel: {frame: this.timeline.resolve("info")},
                        ease:Linear.easeNone
                    });
                }
                else {
                    // transition out of info
                    TweenLite.to(this, 0.75, {
                        easel:{frame: this.timeline.resolve("in")},
                        ease:Linear.easeNone
                    });
                }
            }
        }
    };

 

Summary

I hope this gives a rough idea of how the game state and with that – game logic can be kept outside of any view code.