Phaserjs V2的state状态解析及技巧
阅读原文时间:2023年07月10日阅读:2

用phaserjs开发了好多游戏了,但是对phaser还是了解不深,只知道怎么去用,今天就特意花点时间研究下phaser的状态管理到底是怎么回事。

首先,new Phaser.Game,以下是Phaser.Game的部分源码:

Phaser.Game = function (width, height, renderer, parent, state, transparent, antialias, physicsConfig) {

/\*\*  
\* @property {number} id - Phaser Game ID  
\* @readonly  
\*/  
this.id = Phaser.GAMES.push(this) - 1;

………………………(省略的代码)
// Parse the configuration object (if any)
if (arguments.length === 1 && typeof arguments[0] === 'object')
{
this.parseConfig(arguments[0]);
}
else
{
this.config = { enableDebug: true };

    if (typeof width !== 'undefined')  
    {  
        this.\_width = width;  
    }

    if (typeof height !== 'undefined')  
    {  
        this.\_height = height;  
    }

    if (typeof renderer !== 'undefined')  
    {  
        this.renderType = renderer;  
    }

    if (typeof parent !== 'undefined')  
    {  
        this.parent = parent;  
    }

    if (typeof transparent !== 'undefined')  
    {  
        this.transparent = transparent;  
    }

    if (typeof antialias !== 'undefined')  
    {  
        this.antialias = antialias;  
    }

    this.rnd = new Phaser.RandomDataGenerator(\[(Date.now() \* Math.random()).toString()\]);

    this.state = new Phaser.StateManager(this, state);  
}

this.device.whenReady(this.boot, this);

return this;

};

先看this.device.whenReady(this.boot, this);这段代码,源码的意思是设备准备就绪或者dom文档准备就绪后就执行boot,说白了就是DOMContentLoaded这个事件或者window的load事件的回调,一切准备就绪,执行boot,boot源码如下:

boot: function () {

    if (this.isBooted)  
    {  
        return;  
    }

    this.onPause = new Phaser.Signal();  
    this.onResume = new Phaser.Signal();  
    this.onBlur = new Phaser.Signal();  
    this.onFocus = new Phaser.Signal();

    this.isBooted = true;

    PIXI.game = this;

    this.math = Phaser.Math;

    this.scale = new Phaser.ScaleManager(this, this.\_width, this.\_height);  
    this.stage = new Phaser.Stage(this);

    this.setUpRenderer();

    this.world = new Phaser.World(this);  
    this.add = new Phaser.GameObjectFactory(this);  
    this.make = new Phaser.GameObjectCreator(this);  
    this.cache = new Phaser.Cache(this);  
    this.load = new Phaser.Loader(this);  
    this.time = new Phaser.Time(this);  
    this.tweens = new Phaser.TweenManager(this);  
    this.input = new Phaser.Input(this);  
    this.sound = new Phaser.SoundManager(this);  
    this.physics = new Phaser.Physics(this, this.physicsConfig);  
    this.particles = new Phaser.Particles(this);  
    this.create = new Phaser.Create(this);  
    this.plugins = new Phaser.PluginManager(this);  
    this.net = new Phaser.Net(this);

    this.time.boot();  
    this.stage.boot();  
    this.world.boot();  
    this.scale.boot();  
    this.input.boot();  
    this.sound.boot();  
    this.state.boot();

    if (this.config\['enableDebug'\])  
    {  
        this.debug = new Phaser.Utils.Debug(this);  
        this.debug.boot();  
    }  
    else  
    {  
        this.debug = { preUpdate: function () {}, update: function () {}, reset: function () {}, isDisabled: true };  
    }

    this.showDebugHeader();

    this.isRunning = true;

    if (this.config && this.config\['forceSetTimeOut'\])  
    {  
        this.raf = new Phaser.RequestAnimationFrame(this, this.config\['forceSetTimeOut'\]);  
    }  
    else  
    {  
        this.raf = new Phaser.RequestAnimationFrame(this, false);  
    }

    this.\_kickstart = true;

    if (window\['focus'\])  
    {  
        if (!window\['PhaserGlobal'\] || (window\['PhaserGlobal'\] && !window\['PhaserGlobal'\].stopFocus))  
        {  
            window.focus();  
        }  
    }

    if (this.config\['disableStart'\])  
    {  
        return;  
    }

    if (this.cache.isReady)  
    {  
        this.raf.start();  
    }  
    else  
    {  
        this.cache.onReady.addOnce(function () {  
            this.raf.start();  
        }, this);  
    }

}

