开发技术论坛

 找回密码
 立即注册
搜索
查看: 15569|回复: 0

如何重绘「江南百景图」?近300页 PPT 免费分享!

[复制链接]
发表于 2021-9-22 14:41:51 | 显示全部楼层 |阅读模式
游戏程序
平台类型:  
程序设计:  
编程语言:  
引擎/SDK:  
去年,古风模拟经营类手游《江南百景图》成功破圈,成为年度现象级爆款。如何将它搬到小游戏平台?是转换还是重写?使用哪些技术方案,能在包体大小仅为原版1/20的同时,达到与 App 版相当的游戏体验?椰岛小游戏研发负责人大城小胖,带着他近300页的 PPT,在 Cocos 的两次线下活动中做了全面的技术分享。

微信图片_20210922142523.jpg

转换 or 重写

《江南百景图》App 版游戏包大小有 600+M,上线前期还有部分用户反映游戏运行时手机发热严重。而小游戏版在经过立项选型后,决定使用 Cocos Creator 重写,仅用了1天就做出了 Demo。经过4个月的优化,我们最后将包体压缩到 30M 左右,同时保证游戏体验与 App 版相当。

微信图片_20210922142535.jpg

优化的过程中,我们也做了以下工作,其中 代码 部分需要重新设计和编写。

微信图片_20210922142539.jpg

渲染优化

原生版本的《江南百景图》移植到小游戏首先需要解决的就是 耗电高、易发烫、Draw Call 高等问题。

合批

合批是降低 Draw Call 最快也是最有效的方式。优化同样的 Texture,将多张的图片合并到一张图集上,这样不论要生成多少张不同的图片,都不会打断合批渲染,Draw Call 也就降低下来了。

但是《江南百景图》的资源非常多,每个玩家使用资源的顺序也不尽相同,如果玩家使用的资源分别在不同的图集上,还是会导致合批渲染被打断,产生 Draw Call。因此,针对这一情况,我们采用了 Multi-Texture 的方式进行了优化,其原理是将传统的判断是否在同一张图集,转换为判断是否在 同一批图集,这样就大大减少了 Draw Call 产生。

微信图片_20210922142542.jpg

另外,通过 gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) 这个指令,可以知道在一台设备中 Shader 最多支持几张图集。测试发现目前 90% 以上的手机至少支持8张,因此我们将批图集的数量设置为8张。因为一个批次有 8 张图集,所以我们是通过这个 idx 判断某张图用的哪个图集,代码也很简单。

微信图片_20210922142546.jpg

动态合图

小游戏版本采用了 Cocos 的动态合图机制,这样在 CDN 下载的图片也能进行合图。而为了提高合图的效率,避免浪费空间,我们会将长度或者宽度特别大的图片进行裁剪。

微信图片_20210922142550.jpg

例如左图中的旗杆,由于图片太长,在动态合图时会导致空间浪费,因此我们将这张旗杆的图片裁剪成两张,如右图所示,再在项目中进行拼接处理。

采用同一个材质资源

在《江南百景图》中,玩家移动地图时,原本在显示范围外的图片将从水墨色变为彩色。

微信图片_20210922142555.gif

传统的方案是改变图片材质,当地图移动到要显示的节点时,节点一个一个地进行材质的切换,达到一个 “淡入淡出” 的效果。但是在项目中尝试之后,我们发现这样会导致 Draw Call 上升,而且拖拽地图又是一个很频繁的操作,游戏中实际效果较差。

因此在这里我们将所有城市物体资源,无论是人物还是建筑、常态还是淡入状态,都用统一的 Material、并使用顶点数据传递“时间参数”,以此节约性能消耗,最终达到所有建筑和人物的创建、移动、销毁等全都只需要一个材质就能够完成。

微信图片_20210922142600.jpg

很多人会觉得一个普通的图片也用这么复杂的方案,会影响性能,导致性能变差。但是实际测试效果并不差,这也告诉我们,在游戏开发中还是要以实践为准,不能想当然。

