Cocos引擎-方面:
小游戏概论,主要包括一些入门的相关知识。我们将内容分成了6个部分,分别是:
1.如何正确了解小游戏?
2.小游戏开发需要哪些知识?
3.小游戏开发环境
4.小游戏的资源管理
5.HTML 5发布为小游戏
6.小结
一、正确理解小游戏
为方便大家理解,第一部分先讲一下小游戏到底是什么。
相信这个群里的所有听众基本上都不会对这几款小游戏感到陌生。目前整体来看,第一波上线的小游戏更多以休闲类为主。例如《跳一跳》,应该是当前最流行的产品,虽然它只是一款单机。
此外,除棋牌类游戏,能够支持好友对战功能的只有《欢乐坦克大战》和《Q宠大乐斗》两款产品。
当然,对于棋牌来说,小游戏可能也会为其带来一波新浪潮。近一段时间棋牌类游戏由于房卡棋牌等模式的兴起也非常流行,小游戏上线后,大家拉个群,发一个小游戏的链接,就可以进行游戏了。这种便利会给棋牌行业带来很大的增益。
1.聊天入口。用户可以将小游戏分享链接直接推到群聊或与朋友的会话中,直接将他们拉进小游戏中。
2.搜索入口。玩家可以通过下拉微信聊天记录,或在专门的小程序页面对特定小游戏进行搜索。若想利用这个入口,游戏需要有较好的口碑,当然如果名字起得太长太难,可能也会影响这个入口的使用效果。
3.微信小游戏列表入口。玩家可以去这个列表中去寻找、发现新的小游戏。
4.朋友圈。从我的直觉来看,这个入口与聊天入口将会是最重要的2个小游戏入口。但是通过群聊的聊天分享,我认为是比朋友圈更重要的入口。也就是小游戏立项的时候务必、务必要做好通过社交拉新用户进入游戏的设计。
在小游戏里,除了游戏本身的逻辑开发和引擎使用外,对微信目前开放社交API的使用,以及怎样去设计社交,都会对游戏的传播效果造成影响。分享点的刺激是有讲究的,在早期游戏里,游戏更多是以“战力”、“等级”这些数值向的设计分享点,而现在,游戏分享点则更多在于“情感之上”,这些都是在游戏设计过程中值得思考的内容。提醒一点,80后和90后触发分享的情感点是不一样的,我不能说更多,但是游戏制作人自己应该加以揣摩。
回到小游戏上,在讲技术之前,我们要去理解小游戏的趋势架构是如何的。小游戏不等于原生游戏,也不等于HTML5游戏。现在一些媒体经常会讲:“小游戏是H5游戏的春天。”严格来说,这么讲不是太准确。
标准的HTML5游戏是什么呢?标准H5游戏是Facebook Instant Games、爱微游、QQ空间的那些游戏 。这些是在浏览器里跑H5的游戏;但是小游戏本质是Runtime游戏,它跟小程序类似,结构是小程序+游戏库API。
在这个架构之上,会有更多优势。首先它会比H5游戏更稳定;其次小游戏跳出重进后,用户不用像在H5游戏里那样进行重新登录;另外小游戏可以调用微信的原生用户,在转发、支付等方面也固定了接口,可以说对整个微信生态建设是有很大帮助的。当然对于我们来说一定要注意的是,虽然小游戏的调试环境是在浏览器当中,但这只是其 HTML5兼容性的体验,它真正在微信当中时是用 Runtime的。
我们自己很清楚H5和小游戏之间在技术结构上的差异,但现在如果说小游戏不是H5游戏又会被人喷,于是我们今天发明了一个新名词:HTML5技术栈。它包含了所有纯H5和类H5的技术解决方案。
这张图我想表达的是,在原生游戏中,可能还有一些游戏在用自研引擎来制作,但在小游戏中,已经没有自研引擎的身影了。所以不管是国内的Cocos、Laya、Egret这些H5引擎,还是用国外的Phaser、Createjs,基本上都是可以直接用的。
需要注意,特别是小公司,大家最好使用已经被微信适配证明过的引擎,特别是现在微信小游戏开发文档只有中文版本,海外的引擎可能并不能帮助你多少,除非你的研发团队能吃透他们的英文文档而且忍受带时差的论坛技术支持。而且在这次上线的游戏中,除了华夏乐游与飞鱼科技外,其他都是腾讯自研的,你不知道他们在研发过程中究竟踩了多少坑,改了多少东西。
对于3种游戏的对比,我来严格列举一下他们之中区别。首先,他们的入口都不同,这也就代表了它们的用户属性是不同的。比如,可能很多小游戏用户并不是原生游戏玩家,作为非玩家它们对于游戏产品的需求也会与玩家不太一样,对于“VIP1-VIP15”、“首充大礼包”、“首充3倍”,它们或许就不会那么敏感。当然,这些都是值得去继续揣摩的事情。
在流量成本上,小游戏和HTML5的崛起有一定原因源于其低于原生游戏的流量成本。实际上游戏品类没有什么变化,变的是流量成本,当某一模式的流量成本过高了,行业就会有动力去挖掘一个新平台,去获取一批新的游戏用户。PC端游到PC页游,PC页游到手机原生、再到手机页游,表面上看是一件技术驱动市场变化的事情,但本质上是流量成本驱动技术迭代、进而驱动市场变化。这个本质要理解清楚。
在开发的时候,CP也要注意性能的约束。纯H5游戏在性能和占用内存上都是约束性最高的,所以如果往往H5游戏做小游戏移植版本,不论用哪个引擎,适配工作都会进展得相对顺利。当然需要注意,诸如CSS、DOM是不能用于小游戏开发的。但是反过来,如果你移植一款手机原生游戏到微信小游戏,就得非常小心内存使用的问题了。
其实原生游戏移植到小游戏也有一些案例,比如《保卫萝卜》,但一定要注意,在移植原生游戏过程中一定要注意性能和内存上的问题,如果控制不好很可能程序就会崩溃。其实有不少曾经的客户过来问我,如果原生游戏是用C++这些语言写的,能不能直接移植到小游戏上。很遗憾,不行,目前必须通过手工去翻译,因为今天微信提供的标准接口只有JavaScript。
我出于个人技术倾向、项目历史的原因,非常强烈地不喜欢那些通过编译器去转化不同编程语言的方案,因为这些方案并不「自然」,表面上看是省了很多移植开发量,但在调试和优化阶段会有无数的坑等着你。所以最优的方案,我仍然建议大家用 JavaScript 重新移植游戏,不论你的原生游戏是Cocos C++, Lua还是Unity的。
二.小游戏开发商需要哪些知识?
接下来我来讲述一下小游戏的相关知识,希望帮助大家能够顺利上手开发。
这张图是小游戏目前可以使用的技术栈。最上层是各家游戏引擎,经过微信验证后,都是可以提供给大家尝试的。中间一层是小游戏的底层,是小游戏所借助的HTML5的技术栈。
为什么说它是HTML5技术栈?因为小游戏并没有完全使用HTML 5标准,它只是模拟这些的接口,从而更好地完成HTML 5向小游戏的移植工作。最下面这部分是微信自己的API,之后会对它进行展开讲解。
首先是底层的技术,它包括JavaScript代码和Canvas 2D、WebGL 1.0的API。这2个API都是和Web上的API是一致的,这也是微信想要拉拢H5开发者所作出的努力。微信同样也很快地与国内的几个主要引擎商进行了合作,让各家引擎可以第一时间支持微信小游戏的发布。同时微信小游戏Runtime也开发了一个Adapter,并且移植了一些海外诸如Phaser, Three.JS, CreatreJS等H5引擎到小游戏环境里面。
上层我们建议大家通过游戏引擎来开发,这是出于对成本的考量,因为游戏引擎可以为大家尽可能缩短研发周期、降低项目成本和风险。现在微信所试验过的就是上图所列出的6个引擎,这些大家都是可以去尝试的。
除此之外还有微信小游戏的SDK,这些虽然是是微信所提供最底层的东西,但实际上是除去引擎外大家需要去关注的部分。在游戏玩法逻辑研发之外,这些接口都是要去研究的,这才能让游戏拥有更好的社交玩法。具体这些接口的详细功能大家可以参阅微信的文档。目前可以看到,仅小游戏所拥有的直接分享功能和即点即玩的特性让它在传播和进入门槛上都拥有极大的潜力。
这是《跳一跳》和《星途Go》好友排行榜的例子,包括游戏里的排行榜和每个关卡的排行榜。
这个例子是《坦克大战》中的邀请对战,当你把邀请发到群里后,进入的玩家可以选择阵营,形成3对3模式。
直接接受邀请是微信所做的最大创新。在原生游戏中,玩家点击邀请链接后首先要下载安装,之后还要互加好友,随后才可以进行匹配,整体流程路径很长,流量转化成本也很高。然而在小游戏场景下,当你的好友点开你的分享链接后,可以直接进入游戏,这是一个突破,大家需要在这方面考虑更多。
我刚刚仔细检查了一下微信小游戏的API,有转发邀请,但是并没有获取其他微信好友昵称、备注名、分数的排行榜API。大家可以再检查确认一下,如果的确是在今天没有这个公开的API,那么游戏设计为好友排行榜竞争的玩法时候则需要注意一下。不过目前在腾讯游戏中都可以使用,未来这个API也有可能会出现。
三.小游戏开发环境
接下来我会进入更加技术的一个环节,让大家更了解如何去适应小游戏的开发环境。
这张图是小游戏的运行框架,它不是微信官方给出的,是我们自己分析得出的。首先,最底层是iOS安卓的硬件、系统等内容,这些提供了系统层级别接口。微信在这基础上开发了微信安卓版和iOS版,其中包含了包括用户、支付、文件、多媒体等各种模块。
然而在在这样一个原生应用中,没有浏览器的加持H5程序又怎样运行JavaScript代码呢?这是通过JS VM在安卓上集成V8引擎,在iOS上集成JavaScript Core引擎去执行JS代码的。
微信仅仅执行JS代码还不足以让开发者的小程序运行起来,因为开发者所调用的用户转发文件系统这些接口都是不存在于JS VM中的。实际上,它是通过绑定的接口来讲微信原生接口桥接到JS接口上的。当你调用这些JS接口时,实际上就是在调用原生接口来达成功能。如果大家对绑定技术感兴趣,可以去关注我们开源的JSB技术,这也是目前游戏引擎当中唯一开源的实现技术。
这里我们就不深入细讲了,继续讨论微信小游戏是怎样提供接近于浏览器环境以及怎样跟游戏引擎相结合。
这张图比上一张多了一个Adapter,我将小游戏总结为2部分:图形渲染API以及微信API。右边的Adapter是微信官方提供的适配脚本,它提供了右边2个API不存在的一些浏览器API,这些往往是大家开发游戏所依赖的API。比如,创建Image,这样才可以使用贴图;Audio是播放功能;LocalStorage是存储;使用WebSocket来做网络调用等。
在小游戏原始环境的图形渲染API和微信API的基础上,加上Adapter之后,就跟浏览器的环境非常接近了。
在此基础上,微信提供了一个完整的Runtime,接近于浏览器的环境,但并不等同。游戏引擎的工作就是进一步抹平浏览器和微信环境的差异。比如在Cocos引擎中,你可以直接切换HTML5版本和小游戏版本,不需要做任何游戏代码上的修改。也就是说你用JS写的游戏,一份代码就可以同时在小游戏平台和原生平台上运行。
总结一下,微信的三大接口:
1.渲染接口
2.微信功能接口
3.Adapter接口
除了抹除浏览器和小游戏之间的差异外,游戏引擎还可以带来成本上的巨大降低。就像上图写的,成本降低主要来自于3个方面:
1.Framework中,当我们游戏引擎封装了更高层的API,开发游戏会更加便利,这就使得人力成本降低
2.编辑器层面中,一个好的编辑器可以明显提高程序、美术、策划之间的协同效率。
3.在兼容性层面中,游戏引擎带来的设备兼容性和稳定运行效率可以降低大家的维护成本,加上刚才提到的跨平台能力,可以给大家带来更强大的渠道和流量。
所有这些人力成本、维护成本的降低以及跨平台所带来的丰富流量,可以让大家的项目以更低的开发成本完成游戏,继而盈利。
当你希望在微信小游戏环境中使用第三方库的时候,很多第三方库会遇到问题。比如JQuery,它就使用了DOM的API,DOM API在微信小游戏中是不存在的,而且也无法模拟,因为根本就没有DOM树。
纯JS的第三方库是绝对不会有问题的。当然,有一些第三方库使用ES6来编写的,这个时候你要注意去掉“ES6转ES5”的标签,在微信开发者工具的详情页面中可以找到。总之,原则是使用DOM API的第三方库请不要使用,JS的第三方库可以随意使用。
依赖于网络的第三方库,像Protocol Buffer,这些在加载的部分是需要定制的,需要加载部分API适配到微信小游戏所适配的API上,比如wx.request.downloadFile。
四.小游戏的资源管理
第四个部分,我想分享的是小游戏的资源管理,这是小游戏目前和浏览器环境差异最大的另一个方面。
首先为大家介绍一下小游戏资源管的分布方式。大家如果使用过开发者工具的话会注意到,当你开发一个小游戏的时候,你如果点预览,它会有一个上传的过程,之后扫它提供的二维码就可以测试这个小游戏。实际上这个上传的过程就是将你的小游戏包上传到CDN内。在你的用户扫这个二维码时,实际上是从微信的CDN下载这个小游戏包到他的手机上,并执行。
这是最简单的版本,让我们来看一下相对复杂一点的。
微信小游戏为了能够提升首包加载速度,将包体限制在了4M,这显然对很多HTML5游戏来说是不足够的。所以大家就需要将一些资源文件放到自己的远程服务器中。也就是这张图所体现的访问关系:当用户扫你二维码的时候,会让小游戏包下载到微信,然后微信内小游戏包代码在执行过程中会请求远程服务器资源。
不同用户会将不同的资源下载到他自己的沙盒环境当中,并且不同小游戏之间的资源缓存也是相互分割的。这样就可以保障不同用户和不同小游戏间的资源不会互相冲突。
H5开发者会非常在意首场景的加载体验,微信小游戏也非常需要注意这一点。小游戏在下载过程中会将包首先完整下载,然后才会进行详细的代码完整初始化。在初始化之后再去加载远程资源,再去启动场景。
这与浏览器有很大的区别。浏览器永远是按需加载,当它需要一个资源的时候才会加载一个资源。所以大家一定要在小游戏当中控制好自己的包体大小。
经过一段时间研究并与微信团队进行沟通后,我们得出了一个目前较好的资源解决方案。我们会将代码包放在小游戏包内,所有的资源都放在开发者的Server上。这样,当代码请求某个资源时,就会动态进行动态下载,这样才能满足按需加载的需求。
所以这张图和上面那种图没有太大区别,唯一需要注意的是,在微信CDN上储存代码包,在开发者Server中储存详细的小游戏资源。值得注意的是,你的代码是不能够放到自己的Server上的,微信小游戏禁止在微信CDN以外的域名中加载它的代码。
接下来我解释一下Cocos Creator是加载微信小游戏时的资源策略。首先,我们同样从微信的CDN当中下载代码包。下载完成后,当你的代码需要某些资源时,我们会先从大包里去查找。若内置资源没有,我们会去查找资源缓存(稍后解释)。如果资源缓存也没有,我们会从远程服务器去下载。数据下载后,我们会将数据自动缓存到资源环境中。这样便形成了一个缓存机制。
我们这样做的原因是,小游戏环境是实际上是没有类似浏览器的缓存和资源过期机制的。 在浏览器中,当用户请求一个页面时,第一次请求会下载所有资源,一段时间后,第二次请求时它会自动检查所有资源是否过期,如果没有过期则会从本地缓存空间中下载该资源,这对于开发者和用户都是透明的。但小游戏没有这样的机制,所以当你的信息再去请求资源时,每次都会重新下载该资源,不管你这个资源此前下载过多少次。所产生耗电量和加载时间对于用户来说都是不可接受的,所以我们在Cocos Creator当中内建了一个更完整的解决方案。
如何去处理资源过期的问题呢?如果说资源URL是完全一致的话,在逻辑当中是不会下载新资源的,这就会导致Bug。我们所推荐的解决方案是,在Cocos Creator打包时会有一个“MD5 CASH”选项,勾选后我们会为所有文件名打上MD5码。这样当文件更新后,文件名就会变化,这就一款为服务端URL会变化,这时代码包在请求时就会被认定为一个新的资源,这样就完成了资源缓存和更新机制。
同时我们还提供了API让开发者删除资源缓存,这样在大版本更新时,如果缓存资源过多,你就可以先去清除缓存,然后再去更新所有资源。
当然,用户如果觉得我们的资源管理方案不太适合你,你也可以去自己设计一个合适自己的资源管理方案。这些方案所以来的API微信都有提供,即微信文件API和微信网络API。网络API可以让用户将文件下载到缓存空间中;文件API可以支持文件的重命名、删除等操作。
五.HTML5 游戏发布为小游戏
相信很多朋友关注的是,如果我已经有H5游戏了,我怎样把这个H5游戏发布到小游戏当中。这个其实是微信团队非常关注的事情,他们希望大家的H5游戏可以去针对微信做了社交玩法后发布到微信小游戏环境当中。我们继续以Cocos Creator为例来看如何将已有游戏发布到小游戏。
这张图是我们编辑器的截图,在这里面可以去编辑场景、UI,可以进行资源的管理、发布、打包。
用Cocos发布小程序需要这几个步骤:
1.你可以从你的小程序公众平台上找到App ID并输入,然后将发布平台修改为WechatGame。
2.点击右下角build构建,你就会看到下面这个界面:
3.出现该画面就说明在Cocos Creator构建完成后,就可以直接在微信开发者工具里看到你的项目了。这个过程不需要用户去修改任何配置文件。
4.当调起微信开发者工具之后,你就可以做预览操作,在手机上测试微信小游戏了。
六.小结
总结一下知识点。
首先,微信提供游戏上的支持,并且提供了庞大用户基础和用户分享API,这肯定会催生出完全不同的游戏体验。我认为社交性玩法在微信上会有更大的发挥空间,会比以往的社交平台都要大。
其次,我们讲到了微信小游戏和浏览器环境的2大差异:API支持和资源加载。
另外,我们推荐大家使用游戏引擎,加速游戏的开发和迭代,从而降低产品风险。
同时,小游戏、手机页游、PC页游其实都依赖于HTML5的技术栈,包括Cocos Creator原生发布也都依赖于此。HTML5引擎提供了基于HTLM5技术栈的跨平台发布,为大家提供了更多选择和可能性。
今天我们所说的HTML 5技术栈包括手机页游、手机原生和PC页游。手机页游包括QQ空间、微信小游戏、厘米秀这些Runtime方案,Facebook Instant Games这种纯H5方案以及爱微游、疯狂游乐场这些H5渠道等。手机原生包括各种JSB、Runtime打包游戏、微端游戏等。第三个是PC页游,因为Flash之前宣布2020年停止更新,所以很多PC页游上的Flash游戏现在也在转成HTML5游戏。
简单计算一下,我用手机原生游戏市场规模除以PC端游规模,再乘以PC页游规模,粗略算出手机页游的市场空间可以打到每年280亿人民币。如果说,手机页游和PC页游互为此消彼长的话,向上取整则可能会有500亿的市场空间。中国游戏产业规模占全球游戏市场的25%,当然这个数还在上升,算上这一方面,H5技术栈可以支撑全球手机页游 + 部分手机原生 + PC页游市场,理论市场容量上限可以达到2000亿人民币。所以说,现在做这方面的技术储备其实是非常有价值的。
Q&A环节
1.作为个人开发者,利用微信的用户和支付功能,而不是将微信作为一个引流工具,能否从游戏中获得较好的收益?另外,Cocos和微信支付对接中是否有坑?
关于支付:微信小游戏目前还没有与iOS打通,具体什么时候打通我也不太清楚。所以说能否从游戏中获得较好的收益,这个问题应该由微信来回答,因为这些数据如今都是保密的,包括DAU、留存率、安卓目前的收益率、用户画像,像用户画像是需要你们去自己琢磨的。
关于引流:我不确认微信是否允许这么做。但这肯定不是微信小游戏团队希望看到的结果
关于Cocos和微信支付对接:这种支付SDK对接根本就不是我关心的层面。就Cocos来说,与微信对接没有什么坑,只是纯粹的SDK调用。我相信包括其他引擎也不会有什么坑,因为实在是太简单了。
2.微信小游戏的优势和劣势有哪些?如何绕开劣势?
优势在于其庞大用户基数与其社交性玩法,流量成本会很低。目前来看,若想造成病毒式的传播,目前可能也就在Facebook、微信、QQ这种大的社交平台上可以比较容易地实现。
当前劣势可能还是在于对非游玩家特点的理解上。我认为大部分微信小游戏用户很可能原来并不是传统意义上的游戏玩家。这些玩家的游戏化需求、情感、乐趣,都和我们以往理解的手游玩家不一样。当然我认为也不能把这一点完全看成劣势,因为就算是非游戏玩家也是有游戏化需求的。问题关键在于怎样正确理解、切入、服务这些大量的非游戏用户。现在小游戏刚刚起步,所有人都在摸索这件事情,谁能先利用好这个平台的用户特点,谁就能成为这个新领域的巨头。
3.目前包括已上线的十几款小游戏都是以轻度休闲为主,处于试水阶段,如何看待重度网游在小程序的发展潜力和前景?
我从2个方向来讲。
首先,其实在PC页游发展上也可以看到,都是从休闲游戏(偷菜抢车位、抓奴隶)开始的,再将用户培养到一定程度后才慢慢出现重度页游。手机页游也是如此,不要想着第一天就可以直接跳跃到重度游戏。行业发展规则必然是要先去培养和教育用户,然后再过渡到做中度、重度游戏,我认为不会有非常快的跳跃。
其次,微信小游戏很多玩家都是非游戏玩家,他们目前对于重度游戏可能并不会很感冒。如果是重度游戏玩家,他为什么要去玩小游戏?到底小游戏用户里有多少是重度玩家?重度玩家为什么要玩微信小游戏,而不是已有的《传奇霸业》《奇迹MU觉醒》等重度的微信精品游戏,理由是什么?这现在都是需要去摸索和探讨的问题。其实就算是Facebook Instant Games目前也是主要由轻度休闲游戏组成的。
其实就现在PC平台来看,目前很多PC端游的品类也没有完全移到页游上面,典型如FPS、MOBA游戏,在PC页游上是看不到的。平台属性还是天然就决定了的。端游和PC端游的用户场景是不同的,像之前很多挂机类页游之所以能崛起,就是因为在很多场景下用户不具备安装客户端的条件,同时也无法投入过多精力去进行端游,比如在办公室中。目前来看,手机原声游戏和手机页游的用户场景确实有所重叠,但这都需要进一步地探讨并优化。
4.小程序对初始包大小限制比较严格,目前看官方的《四川麻将》感觉很粗糙,如何突破这个限制?
初始包大小限制就是4M。粗不粗糙和初始包大小是没有关系的,你可以首场景加载4M初始包 + 10M资源,只不过加载速度会慢而已,你的代码加载更多也没有问题。我认为可能《四川麻将》粗糙的原因可能在于开发者更多考虑了用户的等待时间体验。我们也是建议尽量控制首场景的大小,在之后的游戏中逐渐加载更多资源,为用户提供一种渐进加载的游戏体验。
5.微信小游戏不支持热更新了吗?
热更新在 HTML 5 游戏中是不存在的,因为是没有本地文件的,你永远可以更新自己的服务器内容,让用户得到更新的资源。对于小游戏来说最关键的是,你能不能热更新你的代码包。你的代码包会存储在微信CDN中,你必须向微信CDN提交更新申请,至于具体的审核过程,还需要看后面微信官方所给出的具体方案。
6.小游戏内存控制在多少比较合适?
这需要看用户手机的硬件情况,开发者可以做的就是尽量控制不要让内存占用持续增长,维持在某一个峰值以下。我认为控制在100M以下比较安全。H5游戏一般是控制在150M以内,200M也是可以跑,但不安全。安卓一般内存会比苹果手机要大,所以在测试的时候优先测试苹果手机,尤其是 iPhone 6,如果没有崩溃现象,基本上就是可以了。
Laya引擎-方面:
导语:使用一个简单的游戏开发示例,由浅入深,介绍了如何用Laya引擎开发微信小游戏。
在支持微信小游戏的游戏引擎中,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中。
离屏绘制
直接操作上屏的canvas,性能是很差的,尤其是有单帧绘制操作很多的时候,性能下降更明显。 这个时候,我们可以预先创建一个离屏的canvas,预先在这个canvas完成这一帧要绘制的所有动作,最后一次性的把这个离屏canvas绘制到上屏canvas中。
当然还有很多其他更多的技巧和手段来提升canvas的性能,在这样的情况下如果我们直接使用canvas去开发一个游戏,还会面临比如碰撞算法、物理系统之类的问题。 所以,如果只用canvas去开发游戏,就如同你在吃鸡游戏里,只拿了一把平底锅,你怎么和别人正面刚?
所以,我们需要一把98K把自己武装起来,那就是使用游戏引擎开发。
2. 为什么选择Laya?
目前支持微信小游戏的引擎,有Cocos,Egret,Laya,我们先看下三者的功能比较:
从各种支持度上来讲,laya是目前支持度最好的,也据laya侧的宣传,他的性能也是最高的。(关于性能的问题,因外部水军比较多,在没有做实际详细测试前,暂时不发表评价。)
在公司内部,都有三种引擎的游戏实现,下面是截止5月份的公开数据的引擎占比:
其实三个引擎都提供了很好的支持度,一般来说,如果原先使用过Cocos实现过APP端游戏要移植到微信小游戏端来的,使用Cocos是最好的选择,如果是从头开发一款小游戏,那还是在Egret和Laya里选择一款吧!
3. Laya 环境搭建
前面讲了那么多,都还只是前戏,只是为了大家对游戏的开发有个初步的了解,从这一节开始我们就进入正题了。
到 [https://www.layabox.com/](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”。
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
<!--启动类添加到这里-->
<!--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编辑器的资源面板最下方找找到刷新按钮![image](https://upload-images.jianshu.io/upload_images/5341773-1d92c3cd7c3dc5a2?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
,新增资源图片后,一定要记得点下刷新,否则资源面板的内容不会自动刷新。
只要是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一样,让伞兵从天下掉下来,怎么实现?接着看动画部分吧!
6. 动画
6.1 创建伞兵对象
在src目录下创建一个新目录role,用来存放游戏中角色。 在role里创建一个伞兵Soldier.ts对象文件。
module role{
export class Soldier extends Laya.Sprite{
constructor(){
super();
this.init();
}
init():void{
var img:Laya.Sprite = new Laya.Sprite();
img.graphics.drawTexture(Laya.loader.getRes("demo/soldier.png"),0,0,100,86);
this.addChild(img);
}
}
}
修改GamePage.ts,把伞兵加入到游戏主画面中去,重点看renderSoldier()
module view{
export class GamePage extends ui.GamePageUI{
private soldier:role.Soldier;
constructor(){
super();
this.init();
}
init():void{
this.renderSoldier();
}
renderSoldier():void{
this.soldier= new role.Soldier();
this.addChild(this.soldier);
}
}
}
运行起来看下,发现游戏主画面上,已经多了一个伞兵(请忽略我的很烂的抠图,手动捂脸^_~ )
6.2 让伞兵掉下来
做过前端的应该都明白,伞兵掉下来,就是要启动一个定时器,不断修改伞兵的Y坐标+1,移动伞兵图片的位置。原理都知道,但是如何实现呢?
一般定时器有两种:
setInterval:基于用户指定时间
requestAnimationFrame :基于浏览器帧能力
相比起来,requestAnimationFrame 性能更高,更适合做动画。但是在游戏里会有很多地方都用到定时器,如何管理那么多定时器,是非常让人头疼的事情。所以Laya也提供了自己的定时器的相关实现:Laya.timer 来简化定时器的使用,这个定时器同样是基于帧率的,我们来看看这个怎么用。
修改GamePage如下,重点看Laya.timer.frameLoop
module view{
export class GamePage extends ui.GamePageUI{
private soldier:role.Soldier;
constructor(){
super();
this.init();
}
init():void{
this.renderSoldier();
//创建定时器
Laya.timer.frameLoop(1,this,this.onLoop);
}
renderSoldier():void{
this.soldier= new role.Soldier();
this.addChild(this.soldier);
}
onLoop():void{
//让伞兵45度下降
this.soldier.y=this.soldier.y+1;
this.soldier.x=this.soldier.x+1;
}
}
}
来看下效果,看起来还不错
7. 碰撞
7.1 增加炮弹
下一步,就改是大炮打伞兵了,当然首先得给大炮创建一个炮弹。
Ball.ts
module role{
export class Ball extends Laya.Sprite{
constructor(){
super();
this.init();
}
init():void{
var img:Laya.Sprite = new Laya.Sprite();
img.graphics.drawTexture(Laya.loader.getRes("demo/ball.png"),0,0,45,54);
this.addChild(img);
}
}
}
在GamePage上添加炮弹
renderBall():void{
this.ball= new role.Ball();
this.ball.pos(162,540);
this.addChild(this.ball);
}
嗯,炮弹添加成功,不过,貌似有点问题,怎么炮弹显示层级在大炮上面了?似乎有点难看?
7.2 调整Sprite层级
还记得前端世界里神奇的z-index吗? Laya也有,叫zOrder。调整zOrder的数值,可以调节Sprite的层次(脱了马甲,我一样认识你,^_^)
把渲染炮弹部分改一下层级:
renderBall():void{
this.ball= new role.Ball();
this.ball.pos(162,540);
this.pao.zOrder=10; //调高原先大炮的显示层级
this.addChild(this.ball);
}
这次炮弹躲在大炮后面去了,一会儿再让他出来吧!
7.3 点击大炮发射炮弹事件
炮弹向上飞,就和伞兵向下掉一样,在帧循环里不断修改y值就可以。但是这次,我们要响应事件了,必须点击大炮,触发点击事件后,才发射炮弹。
再次修改GamePage.ts,这次的重点是多了 this.pao.on(Laya.Event.MOUSE_DOWN,this,this.onMouseDown); 这个事件监听.
module view{ export class GamePage extends ui.GamePageUI{
private soldier:role.Soldier;
private ball:role.Ball;
private isSendBall:boolean=false;
constructor(){
super();
this.init();
}
init():void{
this.renderSoldier();
this.renderBall();
//给大炮增加事件监听
this.pao.on(Laya.Event.MOUSE_DOWN,this,this.onMouseDown);
//创建定时器
Laya.timer.frameLoop(1,this,this.onLoop);
}
renderSoldier():void{
this.soldier= new role.Soldier();
this.addChild(this.soldier);
}
renderBall():void{
his.ball= new role.Ball();
this.ball.pos(162,540);
this.pao.zOrder=10;
this.addChild(this.ball);
}
onMouseDown():void{
this.isSendBall=true;
}
onLoop():void{
//让伞兵45度下降
this.soldier.y=this.soldier.y+1;
this.soldier.x=this.soldier.x+1;
//如果是发射炮弹状态,炮弹向上发射
if (this.isSendBall){
this.ball.y=this.ball.y-3;
}
}
}
}
在运行一下看看:
到目前为止,还进行得不错,就差击落伞兵了,可怜的伞兵,你的死期就要到了,还差一个碰撞了。
7.4 炮弹与伞兵的碰撞
碰撞算法常见的有以下这些:
矩形碰撞:矩形图片接触碰撞,计算性能最快,但是如果图像并不近似矩形的时候,准确度就不高了。
圆形碰撞:和矩形类似,比如炮弹就是圆的,用圆形检测,更适合真实情况。
多矩形碰撞:如果图像相对比较复杂,可以拆分为多个矩形,在准确性和性能方面取得平衡。
像素检测碰撞:如果需要非常精确的碰撞,就要使用像素检测了,这个性能相对就比较低了。
在Laya里,对于矩形碰撞检测,提供了Rectangle.intersection()方法,可以非常方便的进行矩形检测。
继续修改GamePage.ts
gameOver():void{
Laya.timer.clear(this,this.onLoop); //停止游戏帧定时器
this.renderBoom(); //显示爆炸图片
this.removeChild(this.soldier); //删除伞兵
this.removeChild(this.ball); //删除炮弹
}
onLoop():void{
//让伞兵45度下降
this.soldier.y=this.soldier.y+1;
this.soldier.x=this.soldier.x+1;
//如果是发射炮弹状态,这炮弹向上发射
if (this.isSendBall){ this.ball.y=this.ball.y-3;
//使用矩形碰撞判断,如果炮弹和伞兵碰撞,则游戏结束
if (this.ball.getBounds().intersection(this.soldier.getBounds())){
this.gameOver();
}
}
}
再来看下效果:
Boom,伞兵成功被大炮打中,“绝地求死”完美收工!
8. Laya的性能优化
8.1 性能监测工具
Laya已经内置了性能监测工具,只要初始化后执行Laya.Stat.show();就可以打开
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;
//打开性能监测面板
Laya.Stat.show();
}
上面清楚的显示了目前的FPS、Sprite的数量、DrawCall 、内存消耗等,我们优化的目标就是把这些值降低下来。
8.2 优化手段
减少Sprite的数量
不可见区域的Sprite及时移除
静态内容使用cacheAs=bitmap降低DrawCall
使用Laya.Pool管理对象,减少重复创建的性能消耗
对象无用时,及时销毁
定时器及时销毁
。。。
具体的优化手段有很多,大家可以在具体的业务开发中不断的总结提炼。
9. 发布到微信小游戏
讲了那么多的Laya,说好的微信小游戏呢? 不要急,这就来了,Laya生成的代码,可以非常方便的发布到微信小游戏。
点击 ![image](https://upload-images.jianshu.io/upload_images/5341773-efdec7af2975ed9f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
进入发布界面,在发布平台选择“微信小游戏”,此时生成可以在微信开发者工具下运行的release/wxgame版本.
使用微信开发者工具打开,已经可以完美运行了。而且我们发现laya把我们刚才写的代码,和Laya的核心库一起,都被打包成一个code.js了。
[ 微信开发者工具 ]
10. 开发环境兼容
可是,作为微信环境下的游戏,因为code.js是laya自动生成的,我们开发还是必须在laya的开发环境下,但是laya并不支持微信的接口调试,那我们可以在Laya里判断开发环境吗?
当然可以,用Laya.Browser.onWeiXin 就可以判断了,比如:
if (Laya.Browser.onWeiXin) {
let wx=Laya.Browser.window.wx;
//执行微信的API逻辑.....
}
只是调试起来就有点蛋疼了,得Laya里写好,发布到release/wxgame,再在微信开发者工具里调试。
=总结=
总体来说,Laya入门还是比较简单的,虽然官方也做了很多文档,也有做视频教程,但是感觉资料还是有点缺,这次自己研究Laya的历程分享出来.