很有序的初始化一些事件回调和方法,其中world,stage等等都有boot初始化方法,整个Phaser里Phaser.Stage只在这里实例化一次,仅此一次,游戏的舞台就只有这一个stage,stage继承PIXI.DisplayObjectContainer,到这里就都清楚了。

Phaser.World也仅此一次被实例化,它继承自Phaser.Group,Phaser.Group也继承自PIXI.DisplayObjectContainer,但是实例化它的时候,默认是把它添加到Phaser.World里的,

if (parent === undefined)
{
parent = game.world;
}

所以Group都是在World里,除非显示指定stage为其parent,实际开发几乎没这个必要。

Phaser.GameObjectFactory类封装了Phaser的组件,如image,sprite等,显示组件基本都是通过game.add直接被添加到World里(bitmapData除外这里不作研究):

image: function (x, y, key, frame, group) {

    if (group === undefined) { group = this.world; }

    return group.add(new Phaser.Image(this.game, x, y, key, frame));

},

接下来看下this.state = new Phaser.StateManager(this, state);这段代码,Phaser.StateManager也是仅被实例化一次,主要用来管理游戏state的,构造函数有两个参数game和state,game就是我们的游戏对象,stage是默认的

boot: function () {

    this.game.onPause.add(this.pause, this);  
    this.game.onResume.add(this.resume, this);

    if (this.\_pendingState !== null && typeof this.\_pendingState !== 'string')  
    {  
        this.add('default', this.\_pendingState, true);  
    }

},

默认state的key为'default',通过game.state.add添加state的源码:

add: function (key, state, autoStart) {

    if (autoStart === undefined) { autoStart = false; }

    var newState;

    if (state instanceof Phaser.State)  
    {  
        newState = state;  
    }  
    else if (typeof state === 'object')  
    {  
        newState = state;  
        newState.game = this.game;  
    }  
    else if (typeof state === 'function')  
    {  
        newState = new state(this.game);  
    }

    this.states\[key\] = newState;

    if (autoStart)  
    {  
        if (this.game.isBooted)  
        {  
            this.start(key);  
        }  
        else  
        {  
            this.\_pendingState = key;  
        }  
    }

    return newState;

},

就是一个包含init,preload,create等的object或function,或者是Phaser.State类,这个类就是封装一个state包含的所有方法,所有方法没有任何实现。

game.state.start源码:

start: function (key, clearWorld, clearCache) {

    if (clearWorld === undefined) { clearWorld = true; }  
    if (clearCache === undefined) { clearCache = false; }

    if (this.checkState(key))  
    {  
        //  Place the state in the queue. It will be started the next time the game loop begins.  
        this.\_pendingState = key;  
        this.\_clearWorld = clearWorld;  
        this.\_clearCache = clearCache;

        if (arguments.length > 3)  
        {  
            this.\_args = Array.prototype.splice.call(arguments, 3);  
        }  
    }

},

就是检查下是否是一个State,没什么啊,其实真正的start是在preUpadte里,

preUpdate: function () {

    if (this.\_pendingState && this.game.isBooted)  
    {  
        var previousStateKey = this.current;

        //  Already got a state running?  
        this.clearCurrentState();

        this.setCurrentState(this.\_pendingState);

        this.onStateChange.dispatch(this.current, previousStateKey);

        if (this.current !== this.\_pendingState)  
        {  
            return;  
        }  
        else  
        {  
            this.\_pendingState = null;  
        }

        //  If StateManager.start has been called from the init of a State that ALSO has a preload, then  
        //  onPreloadCallback will be set, but must be ignored  
        if (this.onPreloadCallback)  
        {  
            this.game.load.reset(true);  
            this.onPreloadCallback.call(this.callbackContext, this.game);

            //  Is the loader empty?  
            if (this.game.load.totalQueuedFiles() === 0 && this.game.load.totalQueuedPacks() === 0)  
            {  
                this.loadComplete();  
            }  
            else  
            {  
                //  Start the loader going as we have something in the queue  
                this.game.load.start();  
            }  
        }  
        else  
        {  
            //  No init? Then there was nothing to load either  
            this.loadComplete();  
        }  
    }

},