优化 Shader 的输入数据

由于《江南百景图》的图片资源中不会用到 Color 这个属性,因此在材质中,我们将原有的 Color 数据去除掉。

微信图片_20210922142605.jpg

下图是一个正常的顶点数据:

微信图片_20210922142608.jpg

接下来将原有的 Color 数据去除掉,用来存放项目中所需要的其它信息,这样可以减少 CPU 与 GPU 互相传输的数据量。

微信图片_20210922142615.jpg

层级规划

我们将不同的类型的资源,分别放置在对应的层级中。《江南百景图》共分了13个层级,下图只展示了部分比较重要的层级:

微信图片_20210922142618.jpg

其中比较有意思的是旗帜层。旗帜是《江南百景图》中的一个常见元素,但因为项目实际技术限制,无法将一个旗帜制作在一个完整龙骨动画中,如果强行放在一起,就会导致在渲染到旗帜的时候出现断批。我们采用了 动态组织层级关系 的方式来解决这个问题。例如这是一个原来的旗帜预制体:

微信图片_20210922142622.jpg

采用 动态组织层级关系 的方式,将旗杆与旗面拆开,旗杆放在下面的普通建筑物层,旗面则单独分为一层旗帜层放在上层,这样就很好地避免了渲染时一直被打断合批的情况。

微信图片_20210922142627.jpg

UI 渲染优化

UI 部分我们没有使用动态合图或者 MultiTexture,动态合图我们留给了游戏中的人物和建筑、而没有使用 MultiTexture 主要是开发成本的原因。但在我们的优化下,现在游戏的 Draw Call 可以降得很低。

微信图片_20210922142631.jpg

UI 方面我们也是做了分层,比如下面左边的图上我们的 button 层,里面都是按钮部分,右边是我们的标签牌层级。这样我们就可以根据功能区去划分图集,然后和游戏里的层级对应起来,而不会打断合批。

微信图片_20210922142652.jpg

自定义引擎

Cocos 是个开源引擎,我们可以根据项目的实际需要,对引擎进行定制、修改,从而达到更好的效果。

增强 TiledMap

我们在 Cocos Creator 原有的 TiledMap 组件的基础上,拓展了新的功能,下图是 Cocos 自带的组件。

微信图片_20210922142657.jpg

这里就不详细说了,有兴趣的可以去官方文档查阅,我们主要来说一下经过拓展的新功能。

微信图片_20210922142700.jpg

1.  Diamond Tile:游戏中使用了很多 TiledMap 中的图块菱形方块, 但是引擎默认的传递方式是矩形,这样就会造成数据浪费和冗余。

微信图片_20210922142704.jpg

这些图片首先都是 规则的菱形,所以很简单,直接 根据宽高进行进行计算。

微信图片_20210922142708.jpg

将菱形周围多余的部分切割,这样很明显图片大小减少了一半,这里注意一下非标准图形就不能这么用了。

2.  Share Culling:《江南百景图》共有三层 TiledMap 地图层,勾选时 将只对 TiledMap 的第一个地图层进行处理判断可视区域的范围,而其他的地图层将直接照搬第一个地图层的处理结果,这样能够节约不少性能。

3.  With Color:如果不需要颜色数据就可以勾选,减少数据量的传输。

将道路转为 Tile

游戏中的道路是不需要进行淡入淡出效果的,如果当作普通建筑物资源来用之前的材质进行渲染,会消耗相当多的性能。因此我们将道路作为 Tile Map 地图的一部分,让道路不需要用之前提到的材质进行渲染。

还有一个小细节,在 Tiled Map Editor 中设置的宽高,与实际项目中使用是无关的,因此在生成的时候可以将地图块按照实际项目需求进行缩小,减少资源使用。

微信图片_20210922142713.jpg

资源压缩

