前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2D MMO中角色动画的优化总结

2D MMO中角色动画的优化总结

作者头像
张晓衡
发布2019-09-11 16:32:38
1.2K0
发布2019-09-11 16:32:38
举报

在深圳Cocos沙龙上,有幸结识了社区中大名顶顶的Colin,Shawn在社区论坛上第一次看到Colin的团队用CocosCreator制作的《热血暗黑》时就被深深地震撼到了!更为重要的是,Colin将他的技术心得和宝贵开发经验写成文字,每一篇分享都是满满的干货。而且幸运的是Shawn得到Colin的授权许可,将他的文章散播到奎特尔星球,我们一起欣赏一起成长!

2D MMO中角色动画的优化总结

1

概述

我们的项目是传统的2D MMO,即人物动画是以图片帧的方式表现的,一个角色大约有8个动作,1个动作有8个方向,1个方向约有10到20帧的图片。这样算起来一个角色由近千张图片帧组成。这样的量对内存和性能都有很高的要求。从立项到目前经历了很多次优化,终于在流畅度和内存占用上达到比较理想的状态。

不知道群里有多少人也在用Creator做类似的MMO手游,我在这里把优化的经历大概写出来供大家参考,也许你们有更好的优化方案,都欢迎一起讨论。

2

最初的实现方案

最初为了快速实现效果,采有很直接的方式:

  • 一个动作一张图集(Plist),8个动作则有8个图集。
  • 每个动作只包含5个方向的图片帧,另外3个方向通过翻转实现。
  • 动画则用Creator提供的动画剪辑(AnimationClip)来实现。

由于一个AnimationClip只能播放一个方向的动画,那么一个角色就需要8*8=64个AnimationClip,如果每个动画剪辑都要在编辑器中编辑,估计美工人员会先晕倒了。

幸好.anim文件是json格式,很容易理解它的含义,于是我们用Python写了一个导出脚本,美术只需要提供角色的所有散图,脚本调用TexturePacker先合成图集,再动态生成anim文件,最后生成一个prefab。

这个prefab就代表一个角色外形。程序的使用就很简单,首次使用先加载Prefab并实例化出结点。从结点取出Animation组件并调用播放接口即可。当角色释放时,把结点收回存到NodePool去,以备下次使用。

3

显示易见的性能问题

从上面的实现看,其实不用Profile也很容易知道会有性能问题,虽然里面使用了NodePool缓存创建过的角色外形。但首次加载这些资源会很卡:一个角色的图集和动画剪辑加起来接近100个文件,想想那个IO压力都觉得蛋疼。

在PC的Web端,这个性能问题没有暴露出来;在安卓甚至苹果机上,一旦旁边有角色进场景,马上就能感受到卡顿,有时甚至能卡上1到2秒。因为我们做的MMO,场景中的玩家进进出出是很平常的,这样的卡顿是不能接受的。

4

优化之一:去掉动画剪裁

首先能想的是去掉anim文件,角色动画其实很简单,只是一些序列帧的播放,并没有用到移动缩放这些动画类型。用anim文件来描述动画有点浪费了,完全可以用另一个简单的Json文件记录动作的信息,比如这个角色有几个动作,每个动作有几个方向,每个方向有几个帧,只要这些信息就够了,类似这样:

代码语言:javascript
复制
{
	"run": [5, 20, 0.033],
	"idle": [5, 4, 0.1],
}

而图集中图片帧的名字,需要有一点规则,即可以通过动作名方向值帧索引这些合成。然后就可以找到对应的图片帧了。

在程序中,仍然是使用AnimationClip来实现动画,只不过它是动态创建的,使用引擎提供的一个API:cc.AnimationClip.createWithSpriteFrames,具体可以参考文档。AnimationClip不用一次性创建出来,可以在播放某个动画时再创建,这样创建的消耗就平摊出来了。我们实现了一个AnimControler组件,通过它来播放动画,而延迟创建动画剪辑这些就顺理成章就封装到组件里面了,外部逻辑不用关心。

这一轮优化下来,一个角色只有动作图集和一个Json文件,去掉了大量的anim文件。包体也因此少了30M大小,算是一个额外好处。

5

优化之二:去掉Plist文件

