使用Phaser3写H5的简易避坑指南

回到H5行业后的前几个项目,试着用了下以前一直想用的Phaser3,体验还阔以。本篇简单讲讲一些经验和碰到过的坑。

首先需要提前讲的,比较合适的参考/文档(目前来看)有以下三个:

  1. Phaser3 官方 API 文档
  2. Phaser3 官方 Demo 集锦
  3. (我心目中巨实用的)目前最靠谱的第三方文档

(最后一个重点推荐,之前有好多东西,我看官方文档看的满头问号,但是看这个文档是真的能看懂~)

好,下面可以开始(suì)正(suì)题(niàn)了。

Phaser3使用起来大概可以分成 3 个部分:

  1. 加载基础的库文件(废话),这里包括Phaser3库本体(可以自己定制)和一些第三方插件(如果有的话);
  2. 全局配置,是一个对象,包括渲染方式(Canvas 还是 WebGL)、背景色、Canvas 容器的 CSS 基础样式、缩放适配、事件监听的开/闭,一直到 Scene 配置(后边讲),直接写到里边就成;
  3. Scene,简单来说有点像游戏里的场景,每个场景里会包含各自独立的精灵、图片之类不同的资源,以及各种数据,甚至插件等。比如切换页面,就可以直接用场景功能来实现。

在代码结构上,我个人比较倾向于每个Scene/页面一个文件,一个Scene重新new个Phaser.Scene出来就好。然后在首页HTML里把每个Scene文件全加载以后,再上init.js全局配置就行了。当然,一些统计/jQ辅助/wechat分享注册/bgm自动加载之类的代码放前边就好。

下边说一些零零碎碎的经验:

  1. 首先是Phaser3库文件可以精简,官方有个photonstorm/phaser3-custom-build项目,就是教你怎么自己build库文件的,我自己fork了一下放到了MarsGT/phaser3-custom-build,主要是敲掉一些用不着的东西,精简过后大概600k~800k左右。
  2. this.add.text如果用自定义字体(比如用Fontmin精简过的),需要在HTML里加个用了这个字体的div,比如<div style='font-family:myfont;position:absolute;visibility:hidden;'>000</div>,否则字体不会生效;如果就想用默认的字体,直接把fontFamily设置为sans-serif就行了。
  3. 骨骼动画目前没法直接用DragonBones的Runtime(我是Demo都跑不出来的那种不能用),但可以使用Spine的,直接到Phaser3主项目的plugins/spine/dist目录下就能找到这个插件了,在HTML里加载,然后在全局配置里加上
    plugins: {
       scene: [
           {
               key: 'SpineWebGLPlugin',
               plugin: SpineWebGLPlugin,
               start: true
           }
       ]
    },
    
    就能用了。
    另外在DragonBones输出Spine格式的时候要注意,我个人不推荐用编辑器直出,而更建议用DragonBones Tools这个工具转换已有的DragonBones文件,主要是因为编辑器直出的是3.3版本的(spine)文件,而转换出来的则是3.6。不过转换出来还需要检查下json主配置文件,里边有个deform字段,其下可能会存在一个空字符串主键导致报错,改成"default"即可。还有,this.load.spine之前可以先用this.load.setPath设置下路径,这样json文件和atlas文件可以不用再写整段路径了,还有png素材也不用去单独指定路径了。
    再补充下,如果DragonBones动画里使用了ik,到网页上时ik可能会出现完全失效的情况,会使动画出现“鬼畜”(实际是由于失去了ik的约束,使原有骨骼出现了错位),这是因为 DragonBones导出的Spine JSON文件中,ik字段数组下的每个成员都缺少了一个order字段(其实就是排序),加上之后就没问题了。
  4. 说一下预加载。预加载一般单独放个Scene里,比如可以叫loader,那么loader需要的资源就在loader.preload回调里加载,然后整个H5需要用到的资源放到loader.create回调里加载(实际就是动态加载)。另外注意在create回调里加载资源的时候,最后需要加一句this.load.start()启动加载。至于progress和complete这俩事件就不多说了。
  5. 如果不是全站CDN(HTML文件在普通服上,其它静态资源在CDN),可以在HTML文件的<head>里加个<base>标签设置CDN基地址,相对比较方便。
  6. 对于数量限定的位图字体,比如只在游戏计分出现的字体(只有数字),用RetroFont是最方便的。切图处理时需要注意,要把所有用到的字符单独输出等大的图片,然后横向拼到一张图里,加载时按普通图片加载就行,初始化需要用RetroFont的方式:
    this.cache.bitmapFont.add('my_num', Phaser.GameObjects.RetroFont.Parse(this, {
       image: 'my_num',
       width: 52,
       height: 83,
       chars: '0123456789',      // 指定图里都是什么字符
       charsPerRow: 10,          // 每行字符数
       spacing: { x: 0, y: 0 }   // 每字符相隔像素数
    }))
    
    然后就可以直接按bitmaptext那样去用了this.add.bitmapText(106, 64, 'my_num', '012')
  7. 全局配置,直接放代码了:
    var config = {
       type: Phaser.AUTO,                    // 一般用Phaser.AUTO就行,很少有项目需要强行指定Phaser.WEBGL的。Canvas那项我觉得没啥用
       // backgroundColor: 0x1b1b1d,         // 看项目需要,如果需要背景色时可以直接在这里设置
       render: {
           antialias: true,                  // 抗锯齿,建议开
           transparent: true                 // Canvas容器背景透明,如果有和DOM有交互时这项可以开,记得把背景色关掉
       },
       canvasStyle: 'overflow:hidden;',      // 在这里设置Canvas容器的行内样式
       banner: false,                        // 把这项关掉,就不会在控制台输出Phaser引擎版本之类自带的调试信息了
       scale: {
           width: 750,                       // 建议把设计稿尺寸写这里,会按这个尺寸缩放
           height: 1206,
           mode: Phaser.Scale.ENVELOP,       // 适配方式,Phaser.Scale.ENVELOP就相当于是cover,而Phaser.Scale.FIT相当于contain
           autoCenter: Phaser.Scale.CENTER_BOTH, // 居中方式,有CENTER_HORIZONTALLY、CENTER_VERTICALLY和CENTER_BOTH三种可选
           max: {                            // 指定适配后的最大尺寸,这里限制一下,在pc端会美观一点(不会出现尺寸特别大的情况)
               width: 1080,
               height: 1920
           }
       },
       physics: {
           default: 'arcade',                // 物理引擎,一般不大的需求用arcade就够了
           arcade: { debug: false }          // arcade设置,打开debug可以看到刚体的定界框
       },
       input: {
           mouse: false,                     // 移动端项目一般关掉mouse,不然会出点透bug
           activePointers: 1                 // 多指触摸限制,不过貌似用处不大
       },
       disableContextMenu: true,
       loader: {
           crossOrigin: 'anonymous'          // 避免图片跨域
       },
       scene: [loader, home, game]           // 这里放每一个Scene的变量,初始化完成后默认加载第一个Scene
    }