Phaser3学习笔记
说明#
最近Phaser的中文社区突然活跃起来。(也可能是我最近关注多一点造成的错觉。)很多初学者问一些过于基础的问题,比如怎么旋转一个精灵,有时引起社区成员的反感甚至奚落;这些问题本应该在自学入门中了解。我把自己自学Phaser的一些经验、资料累积在这里,对初学者可能有用。
- 文中材料主要是针对Phaser3的。Phaser3变化很大,与Phaser2不同的地方做了重点标记。Phaser3官网实例学习笔记已经单列一文。
- 摘录的代码都是片段。我用的是TypeScript语言,但摘录的代码可能是JavaScript,或在此基础上添加了TypeScript类型标记。JavaScript代码在TypeScript中是有效的,但可能使编辑器、编译工具等报错——这跟设置有关。
- 本文是随时更新的,最终会累积成一个便览手册;可通过搜索或底端的目录快速查找。
入门三部曲#
对于初学者来说,合适的入门程序可以大大地提高成效。我的经验是:
- 学习官网入门教程,知道做出Phaser游戏的最必要的技术,了解一个Phaser游戏的概观;
- 浏览官网实例,了解Phaser能做什么,大体是怎么做的;做好注记,以便日后快速查询;
- 从零开始做一个Phaser游戏,遇到问题随时搜索官网实例、API文档、开发日志等;
怎么看明白phaser文档?#
Phaser文档是通过脚本工具整理成数据库,然后再生成的,所以很整齐,很有规律。但首先要找到规律。可以先看官方实例,照着写一遍,一边对照文档,这样来找规律。
最让人迷惑的地方可能是,很多对象中都有不少成员(member)是用来引用其他对象的,相当于快捷方式。所以,看文档时,你看到的往往不是实际使用的情形,而需要通过连接不断跳转。
比如类似A_Class.B_Class.c_member:D_Class.e_method()
这样的形式,每个.
都是一个文档跳转,可能链条更长。其中c_member:D_Class
就表示一个引用关系,也就是说,实际关系是A_Class.B_Class.c_member.e_method()
。
而使用的时候还要实例化,可能是a_Class_instance.b_Class_instance.c_member.e_method()
,也可能直接是b_Class_instance.c_member.e_method()
。
举一个实际的文档(旧版)例子。层级和跳转如下:
- Phaser: Namespace
Phaser.Scene.add
实际上引用的是工厂类Phaser.GameObjects.GameObjectFactory
,其下有很多方法可生成游戏对象,比如Phaser.Scene.add.sprite()
就是生成精灵,并把精灵加到场景的显示列表中。实际上通常是在场景实例this
中使用,那就写作this.add.sprite()
。
而this.make.sprite()
引用的是游戏对象生成器Phaser.GameObjects.GameObjectCreator
中的方法sprite()
。这个方法能生成Sprite实例,但需要通过参数addToScene
指明是否添加到场景的显示列表中,或在生成后通过this.add.existing(child)
加到场景的显示列表中。
建议参看官方对模块结构的解释。
如果是因为英语的问题,可以使用
- 电子词典,配置好快捷键,比如GoldenDict可以使用ctrl+c+c把选中的文字放到词典浮窗中。
- DeepL,方便翻译大段文字,精确度很高,能识别程序代码。
学习资源#
- 官网入门教程。一个简明、完整的权威教程。
- Phaser3实例库。可以在labs.phaser.io连线使用。英文不大好的可以看我做的学习笔记:Phaser3官网实例学习笔记,《Phaser2官网实例学习笔记》。
- Phaser3在线文档(github版),或Phaser3在线文档(官网多版本) 。可参考完整的文档库。
- 官方电邮通讯/电子期刊Phaser World。其中的开发日志记录了Phaser3某些重要部分的的技术思路、使用方法,适于深度的专题学习。也包含很多官方推荐的他方教程,因无系统,精粗杂陈,不便使用。
- Phaser3笔记/Notes of Phaser 3,一位大牛对API各部分的笔记,另有他做的一百多个插件,以及常用工具列表。
- FairyGUI-Phaser,一个适配Phaser3的FairyGUI插件。
- 模板
- 丰富的官方模版
- geocine/phaser3-rollup-typescript,用Rollup+Vite打包和热更新,更改源代码后会迅速自动更新打包,刷新页面,速度非常快。有必要时官方typescript模板。
- Phaser3-TypeScript项目模板和实例库,使用Webpack或parcel打包。包含项目开发工具和15个比较完整的游戏实例。
- GitHub当有不少优秀的非官方JavaScript+Phaser3项目模板,因本人没有用过,不便推荐。
- 龙骨动画官方插件
- 3D支持插件 enable3d
- wechat-small-game-phaser库,这是一个使PhaserCE适配微信小游戏的库,思路清晰。经实测,基本架构同样适用于Phaser3。
- 《phaser3 微信小游戏若干问题》,解决Phaser3在微信中载入资源时不支持BLOB的问题(文章只解决了图片载入问题,其他资源载入问题需要照例自行修改Phaser3源码)。
- Phaser官方新建的独立讨论组。Phaser2论坛。Phaser3旧论坛。
- 我建的一个QQ群交流“Phaser3学习中心组”(938890881)。人气比较高的QQ群有“Phaser小站”(519413640,兼有同名网站),“Pixi.js&Phaser交流群”(384427721)
资源检索#
搜索引擎自然不用说了。
更新:Phaser Explorer phaser编辑器帮助中心,同时支持2、3版!!!可用空格代表通配符。 这是编辑器Phaser Editor 2D的项目。更多用法请看项目说明。
有一个我经常用的Phaser Chains项目,可惜目前仅基于Phaser2.4.7。期待早日支持Phaser3。 它可以对API文档和官网实例进行联合检索,支持通配符,支持对象属性的关系链条, 所以要比翻看文档方便、直观得多。比如搜"state.*sprite",就能查到很多相关条目(条目又关联着文档和官网实例):
1 2 3 4 5 |
|
另外,官方文档有SQLite和json版,值得深入挖掘。
Game配置对象GameConfig#
Phaser3的配置对象包含很多项目,详见配置对象GameConfig文档,也参考Phaser.Game配置文件详例,还有小抄。
Phaser3广泛使用配置对象,又比如在生成Sprites、Tweens实例时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Scene 场景#
引用其他的资源#
1 |
|
场景间的通信#
https://blog.ourcade.co/posts/2020/phaser3-how-to-communicate-between-scenes/
GameObjects游戏对象#
简明关系请参看Phaser3游戏对象一览
-
Phaser3游戏对象的一个重要变化,是把Phaser2中Sprite、Text、Group等游戏对象的容器功能分离出来,归到Container中。这样一来,Sprite、Text等不能添加子项;Group有子项,但实际是逻辑分组,非独占,通常也不能整体地控制显示状态(可以通过Actions批处理)。Phaser2的渲染核心是PIXI,虽然PIXI有Container,但在Phaser2中一般不直接使用。
-
Phaser3游戏对象的原点(origin,对应于Phaser2的锚点/anchor属性)默认为中心点,而不是左上角。原点通过属性
originX
,originY
获取,通过方法setOrigin(x, y)
设置。origin以比分比计算(0到1),另有以像素计算的displayOrigin。 -
Phaser.GameObjects.GameObjectFactory/游戏对象工厂类。实际上通过
Scene.add
属性引用,比如this.add.sprite(config)
。 - Phaser.GameObjects.GameObjectCreator/游戏对象生成器。实际上通过
Scene.make
属性引用,比如this.make.sprite(config)
。与工厂类不同,生成器生成的游戏对象不会自动加载到场景上。
Sprite精灵#
精灵是Phaser游戏中最主要的游戏对象,在游戏对象中有代表性。要便览精灵的属性和方法,请参考《Phaser3 API文档大纲-Sprite》。
1 |
|
Size内部尺寸#
Size内部尺寸、原本(native)尺寸,包括:
- Sprite.width
- Sprite.height
改变内部尺寸,供帧或物理体生成之用,不会影响已经渲染的游戏对象的尺寸:
- Sprite.setSize(width, height)
DisplaySize显示尺寸#
- Sprite.displayWidth
- Sprite.displayHeight
有两个方法改变显示尺寸:
- Sprite.setScale(x [, y])
- Sprite.setDisplaySize(width, height)
直接生成有物理属性的精灵#
1 2 3 4 5 |
|
Image/图像#
图像与精灵的不同主要在于,Image没有动画(Animation)组件,消耗更小。 但图像也有输入事件、物理体,可以应用补间动画,可以滚动,所以只要你不使用动画,那么就应该用Image代替精灵。图像常用于显示静态图像,比如logo,背景,布景等没有动画(Animation)的元素。(在Phaser2中,Image的操控项更少,尤其是没有物理属性。) 注意:Tween(补间运动/补间动画)不同于动画(Animation)。
1 2 3 |
|
Shape/几何图形及其子类#
Shape是Phaser3新增的游戏对象。Shape本身不能加到场景上,而是用作下列几何图形游戏对象的基类:
- Arc弧形
- Curve曲线形
- Ellipse椭圆(包括正圆)
- Grid栅格
- IsoBox等距(isometric)方体
- IsoTriangle等距三角体(四面体)
- Line线条(包括三角形、梯形)
- Polygon多边形
- Rectangle方形
- Start星形
- Triangle三角形
这些Shape子类,可以加入场景、组和容器中。你可以像处理其他常见游戏对象一样处理它们,比如应用补间动画、缩放、打开输入和物理属性。Shape通常支持填充色和笔触色。
Shape拥有常规游戏对象的所有特性,不同之处在于它有高效的渲染方式。Shape不需要使用纹理,但又可以利用WebGL中的批处理的好处,所以能很方便地在游戏中快速渲染形状。它不仅没有Graphics的高消耗问题,甚至有些Shape子类比Sprite还要快。
Graphics/图形#
Graphics主要用于绘制一些基本几何图形(比如方形、圆形、多边形、直线、弧线、曲线)。 Graphics初始的时候是空的,需要制定笔触色、填充色,然后用路径绘制,最后笔触着色、填充。有些常见形状,比如方形,可以方便地绘制。
在Canvas和WebGL两种模式中,Graphics的渲染是不同的。在Canvas下是绘制路径,在WebGL下是绘制多边形。这两种方式都很消耗资源,尤其是图画复杂的时候。所以,如果你的几何图画不会变化,或者变化少,你应该用Phaser.GameObjects.Graphics.generateTexture
把几何图画“备份”成一个纹理,并把它返回用于生成精灵Sprite或者其他游戏对象。请注意你的几何图画的复杂程度和数量。
??不适合做缩放操作,而要重绘。因为,相对于Image而言,Graphics缺少一些控制尺寸、位置、纹理的属性和方法,缺少的父类有Phaser.GameObjects.Components.
之下的
- Alpha(但有AlphaSingle)
- Flip
- GetBounds
- Origin
- Size
- TextureCrop
- Tint
1 2 3 4 |
|
1 2 3 4 5 |
|
TileSprite瓦片精灵#
用小块Tile铺设背景。【有待补充】
Sprite3D/3D精灵#
标准精灵的封装,可以被3D相机渲染,可以放在3D空间内。
PathFollower/循路器#
PathFollower是一种特殊的Sprite,拥有精灵的所有属性,同时可以自动循着一条Path运动。
Blitter/显存块移动单元/位块传送器#
Blitter是一种特殊容器,用于更新、管理Bob对象。Bob主要目标是快速渲染,而不是追求弹性;它包括一个纹理或者纹理中的帧、位置、透明度,还有翻转、是否可见的开关,但不能旋转、缩放;它们批量绘制方法以加速渲染。
一个Blitter绑定一个纹理。由这个Blitter生成的Bob可以使用这个纹理中的任何帧(可以动态改变),但不能使用别的纹理。就是因为绑定唯一的纹理,使得它们速度很快。
可以从游戏代码直接操控Bob对象,但Bob的生成和销毁需要通过父级Blitter来操作。
如果你需要在屏幕上渲染大量的帧,那么可以考虑Blitter;尤其是用作自己的特效系统的基础,它是特别有用的。
Bob/显存块移动对象/位块传送对象#
参见Blitter。
Text文本#
Text对象使用标准的Canvas fillText API(效果比较多),在内部隐层画布上预先渲染,然后生成纹理,再渲染到游戏上。 文本所用字体必须是浏览器已经可以取用的,或通过第三方加载器预先已载入(Phaser没有字体加载功能),或已经嵌入CSS中。注意:如果字体名称中有空格或者数字,需要加上引号。
任何内容和格式变动都需要整体重制,因此很消耗算力。变动频繁,或者文本很多时,最好使用BitmapText。
1 2 |
|
BitmapText静态位图文本#
BitmapText的灵活性不如Text,比如不能使用阴影、填充、Web字体,但速度更快。 生成工具:
推荐使用XML格式,如果使用JSON格式,必须符合X2JS库的转换标准。这里有在线工具。
DynamicBitmapText动态位图文本#
与静态位图文本不同的是,动态位图文本每渲染一个字都会引发一次回调,让你控制这个字的渲染属性,比如位置、缩放、着色;这样会更耗时,所以不需要逐字控制时应使用静态位图文本。
Zone区块#
Zone是一个矩形区块,没有纹理,不显示,但存在于与显示列表中,有游戏对象的一般属性。主要用做点击、角色变动的定位,或重叠检测,或作为非显示对象的基类。
RenderTexture渲染器纹理#
能够在运行时绘制多个游戏对象,实时生成纹理,以供他用。对webGL友好,不激发GPU加载。但不能抗锯齿(如渲染Shape时有锯齿)。
Container容器#
一个游戏对象加入容器后,即从显示列表中删除,而加入容器内部的显示列表,由容器负责它的渲染;位置也是相对于容器位置而言的。
容器可以嵌套。
容器可以添加遮罩或作为遮罩,但是子项不能添加遮罩,即遮罩不能叠加。
容器可以添加输入,但是因为它没有纹理,所以你必须提供一个shape作为点击区域。容器的子项也能添加输入,并独立于容器。
容器可以赋予物理体。不过,如果子项也有物理属性,并且容器或任何前辈对象不是位于0x0位置的话,那么你可能碰到意外的结果,比如物理体偏置。不把带物理属性的子项考虑进来,是因为需要过多的运算。
要注意容器带来的额外问题。嵌套越深,消耗越会升级,尤其是在输入事件上。尽可能避免使用。
参考Group
。
Group组#
Group是多维的抽象分组,类似于打标签,便于批量地生成、排布、控制、循环处理近似游戏对象。与Container不同,它自身没有没有显隐、缩放、定位、旋转属性(但可以快速控制子项的这些属性)。他对子项不是独占的,子项可以存在于多个组中。因此Container更适于把多个显示对象集合成一个复合型显示对象,以便整体地控制显示状态;Group则适合于对近似对象进行批量操作。
组的一个重要用途是对象池。
生成静态物理组#
1 2 3 4 5 6 |
|
生成动态物理组#
1 2 3 4 5 6 7 8 9 10 |
|
Lights (WebGL only)/光照#
Light2D的管道。
Mesh(WebGL only)网格#
Mesh/网格是Phaser3新增功能,只支持webGL渲染模式。Phaser2不支持Mesh,因此也不支持Drangonbone等动画格式中的网格蒙皮功能。
1 2 3 4 5 6 7 8 9 |
|
Parameters
1 2 3 4 5 6 7 8 |
|
通过newMesh.vertices[0]
、newMesh.uv[0]
等引用、操作。
Quad (WebGL only)/方块#
Mesh网格的一个单元。
Shader (WebGL only)/着色器#
控制quad方块的显示。【???】
DOMElement/DOM元素#
DOMElement是在游戏上操控HTML元素的方法。需要在游戏配置对象中开启:
1 2 3 |
|
这样会自动在游戏画布上方生成一个尺寸相同的DOM容器div。所有DOM元素都会插入同一个DOM容器,而不管是在那个场景中生成的。可以这样添加DOM元素到容器中:
1 |
|
你可以像操控其他游戏对象一样操控DOM对象,比如设置位置、缩放、旋转、透明度等属性。但它不能开启输入;但可以直接用addListener方法添加原生事件监听器。
DOM元素是对齐原生HTML和游戏对象的好办法。比如,你可以你可以插入登录框,或文本输入框,或者从第三方服务插入条幅广告,或者用于高解析度的文本显示与UI。
Video/视频#
Video对象可以回放预先载入缓存的视频,或者播放给定链接的视频。视频可以是本地的,也可以是流式的。
1 2 3 4 5 6 |
|
loader#
从字符串加载svg#
1 2 3 4 5 6 |
|
文字清晰度(crisp)问题#
场景及其中的文本整体拉伸会导致模糊(blurry),最好指适配/拉伸主场景,其他场景不拉伸;每个Text对象都可以设置分辨率 resolution > 1,这样做有大量消耗内存的风险。
有时使用bitmap text效果比较好。
像素对齐也可能有好处:使用Math.round等计算出图像的整数位置(x,y)。
SVG在加载时渲染成纹理,默认情况下,其尺寸由SVG文件自身指定;同样有消耗内存的问题。但可以指定:
1 2 3 4 5 6 7 8 9 10 11 |
|
输入#
输入事件请参考Phaser3事件一览。
游戏对象的输入控制#
- GameObject.setInteractive( [shape] [, callback] [, dropZone])
开启交互,生成交互游戏对象,即把此对象传给输入管理器,以开启输入。
点击区域(hitArea)默认是基于游戏对象纹理帧生成的正方形。
没有绑定纹理的游戏对象(如Graphics、BitmapText)则需要传递一个shape(配置对象或geometric shape),这时候还需要传递回调函数。
回调函数要能接收三个变量,并返回表示击中与否的布尔值:callback(hitArea, x, y): boolean
。比较常用的一类是几何图形的静态函数Contains
,如Phaser.Geom.Rectangle.Contains
(<static> Contains(rect, x, y)
)。
1 2 3 4 5 6 7 8 9 |
|
- GameObject.removeInteractive()
移除交互游戏对象。不是即时的,是在the next game step。
- GameObject.disableInteractive()
临时禁用。
- GameObject.input.hitArea.setSize(width, height)
设置击中区域(默认方形)。
- GameObject.input
此属性用以保存交互对象。
Scene.input生成输入管理对象#
如方向键管理对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Input.Events输入事件#
DRAG#
热点拖拽输入事件(Pointer Drag Input Event)。
当一个热点拖拽着一个游戏对象移动时,本事件由当前场景所属的输入插件发送。
在场景内监听本事件的用法是:this.input.on('drag', listener)
。
一个热点一次只能拖拽一个游戏对象。
从特定游戏对象监听此事件,则要用:GAMEOBJECT_DRAG
事件。
参数:
Name | Type | Description |
---|---|---|
pointer | Phaser.Input.Pointer | 激发此事件的热点。 |
gameObject | Phaser.GameObjects.GameObject | 拖拽中的交互游戏对象。 |
dragX | number | 拖拽热点当前在世界中的x坐标。 |
dragY | number | 拖拽热点当前在世界中的y坐标。 |
相关事件:
- DRAG_END
- DRAG_ENTER
- DRAG_LEAVE
- DRAG_OVER
- DRAG_START
- DROP
POINTER_WHEEL#
热点滚轮输入事件(Pointer Wheel Input Event),比如鼠标滚轮。
当热点有滚轮事件更新时,本事件由当前场景所属的输入插件发送。
在场景内监听本事件的用法是:this.input.on('wheel', listener)
。
事件等级关系如下:
- GAMEOBJECT_POINTER_WHEEL
- GAMEOBJECT_WHEEL
- POINTER_WHEEL
最上层事件最先被派发,同时再往下流动。请注意,上级事件处理器可以停止该事件的进一步传播。
参数:
Name | Type | Description |
---|---|---|
currentlyOver | Array. |
一个数组,包含事件生成时热点下的所有交互游戏对象。 |
deltaX | number | 鼠标滚轮或类似设备水平移动的总量。 |
deltaY | number | 鼠标滚轮或类似设备垂直移动的总量。通常,上滚为负值,下滚为正值。 |
deltaZ | number | 鼠标滚轮或类似设备z轴移动的总量。 |
1 2 3 4 |
|
生成碰撞、重叠管理对象#
1 2 3 4 5 6 7 8 |
|
调整尺寸Game.resize#
根据浏览器窗口变动调整尺寸。
1 2 3 |
|
销毁Game实例#
1 |
|
src文件结构(最佳实践)#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
另外,如果不使用webpack等打包工具,index.html
文件和资源文件夹assets/
可以跟入口文件main.ts
放在同一层级(即src/
)。
微信版需要单建一个项目文件夹,存放微信小游戏必须的文件game.js
(game.ts
的编译目标)、game.json
、project.config.json
,以及assets/
、js/
等资源。微信版的问题当专文说明。
Math数学#
RND随机数#
- 随机取选取列表中的项目
1 2 |
|
对象对齐(定位)#
常用四个类中的静态方法:
- Display.Align.In,使两个对象叠置对齐(同侧对齐)。
- Display.Align.To,使两个对象并排对齐(对侧对齐)。
- Display.Bounds
- Actions.GridAlign中的静态方法)
- AlignTo(逐一对齐,是Display.Align.To的批处理)
- GridAlign(对齐网格)
- PlaceOn系列(均匀放在图形上)
- IncX、IncY、IncXY
- setX、setY、setXY
- 等等
Phaser.Display.Align.In.#
BottomCenter(gameObject, alignIn [, offsetX] [, offsetY]) BottomLeft(gameObject, alignIn [, offsetX] [, offsetY]) BottomRight(gameObject, alignIn [, offsetX] [, offsetY]) Center(gameObject, alignIn [, offsetX] [, offsetY]) LeftCenter(gameObject, alignIn [, offsetX] [, offsetY]) QuickSet(child, alignIn, position [, offsetX] [, offsetY]) RightCenter(gameObject, alignIn [, offsetX] [, offsetY]) TopCenter(gameObject, alignIn [, offsetX] [, offsetY])] TopLeft(gameObject, alignIn [, offsetX] [, offsetY])] TopRight(gameObject, alignIn [, offsetX] [, offsetY])]
都是Phaser.Display.Align.In
下的静态方法,BottomCenter(gameObject, alignIn)
,是使gameObject与alignIn两个游戏对象,叠置时底边中心对齐(同侧对齐)。以下各项类此。
Display.Bounds.#
Bounds
(边界)类包含一些用于游戏对象定位的静态方法。
CenterOn(gameObject, x, y),将游戏对象的中点对齐某点。 GetBottom(gameObject),获取游戏对象的底部坐标。以下各Get方法类此。 GetCenterX(gameObject) GetCenterY(gameObject) GetLeft(gameObject) GetOffsetX(gameObject) GetOffsetY(gameObject) GetRight(gameObject) GetTop(gameObject) SetBottom(gameObject, value),设置底边值。以下各Set方法类此。 SetCenterX(gameObject, x) SetCenterY(gameObject, y) SetLeft(gameObject, value) SetRight(gameObject, value) SetTop(gameObject, value)
Actions动作(批处理)#
Phaser. Actions
包含一组静态方法,可以对一组游戏对象进行批量处理,应用相同的动作。常常结合逻辑分组Group.getChildren()
一起使用。
条目要包含要处理的属性、方法,否则无效。
@actions\place on circle.js
1 2 3 4 |
|
设置背景色、背景透明#
1 2 3 4 |
|
1 2 |
|
几何学方法Phaser.Geom#
几何学对象,包含一组静态的几何学方法,比如计算面积、周长、比例等,判断两个几何形包含与否、重叠与否等,生成、克隆、拷贝几何形等,以及集合形的移动、调整尺寸、适配等。
1 2 |
|
成员:
\<static> CIRCLE :number \<static> ELLIPSE :number \<static> LINE :number \<static> POINT :number \<static> POLYGON :number \<static> RECTANGLE :number \<static> TRIANGLE :numbe
逐帧控制对象/主循环控制#
参考:
通过扩展游戏对象类的preUpdate, update, postUpdate方法可以逐帧控制对象,或回调,但不是好办法。
update每帧调用一次;注意在其中调用super.update,如果父类有此方法。
preUpdate每帧调用一次,在update之前执行;注意在其中调用super.preUpdate,如果父类有此方法。
更清晰的方法是,比如,通过在场景上监听Phaser.Scenes.Events.UPDATE,来代替使用update方法:
1 2 3 |
|
缩放适配#
屏幕旋转后保持全屏适配phaser3-scaling-resizing-example
编译/构建后文件过大问题#
inline-source-map
体积大,注释掉;- 不直接导入
Phaser
,而是逐一导入其下的对象,如import { AUTO } from 'phaser/src/const';
或import { AUTO } from 'phaser';
,或- 定制构建 https://github.com/photonstorm/phaser3-project-template-custom-build