1

标题是 Phaser3 之三,是特意给前面留了2个坑位。(码字画图不易,转载请注明作者及出处。)

最近在调研一些渲染引擎,才了解到 Phaser。Phaser 是一款非常优秀的 2d 游戏引擎,能够快速让开发者做出自己的小游戏。

我的一个 phaser game demo: 拯救星星

Phaser plugins 机制

Phaser plugins 构成

1、BasePlugin 类规定了 Phaser 所有插件的基本规格,非常简单:

  • init
  • start
  • stop
  • destroy

2、ScenePlugin 类是在 BasePlugin 规格之下的、专门用于 Scene 插件的父类。
也就是说,后面实际用在场景(Scene)中的,都是继承自 ScenePlugin 的子类。

3、PluginCache 会 cache 以下两类插件:

  • 内置核心插件 (进一步分为:全局插件 + scene 专用插件)
  • 开发者定制插件(用于开发者扩展)

4、DefaultPlugins 默认将内置这些插件:

Global: ['game', 'anims', 'cache', 'plugins',
    'registry', 'scale', 'sound', 'textures' ],

CoreScene: ['EventEmitter','CameraManager','GameObjectCreator',
    'GameObjectFactory','ScenePlugin','DisplayList','UpdateList'],

DefaultScene: ['Clock','DataManagerPlugin','InputPlugin',
    'Loader','TweenManager',  'LightsPlugin']

看到没,Scene 场景插件,又分成了两拨:CoreSceneDefaultScene

所以总的来说,Phaser plugins 是两大类:官方内置插件 + 开发者自定义插件。其中,内置(核心)插件分为全局和场景(Scene)专用插件。

全局插件意味着所有场景都能用到,场景专用插件顾名思义只在该场景下独有。

再来看看官方文档是怎么说?

@classdesc
The PluginManager is responsible for installing and adding plugins to Phaser.

It is a global system and therefore belongs to the Game instance, not a specific Scene.

It works in conjunction with the PluginCache. Core internal plugins automatically register themselves with the Cache, but it's the Plugin Manager that is responsible for injecting them into the Scenes.

There are two types of plugin:

  1. A Global Plugin
  2. A Scene Plugin

A Global Plugin is a plugin that lives within the Plugin Manager rather than a Scene.
You can get access to it by calling PluginManager.get and providing a key. Any Scene that requests a plugin in this way will all get access to the same plugin instance, allowing you to use a single plugin across multiple Scenes.

A Scene Plugin is a plugin dedicated to running within a Scene. These are different to Global Plugins in that their instances do not live within the Plugin Manager, but within the Scene Systems class instead.
And that every Scene created is given its own unique instance of a Scene Plugin. Examples of core Scene Plugins include the Input Plugin, the Tween Plugin and the physics Plugins.

You can add a plugin to Phaser in three different ways:

  1. Preload it
    https://labs.phaser.io/edit.h...
  2. Include it in your source code and install it via the Game Config
    https://labs.phaser.io/edit.h...
  3. Include it in your source code and install it within a Scene

For examples of all of these approaches please see the Phaser 3 Examples Repo plugins folder.

For information on creating your own plugin please see the Phaser 3 Plugin Template.

Phaser plugins 机制

对于 Phaser 插件机制,先说一个最粗糙的轮廓:

首先,由 installScenePlugin 记录一些外部插件的安装情况,比如安装 spine 插件。

然后,所有将通过 PluginManager.addToScene() 进行安装,完成每个插件的实例化。

在场景切换时,会重新检查插件的 install 情况。

最后在 destroy game 时,会释放资源。

插件生命周期

因为我搭建的 demo 中涉及到 installdestroy 这两个情形,但是发现再次进入到游戏场景时,出现报错:

Uncaught TypeError: Cannot read property 'add' of undefined
at SpineFile.addToCache (SpinePlugin.js:24784)
at LoaderPlugin.fileProcessComplete (LoaderPlugin.js?868d:876)
at ImageFile.onProcessComplete (SpinePlugin.js:2714)
at Image.data.onload (SpinePlugin.js:24908)

为此,不得不对插件的生命周期进行一次梳理。

SpinePlugin 为例,其安装 installdestroy 过程如下:

SpinePlugin 插件的生命周期

我发现,在game.destroy(true)后,插件的数据是被清除了,但是其实例仍然存活——按照代码逻辑看,反正就是各种情况下,正好跳过了对插件本身的 remove

才开始用就这么大的 bug 吗?

于是我反向去寻找要是插件正确被 remove 掉,它应该是调用哪个方法呢。嗯,果然就有收获:

/**
    * Removes a scene plugin from the Plugin Manager and Plugin Cache.
    *
    * This will not remove the plugin from any active Scenes that are already using it.
    *
    * It is up to you to remove all references to this plugin that you may hold within your game code.
    *
    * @method Phaser.Plugins.PluginManager#removeScenePlugin
    * @since 3.8.0
    *
    * @param {string} key - The key of the plugin to remove.
    */
removeScenePlugin: function (key) {
    Remove(this.scenePlugins, key);

    PluginCache.remove(key);
}

这个方法需要在 game.destroy(true) 后自己手动调用。

好了,插件的生命周期也跑过一遍了,现在对 Phaser 的信心大增。

开发如何引入插件

* 首先引入插件脚本
* ```
* import * as SpinePlugin from './SpinePlugin.js';
* ```
*
* 方式一
* and then adding it to your Phaser Game configuration:
*
* ```
* plugins: {
*     scene: [
*         { key: 'SpinePlugin', plugin: window.SpinePlugin, mapping: 'spine' }
*     ]
* }
* ```
* 
* 方式二
* If you're using ES5 then you can load the Spine Plugin in a Scene files payload, _within_ your
* Game Configuration object, like this:
* 
* ```
* scene: {
*     preload: preload,
*     create: create,
*     pack: {
*         files: [
*             { type: 'scenePlugin', key: 'SpinePlugin', url: 'plugins/SpinePlugin.js', sceneKey: 'spine' }
*         ]
*     }
* }

其实还有第三种,官方文档中也有说明,即:

this.load.plugin(
    /*key: */'SpinePlugin', 
    /*url: */'./plugins/SpinePlugin.js', 
    /*start: */false, 
    /*mapping: */'spine' 
);

但是这种动态加载 plugins 的方式,我尝试失败了。暂时没有追究原因,目前我是用上述文档中的第一种方式加载插件的。

加载插件我们解决了,剩下一个紧要问题是:如何引入插件资源?

我想以懒加载的方式动态引入插件。这里有四个办法:

  • 1、走 webpack dynamic import(),插件模块本身被打包成一个 chunk ,且在需要时自动被 import
  • 2、静态资源 copy 到 plugins/ 目录,再用自写 injectScript 或者 require 脚本按需注入。
  • 3、静态资源 copy 到 plugins/ 目录,再用官方的 loader.plugin 方法。(我没有跑通。。。)
  • 4、(非动态方法) 静态资源 copy 到 plugins/ 目录,并将插件优先以 <script> 引入到 html 文档中。

方法 1 的好处是,你无需管理这些资源,全交给 webpack 自动搞定。
方法 2 需要引入额外的脚本来做这件事情,不过代价也很低。
方法 3 我最期望是这样引入,奈何没跑通。待进一步了解。
方法 4 这是最简陋的方式。

插件开发

以注册的方式动态加入到 Phaser 的插件,包括不限于这些:

  • SpinePlugin
  • FacebookInstantGamesPlugin

码字画图不易,转载请注明作者及出处。


Jeremy_young
128 声望2 粉丝