start的时候,this._pendingState设置了当前的state,preUpdate方法先清除上一个state,再设置当前state,

setCurrentState里调用了link方法同时初始化一些事件监听,clearCurrentState方法调用了unlink方法同时移除一些事件监听,link方法如下:

link: function (key) {

    var state = this.states\[key\];

    state.game = this.game;  
    state.add = this.game.add;  
    state.make = this.game.make;  
    state.camera = this.game.camera;  
    state.cache = this.game.cache;  
    state.input = this.game.input;  
    state.load = this.game.load;  
    state.math = this.game.math;  
    state.sound = this.game.sound;  
    state.scale = this.game.scale;  
    state.state = this;  
    state.stage = this.game.stage;  
    state.time = this.game.time;  
    state.tweens = this.game.tweens;  
    state.world = this.game.world;  
    state.particles = this.game.particles;  
    state.rnd = this.game.rnd;  
    state.physics = this.game.physics;  
    state.key = key;

},

由此可见每个状态都有game所拥有的几乎所有属性,所以每个state都相当于一个单独的game,包括init,preload,create等等。

setCurrentState:

setCurrentState: function (key) {

    var state = this.states\[key\];

    this.callbackContext = state;

    this.link(key);

    //  Used when the state is set as being the current active state  
    this.onInitCallback = state\['init'\] || this.dummy;

    this.onPreloadCallback = state\['preload'\] || null;  
    this.onLoadRenderCallback = state\['loadRender'\] || null;  
    this.onLoadUpdateCallback = state\['loadUpdate'\] || null;  
    this.onCreateCallback = state\['create'\] || null;  
    this.onUpdateCallback = state\['update'\] || null;  
    this.onPreRenderCallback = state\['preRender'\] || null;  
    this.onRenderCallback = state\['render'\] || null;  
    this.onResizeCallback = state\['resize'\] || null;  
    this.onPausedCallback = state\['paused'\] || null;  
    this.onResumedCallback = state\['resumed'\] || null;  
    this.onPauseUpdateCallback = state\['pauseUpdate'\] || null;

    //  Used when the state is no longer the current active state  
    this.onShutDownCallback = state\['shutdown'\] || this.dummy;

    //  Reset the physics system, but not on the first state start  
    if (this.current !== '')  
    {  
        this.game.physics.reset();  
    }

    this.current = key;  
    this.\_created = false;

    //  At this point key and pendingState should equal each other  
    this.onInitCallback.apply(this.callbackContext, this.\_args);

    //  If they no longer do then the init callback hit StateManager.start  
    if (key === this.\_pendingState)  
    {  
        this.\_args = \[\];  
    }

    this.game.\_kickstart = true;

},

每个state在start的时候都会重新执行init,preload,create,update等方法一遍,this.onInitCallback.apply的时候还把参数传进来了,所以如果想start state的时候传参数,请定义个init方法,接受初始化传过来的data,这种模式犹如class的contructor一样使得

state之间相互独立,又可以相互传值,形成一条state网状链式结构,由于clearCurrentState默认clearWorld = true,所以在切换state的时候会先game.world.shutdown();相当于移除所有舞台元素,同时提供了shutdown方法用于关闭state时做的处理。

整个phaser的state流程是----》

初始化Phaser.Game可设置默认state ==》game.state.add(key,state);添加state,==》start state:game.state.start(key1) ==> 移除key1,game.state.start(key2) ==》如此循环,整个过程所有state共享game同时共享其所有方法和属性。

Phaser的初衷也是以最快的速度完成一个游戏,的确这种相互独立e又可以相互连接的state真是可以为开发节省很多很多时间。

设计思路:一个游戏先preload(一般之前会加个boot)=》menuState = 》gameState1=》gameState2=》gameState3等等=》overState  独立模块或场景都可添加state,UI界面最好是继承Group,不要做state,注意由于state切换的时候会destroy world,所以单例或共享View界面最好在start之前全部先移除,否则会出现destroy摧毁当前的单例view的child等所有 导致单例undefined 错误。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章