将一个原版 600+M 的游戏压缩到最终的 30M 左右,资源的压缩工作必不可少。我们需要将游戏资源进行合理的压缩,使其更加适合小游戏运行,并且不影响游戏最终的显示效果。

图片缩放

对不同类型、不同清晰度的资源,我们可以设置不同的缩放比例。我们将大部分的建筑缩放到原来的 0.65 倍,背景中的山川则被缩放到原来的 0.3 倍。另外,就算是相同位置上使用的人物立绘,由于每个人物的自身和背景的颜色、精度不同,也都可以给它们设置不同的缩放比例。

微信图片_20210922142717.jpg

于是我们将所有 Sprite 组件采用 Custom 模式,可以自由控制比例。不同的图片使用差异化配置,设置不同的缩放比例,用脚本控制缩放比例,这样便可以打包出任意画质和体积的各种版本,并且还提升了动态合图的利用率和部分性能。

微信图片_20210922142722.jpg

微信图片_20210922142728.jpg

图片减色

综合比较了大家比较熟知的 tinypng 和 pngquant 两种工具之后,项目最终选择使用 pngquant 对 PNG 图片进行批量压缩。pngquant 可以自定义压缩品质,而且 pngquant 开源,容易维护,风险可控。pngquant 也提供像 ImageAlpha 这样的工具,可以实时查看图片减色后的效果,方便调整参数。

pngquant 地址:

https://pngquant.org/

需要注意的是,由于 Cocos 会进行合图处理,如果对 Build 前的图片做压缩,合图时前期的一些压缩工作可能就此无效化,所以我们要对 Build 后的图片 做压缩处理。

另外我们也建议程序多了解一下图片格式以及其原理。不是所有图片都要使用 PNG 格式,也会有使用 JPG 的情况。

场景剔除

这部分我们的需求是 只渲染可视物体。那么用什么方法确定哪些物体是可见的呢?最开始我们使用了四叉树,但是在 JS 语言中的效果并不好。所以我们给地图划分格子,Grid 的单元格大小要适中,但单元格的边长应为 2的整次幂,便于利用 位运算 提升性能。

如下图所示,红框就是镜头,所以需要渲染的也就是这个红框里出现的格子。然后我们再根据建筑物的坐标、大小去进行计算,判断建筑在哪一行哪一列的格子里,从而确定该建筑物是否是需要被渲染的物体。

微信图片_20210922142732.jpg

微信图片_20210922142736.jpg

这是一段简单的检测函数 大家可以根据自己的项目需求去进行扩展。

微信图片_20210922142740.jpg

除此之外,为了防止特殊情况出现,判断的可视范围需要比实际范围更大一些。

微信图片_20210922142744.jpg

寻路

《江南百景图》使用的寻路算法,有针对单源单点的 A* 和单源多点的 Dijkstra。但这里我们要讲的不是寻路算法,而是在游戏中的用法优化。

针对地图很大、建筑物和人物都很多的情况下,这些算法一起执行就会很损耗性能。所以我们用了 分时寻路,就是把寻路过程由一帧分到若干帧去进行计算,这样就不会在某一个时间段集中进行大量运算,对游戏性能也不会有太大的影响。

微信图片_20210922142748.jpg

除此之外我们还在游戏里做了一个大胆的优化,就是统一管理寻路任务,同一时间只为一个角色服务。也许有人会问,那岂不是一个角色在哪里走、其他对象都在那边等着?其实真正在游戏里不会有这种奇怪的表现。首先每个角色寻路的起始和结束时间都不一样,再者这个同一时间是非常短的,就等于把角色寻路分配到了不同帧里,交替进行执行。

再谈性能

模糊特效

玩家在打开《江南百景图》的任意界面时,游戏的背景需要做模糊处理,而背景中的人物动画等仍需要正常播放。

微信图片_20210922142752.jpg

在经过一系列的研究(可参考 PPT 中资料)后,我们选择了一个较好的方案,将场景渲染到一个小的 RenderTexture 上,然后将其通过 Kawase 模糊后再放大 显示,如下图所示。

