共计 7733 个字符,预计需要花费 20 分钟才能阅读完成。
本文由云 + 社区发表
使用一个简单的游戏开发示例,由浅入深,介绍了如何用 Laya 引擎开发微信小游戏。
作者:马晓东,腾讯前端高级工程师。
微信小游戏的推出也快一年时间了,在 IEG 的游戏运营活动中,也出现了越来越多的以小游戏作为载体运营的活动类型,比如游戏预约,抢先试完等等,都收到了非常良好的效果。
在支持微信小游戏的游戏引擎中,Cocos,Egret,Laya 都对小游戏的开发提供了很多强大的支持。前段时间正好抽空研究了一下这块的内容,现做一个总结,针对如何使用 Laya 引擎开发微信小游戏给大家做一下介绍。因为时间有限,研究并不深入, 如有高手路过,忘不吝赐教。
做个啥游戏呢?“绝地求生”很火,我们做个“绝地求死”如何?策划也很简单,和绝地求生相反,主角不是跳伞的玩家,而是地面的炮手,大炮要把跳伞的伞兵用大炮一个个都消灭掉。
牛逼的策划有了,咱们进入正题,看看怎么实现吧!
1. 如果不用引擎会怎样?
1.1 Canvas 了解下
微信小游戏提供了 canvas 这个游戏核心组件。利用 Canvas 可以在画布上画出文字、图形、图像等等。不过讲微信小游戏之前,得先说说 H5,在 H5 时代获取 canvas 对象非常简单,如下图:
var canvas=document.getElementById(“myCanvas”);
var ctx=canvas.getContext(“2d”);
常用的一些 API:
ctx.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); // 绘制图片
ctx.fillText(text,x,y,maxWidth); // 绘制文字
ctx.rect(x,y,width,height); // 绘制矩形
ctx.clearRect(x,y,width,height);// 清除矩形内像素
ctx.scale(scalewidth,scaleheight);// 缩放
ctx.rotate(angle);// 旋转角度
。。。。
微信小游戏里,也提供了 canvas 对象,只不过获取接口变了:
wx.createCanvas()
其他 H5 环境下有的 Canvas API,微信环境里也都有。
1.2 动画的原理
Canvas 只是一个 2D 的画布,要做一个游戏,动画总不能少吧?要让图片能动起来,这又是怎么做到的呢?请看下图:
好吧,动画其实就是不断画图片,然后擦除,再画图片,再擦除的循环过程,肉眼看起来,那就是动起来了。
在古老的电影胶片时代,我们看到的电影,就是一张一张连续帧的胶片组成的,最后投射到大屏幕上,变成了我们看到的电影。
1.3 动画性能优化
但是,动画是讲究帧率的,一般如果能达到每秒 60 帧,那就和电影一样是很流畅的动画效果了。计算公式:1000ms/60fps=16.67ms,这就要求我们每次动画里的业务逻辑计算,都要 16.6ms 里完成,不能影响下一帧的显示,否则就是卡顿,也就被人说这个游戏好卡,性能好差了。
知道原理了,性能优化具体怎么做呢?
Canvas 分层 有些游戏中,背景是不变的,为了提高性能,可以把游戏背景抽离出一个单独的 canvas,这样,在画面发生变化的时候,不需要重绘整个背景,只需要绘制变化的那部分就可以。
减少 API 调用 每次的 ctx 的 api 调用,都是有性能消耗的,所以,尽量减少每帧的 api 调用次数,具体怎么减少,就要看业务需求了。
图片预裁剪 图片的裁剪过程,也是有性能消耗的,我们可以把裁剪的图片预先存起来,之后在绘制的时候,直接从内存里拿,而不需要每次都重新裁剪。
离屏绘制 直接操作上屏的 canvas,性能是很差的,尤其是有单帧绘制操作很多的时候,性能下降更明显。这个时候,我们可以预先创建一个离屏的 canvas,预先在这个 canvas 完成这一帧要绘制的所有动作,最后一次性的把这个离屏 canvas 绘制到上屏 canvas 中。
避免阻塞 由于我们需要保证 16.67ms 就要完成一次帧的绘制,如果这一帧里,逻辑运算时间超过 16ms 怎么办?那就一定会卡帧了。我们可以使用 webworker 之类的技术,把耗时的逻辑计算在另一个线程执行,或者把任务进行拆解,降低每帧的耗时。
当然还有很多其他更多的技巧和手段来提升 canvas 的性能,在这样的情况下如果我们直接使用 canvas 去开发一个游戏,还会面临比如碰撞算法、物理系统之类的问题。所以,如果只用 canvas 去开发游戏,就如同你在吃鸡游戏里,只拿了一把平底锅,你怎么和别人正面刚?
所以,我们需要一把 98K 把自己武装起来,那就是使用游戏引擎开发。
2. 为什么选择 Laya?
目前支持微信小游戏的引擎,有 Cocos,Egret,Laya,我们先看下三者的功能比较:
从各种支持度上来讲,laya 是目前支持度最好的,也据 laya 侧的宣传,他的性能也是最高的。(关于性能的问题,因外部水军比较多,在没有做实际详细测试前,暂时不发表评价。)
在公司内部,都有三种引擎的游戏实现,下面是截止 5 月份的公开数据的引擎占比:
其实三个引擎都提供了很好的支持度,一般来说,如果原先使用过 Cocos 实现过 APP 端游戏要移植到微信小游戏端来的,使用 Cocos 是最好的选择,如果是从头开发一款小游戏,那还是在 Egret 和 Laya 里选择一款吧!
3. Laya 环境搭建
前面讲了那么多,都还只是前戏,只是为了大家对游戏的开发有个初步的了解,从这一节开始我们就进入正题了。
到 https://www.layabox.com/ 去下载最新的版本,并进行安装。目前有 1.X 版本和 2.0 版本。(本文使用 1.7.20 版本做示例)
然后就可以创建一个新的游戏项目了,我们可以现在选择创建一个 UI 示例项目
[创建新工程]
3.1 代码模式
当然就是给你写代码的地方,感觉这个编辑器,就是在 VSCode 的基础上改的。连最顶上的 Code 标识都还在。也因为这样,所以才能很好的支持 TypeScript。
[代码模式布局]
为什么要使用 TypeScript? 本文不详细展开比较,只需要了解 TypeScript 是 Javascript 的超集,因为多了个“Type”表示他支持强类型,并且由于静态类型化,在写代码的时候编辑器就能提示你的错误,所以更适合开发游戏这种逻辑复杂的应用就好了。当然最终 TypeScript 还是会像 ES6 一样,被编译成普通的 Javascript 执行。但是在开发阶段管理代码来说,已经可以驾驭大型项目了。
3.2 设计模式
就是用来设计 UI 界面的地方,拖拖拽拽就可以把游戏页面整出来。Laya 提供了好多组件,如果有需要的可以使用,当然也可以不用他的组件,自己搞自己的自定义组件。
[设计模式布局]
4. Laya 的 HelloWorld
都说作为一个程序员,买来文房四宝之后,写下的第一行字,一定是“Hello World”。(我拿着公司刚发的 20 周年 LAMY 纪念钢笔,写的第一行字,居然也是“Hello World”,汗~~~)
4.1 游戏初始化
4.1.1.GameMain.ts
首先删掉系统刚才默认的文件“LayaUISample.ts”, 然后新建文件 GameMain.ts
import WebGL = Laya.WebGL;
class GameMain {
constructor() {
//TS 或 JS 版本初始化微信小游戏的适配
Laya.MiniAdpter.init(true,false);
// 初始化布局大小
Laya.init(375,667, WebGL);
// 布局方式设定
Laya.stage.scaleMode = Laya.Stage.SCALE_SHOWALL;
Laya.stage.screenMode = Laya.Stage.SCREEN_VERTICAL;
Laya.stage.alignV = Laya.Stage.ALIGN_CENTER;
Laya.stage.alignH = Laya.Stage.ALIGN_CENTER;
}
}
new GameMain();
Laya.MiniAdpter.init() 是 Laya 提供的对小游戏提供的适配,因为在小程序 & 小游戏环境下,并没有 Bom 和 DomAPI,比如,没有 window,document, 所以需要这样一个适配器,对小游戏的开发方式,进行兼容。
4.1.2. bin/index.html
修改 bin 目录下的 index.html , 删掉 LayaUISample.ts 的引用,改为下面的方式:
<!– 启动类添加到这里 –>
<!–jsfile–Main–>
<script src=”js/GameMain.js”></script>
<!–jsfile–Main–>
在 index.html 里,提供了很多 Laya 的类库,这些类库,最终会被打包成合并一个 code.js. 因为微信小游戏的体积限制,我们不需要把所有的库都加载进来,只选择我们需要的库就好了,用不到的可以都删除。
4.1.3. run
接下来,点击运行,就会出现模拟器界面了。
[运行模拟器]
先别管黑乎乎的一团,下面我们就要增加“Hello World”了。
4.2 绘制文字
4.2.1. Laya.Text
再次修改 GameMain 的代码如下,重点是 var txt:Laya.Text = new Laya.Text();
import WebGL = Laya.WebGL;
class GameMain {
constructor() {
//TS 或 JS 版本初始化微信小游戏的适配
Laya.MiniAdpter.init(true,false);
// 初始化布局大小
Laya.init(375,667, WebGL);
// 布局方式设定
Laya.stage.scaleMode = Laya.Stage.SCALE_SHOWALL;
Laya.stage.screenMode = Laya.Stage.SCREEN_VERTICAL;
Laya.stage.alignV = Laya.Stage.ALIGN_CENTER;
Laya.stage.alignH = Laya.Stage.ALIGN_CENTER;
// 创建 Text 对象
var txt:Laya.Text = new Laya.Text();
// 给 Text 的属性赋值
txt.text = “Hello World”;// 设定文字内容
txt.color = “#ffffff”; // 设定颜色
txt.fontSize=20; // 设定字体大小
txt.pos(100,200); // 设定位置
// 将 Text 对象添加到舞台
Laya.stage.addChild(txt);
}
}
new GameMain();
在上面的代码中,我们给 Stage 舞台上,添加了 Text 对象,然后点击运行
啊哦,传说中的 HelloWorld 终于出现了
4.3 绘制图片
4.3.1 loadImage
Laya 的 Sprite 提供了一个非常简单的 loadImage 方法,可以即时加载图片并加载到舞台上。
// 设置舞台背景色
Laya.stage.bgColor=”#1e83e8″;
// 创建 img Sprite 精灵
var img:Laya.Sprite = new Laya.Sprite();
// 加载显示图片,坐标位于 100,50,并设置宽高 130*108
img.loadImage(“demo/paratrooper.jpg”,100,50,130,108);
// 把图片添加到舞台
Laya.stage.addChild(img);
预览如下,是不是很简单?
但是这个方法,其实并不实用,在真实项目中,一般会有很多图片,我们不会一张一张图片的去加载,而是预先加载好,再去显示图片。也就是我们常常在游戏主界面看到的进度条,其实就是在加载资源。
4.3.2 资源预加载
Laya 提供一个资源加载器:Laya.loader,来解决加载的问题。我们把上面的代码再修改下,实现先加载完图片,然后再绘制图片。
private imgPath1:string=”demo/paratrooper.jpg”;
private imgPath2:string=”demo/shell.jpg”;
constructor() {
//….. 省略 N 行代码
this.renderImage();
//…. 省略 N 行代码
}
renderImage():void{
// 定义图片路径集合
var resArray=[
{url:this.imgPath1,type:Laya.Loader.IMAGE},
{url:this.imgPath2,type:Laya.Loader.IMAGE}
]
// 使用加载器加载图片路径
Laya.loader.load(resArray,Laya.Handler.create(this,this.onLoadComplete),Laya.Handler.create(this,this.onLoadProgress))
}
// 加载完成后,把图片绘制到画布上
onLoadComplete():void{
console.log(“ 加载完成 ”);
var img1:Laya.Sprite = new Laya.Sprite();
img1.graphics.drawTexture(Laya.loader.getRes(this.imgPath1),100,50,100,100);
Laya.stage.addChild(img1);
var img2:Laya.Sprite = new Laya.Sprite();
img2.graphics.drawTexture(Laya.loader.getRes(this.imgPath2),100,300,100,100);
Laya.stage.addChild(img2);
}
// 这里可以获取到加载的进度,以后可以制作进度条
onLoadProgress(percent:number):void{
console.log(“percent->”+percent);
}
4.3.3 图集
只是预加载图片还不够,实际场景由于有很多小图片,所以我们可以把这些小图片拼合成图集,这就类似在前端在做性能优化的有时候所使用的 css sprite 精灵图,这样制作成图集,不但加载性能更高,而且也更便于制作帧动画。
图集的加载类似这样:
var resArray=[
{url:”res/atlas/demo.atlas”,type:Laya.Loader.ATLAS},
]
Laya.loader.load(resArray,Laya.Handler.create(this,this.onLoadComplete),Laya.Handler.create(this,this.onLoadProgress))
和之前的图片加载时 Laya.Loader.IMAGE 不同的是,type 变成了 Laya.Loader.ATLAS。
那图集怎么制作呢?还有,大量的游戏界面,真的就靠手动一张图片一张图片的显示吗?当然不!因为我们接下来该了解下 UI 编辑器了。
5. UI 编辑器
UI 编辑器,当然是用来编辑 UI 的,大多数的客户端程序开发环境,都有类似的 UI 编辑器。点击左侧的
图标,进入 UI 编辑器模式,如下图:
具体 UI 编辑器的功能介绍,建议还是看官方文档,这里就不赘述了。
5.1 创建 UI
因为我们创建的是默认 UI 项目,所以 UI 编辑器里,有一个 TestPage.ui,可以不用管他,我们创建一个自己的 UI。点击 文件 -> 新建文件
进入新建页面窗口,页面类型有 View 和 Dialog 两种,因为这里我们做的是整个页面,所以选 View。如果你有兴趣去看源码,其实 Dialog 也是基于 View 实现的,只不过多了 Dialog 的一些特性。
如果对这个 view 后面还有逻辑代码要写,建议勾选“创建逻辑类”,这样就会自动在 View 目录下自动创建一个和 UI 对应的 GamePage.ts
[新建页面 UI]
5.2 导入资源
在 assets 目录下,新建一个 demo 资源目录,把需要的图片都扔进去,然后在 UI 编辑器的资源面板最下方找找到刷新按钮
,新增资源图片后,一定要记得点下刷新,否则资源面板的内容不会自动刷新。
只要是 demo 文件下的图片,都会被自动打包成图集,路径就是 res/atlas/demo.atlas。不知道有没有同学发现,在上面的图片中,有部分资源显示“不打包”,这是什么原因的?
点击文件 -》项目设置,我们会看到图集限制了能被打入图集的单图的最大宽高,和最终图集的最大宽高,默认标准可以自行修改。超过这个图集标准的图片,就不会打包到图集中去,就需要手动加载了。
[请在这里填写图片描述]
5.3 编辑 UI
编辑页面功能,会用 ppt 的,应该都会用了,拖个图片谁不会?直接把资源管理器的图片,拖到右侧场景编辑器里。这次我们拖了一个蓝天白云的背景,并在最下方放了一个大炮,看起来还有点意思。
顶部有一排图标,是用来协助对齐图片用的,提供了顶部对齐,底部对齐,左对齐,右对齐,中线对齐等等,如果图片很多,用这个对齐就很方便了。
右侧的属性栏,就比较常用了。var 这里,你可以给你拖进来的图片组件,给个变量名,这个变量名,最后会在之前自动生成的逻辑类里用到。我们把大炮定个变量名“pao”,后面会用到;x,y,width,height 这里,就是坐标和宽高,就不用多说了吧?
5.4 导出 UI
UI 做好以后,有个重要的工作,就是千万别忘记导出。很多初学者,经常会忘记这点。导出 UI,才会重新生成图集和 UI 相关设置。
导出以后,我们看 laya/pages/GamePage.ui 文件,不用管里面的详细内容,里面就是刚才我们拖拽图片,自动生成的响应配置文件。
5.5 使用 UI
下面我们要把刚才编辑的 GamePage 显示出来,那就回过头来,再次修改 GameMain.ts
class GameMain {
// 定义静态变量 gamePageView
public static gamePageView:view.GamePage;
constructor() {
//…
this.renderImage();
//…
}
renderImage():void{
// 资源加载
var resArray=[
{url:”res/atlas/demo.atlas”,type:Laya.Loader.ATLAS},
]
Laya.loader.load(resArray,Laya.Handler.create(this,this.onLoadComplete),Laya.Handler.create(this,this.onLoadProgress))
}
onLoadComplete():void{
// 初始化 view
GameMain.gamePageView = new view.GamePage();
// 添加到舞台
Laya.stage.addChild(GameMain.gamePageView);
}
}
new GameMain();
运行一下,主界面游戏背景,和大炮都已经架设好了,好的开端,就是成功的一半了。
接下来,根据最初的牛逼策划,我们要像 pubgm 一样,让伞兵从天下掉下来,怎么实现?接着看动画部分吧!
关注云 + 社区,及时获取下篇更新