本以为经过上面的优化,手机上的卡顿应该解决了,但是很不幸,从表现上看只解决了1/3,仍然有0.5到1秒多的卡顿。接下来就各种脑洞和调试了,甚至怀疑加载图片是不是用同步的方式,后来问了@panda,以及自己查看引擎代码,终于确认用的是异步方式。这样的话不应该呀,难道解析Plist文件用了这么长的时间?

后来在调试原生版本时,发现Plist文件原来会按图片帧打散,一个帧就一个json文件,里面描述了帧的位置偏移等信息。想想我们一个角色有近千个帧图片,当加载Plist时,引擎要把所有的Json文件加载进来。这比加载anim文件可蛋疼多了。

既然发现了瓶颈所在,解决方案就自然而然了:去掉Plist文件,只留图片。那么怎么知道每一帧的信息呢,答案还是从Plist中找。我又用万能的Python写了一个工具,把Plist的帧信息提取到上面提到的json文件中去,然后把Plist文件删除。

在程序中,我不再加载cc.SpriteAtlas,而是直接加载cc.Texture2D,然后当创建cc.AnimationClip时,我需要从配置中找到cc.SpriteFrame的纹理信息,然后用:

代码语言:javascript
复制
new cc.SpriteFrame.create([Texture2D ] [rect ] [rotated ] [offset ] [originalSize ] )

动态创建帧,把这个帧传入动画剪裁。后面的播放就和原来的一样了。

经过这一次的优化,基本上把IO的负担都消除了,我们在IOS上测试了一下,果然如丝般顺滑,进打怪地图会刷出大量的怪物,也感觉不到卡顿。在安卓上也基本可以接受,在大量角色进来时会有很微小的卡,时间不会超过100毫秒,而因为有了缓存,后面也是顺滑的。至于安卓的这个微卡,我归结为两个:

  • 图片加载进来后解析成纹理的过程。png文件涉及到解压,反编码成raw形式。
  • json文件的加载和解析。json的加载其实是同步的,而解析成JS对象也是需要时间的。

上面这两个其实也和安卓的性能相关。

6

优化之三:压缩角色图片

我们的角色图片用的是png,如果把散图合成一张大图,大概接近于2048*2048,即一个角色加载到内存,会消耗10M以上的内存。再加上角色是可以换装的,一个场景同时出现10个以上的外形是很平常的。光角色这一块就会消耗掉100多M的内存。还是挺让人着急的。

考虑到我们的游戏类型是偏写实的,画面的彩色相对会复杂一些,就决定用PVR,ETC这些格式来看看效果。后面在构建过程中,加入了纹理压缩的流程,IOS用PVR4,安卓用ETC+Alpha,最后的效果完全可以接受,在手机的小屏幕上看不出太大的区别。

PVR4使内存减少了8倍,ETC+Alpha则减少了4倍,这个优化量太可观了,而且附加的好处也很多:

  • 加上压缩的同时用GZip对图片再进行压缩,图片大小比原来的PNG减了3倍,使得程序包又减少了几十M。
  • 由于压缩纹理直接传给GPU,加载纹理只有一个反解压GZip的过程,加载过程也必然大大加快。

7

最后的期望

都说内存,速度,和发热是手游优化的三座大山。但引擎是不大可能完全帮你解决这些问题的,最重要的还是要根据引擎的特点,自己研究出合适项目的优化方案。引擎只需要在底层提供好机制,具体策略由项目去考虑。

对于Creator后面的进化,很希望这几个方向得到加强:

  • JS引擎的效率,Web端的V8已经足够优秀,但原生的SpiderMonkey显然差了很多,官方似乎也在提升原生引擎的版本,期望后续的消息。
  • 类似Unity的AssetBundle,有了这个机制,就可以对资源进行分包,这对大游戏的分包发布很有好处。
  • 工程脚本的分割,游戏里面有大量的策划配置文件,有些动辄上万行,整个工程的脚本合并起来会达到好几M,这对热更新非常不友好的。如果能把脚本分割,每个脚本可以打一个Tag,相同Tag的脚本会合并,这样项目就可以根据自己的情况对脚本进行分离处理了。

最后给大家欣赏一下Colin与他的团队正在开发的游戏《热血暗黑》中的游戏图截:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Creator星球游戏开发社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档