微信图片_20210922142756.jpg

RenderTexture 池

在小游戏或 Web 端 创建 RenderTexture 时,比较损耗性能。所以我们在游戏中使用完 RenderTexture 后,不是直接销毁,而是将其放在一个 缓存池 中,下次从缓存池中调用符合要求的 RenderTexture 即可。

点击检测

《江南百景图》中有很多建筑物,而在用户点击时,并非简单地通过地形上的块做判断,而是给每个建筑物画了一个 多边形检测区域。但是建筑物是移动的,如果 多边形检测区域 也随之移动,从性能和逻辑上都不是好的处理方式。

微信图片_20210922142759.jpg

于是在实际操作中,我们让建筑物移动,而对应的 多边形检测区域 不做移动,并将其设置在原点坐标上。用户点击操作时,将点击的坐标减去建筑物相对原点的坐标,就可以进行点击检测了。同理如果建筑物是反转状态,可以将点击坐标进行镜像,而 多边形检测区域 仍然可以不做调整。类似还有其他情况,大家也可以去了解一下各情况下对多边形的处理方式。

数组排序

数组排序是大家容易忽略的一个优化模块,Array.sort() 这样的 快速排序 算法,更适用于混乱无章的数据。而在《江南百景图》中,每帧都会对场景中的人物和建筑物进行排序,而连续的两帧之间差异不会很大,也就是 相对有序 的数据,而这更适合使用 插入排序 算法。

微信图片_20210922142803.jpg

其他优化

「阅后即焚」

游戏中存在一些低频显示的大图,例如进入游戏时的公告、抽到的卡片等,玩家在游戏中看一遍就不会再出现了,对于这一类我们用了“阅后即焚”的思路。

微信图片_20210922142808.jpg

像这些大图,我们通常先从远程服务器下载到本地缓存,产生 Image 对象,还有cc.Texture2D、renderer.Texture2D。

微信图片_20210922142812.jpg

我们通过伪代码来简单讲解一下。加载图片时,将图片添加到我们自己创建的回收用工具类 TextureRecycle 中。

微信图片_20210922142815.jpg

视图关闭时,通过工具类回收这些图片。

微信图片_20210922142818.jpg

在图片的回收阶段中,就可以将以上所有用到的对象都清理干净了。

微信图片_20210922142825.jpg

构建优化

在构建发布流程中,项目使用了大量的自动化脚本来优化构建流程。包括 全平台构建、上传游戏平台、资源预处理和后处理、CDN 同步和版本控制和二次混淆加密 等。但成也脚本败也脚本,过长的构建时间也造成了不少困扰,因此我们也需要做一些额外优化。

Cocos 新版本添加了一个第三方开源压缩工具 Sharp,压缩级别是0-9,数值越大压缩越久,Cocos 的默认参数是 6。由于我们已经进行过 图片减色 处理,因此我们将参数改为 0,这样就能减少很多构建的时间。

微信图片_20210922142829.jpg

而各平台构建时间总是格外漫长,原因是在每次平台构建时,Creator 都要重新生成对应的平台图集。找到原因后,我们在每次构建前,将对应目录中的 info.json 中的 actualPlatform 参数先修改为 对应的平台名称 再打包,这个改动使我们的构建时间由之前的 15 分钟缩短到 10 分钟左右,提升了 30% 效率。

微信图片_20210922142834.jpg

微信图片_20210922142837.jpg

在不懈的优化下,我们看到在现场演示时,这个用于官方演示游戏的高级账号,在游戏场景人物都很丰富的情况下,仍然只有 6个 Draw Call。

微信图片_20210922142841.jpg

文/大城小胖
来源: COCOS
原文:https://mp.weixin.qq.com/s/4s-xK0pAP972tDxJ3VchqA


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发技术论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2021-12-1 09:02

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表