一个Cocos Creator小游戏的开发周记(2021年1月30日)

一个Cocos Creator小游戏的开发周记(2021年1月30日)

8天前,我在GitHub开了一个私人仓库,把Cocos Creator的Hello Wolrd模板提交了上去。6天前晚上八点,我与我们开发小组的成员cyb和lby进行了一个小时的视频会议,大家简单陈述了自己的想法,原定开发一个太空题材的,控制地球:earth_asia:利用引力弹弓效应在不同天体之间移动,类似微信跳一跳的一个小游戏。游戏的程序开发主要由我负责,随着这几天游戏的不断迭代,游戏效果、内容也逐渐丰富,游戏项目和其代码有越来越多的细节实现需要注意,所以我特地写下本文,去记录一些我在开发过程中,游戏效果实现的方法、原理,以及一些需要注意的方面。囿于时间匆忙,内容可能会略显晦涩和粗糙,见谅。

TypeScript和CoCos Creator的简单介绍

首先,这个Cocos Creator小游戏的程序部分是用TypeScript编写的,原因在于TypeScript是一个比较严谨的语言,它是由微软开发的,拥有强类型并且比JavaScript的功能更多,被称作JavaScript的超集。TypeScript可以被编译成任何版本的JavaScript,所以它完全可以代替JavaScript。正是由于TypeScript的强类型,TypeScript代码的编写不容易出现隐藏的错误,因为IDE会进行非常好的错误提示,这样能够大大提升代码的生产力。另外,用TypeScript编写游戏脚本,IDE会进行代码提示,一个对象是什么类型、拥有哪些成员变量和方法、这些方法的作用是什么,这些IDE都会在代码编写过程中给予提示,省去了频繁查找Cocos Creator官方文档的麻烦。然而上述在软件开发过程中IDE提供的比较基本的功能,由于JavaScript的弱类型而都无法提供。

在Cocos Creator里面,每个实体都是节点(cc.Node),而这些节点的结构都是树状的,我个人觉得这个设计特别好,不仅对于这个游戏引擎来说能够提高某些算法的效率,而且对于游戏开发者来说能够省去不少功夫。这里要提的是,在前端开发中,网页的DOM(文档对象模型)也是树状的,正是因为这个数据结构的优越性,网页才能被快速的渲染和更新。

正是由于Cocos Creaor使用了这样的数据结构,再加上它的游戏脚本是前端里的JavaScript或者TypeScript,所以Cocos Creaor的游戏能够被打包到几乎所有平台(这包括Windows、Android、Linux、Mac OS、Web以及许多第三方平台例如微信小游戏、百度小游戏等)。简单点来说,只要能够打开浏览器上网的平台,CoCos Creator都能够一键打包并发布到这个平台。

接下来,我会把游戏开发里面的一些技术细节一一列举出来

游戏性能优化

随着游戏的不断迭代,我实现了越来越多的效果,同时游戏主逻辑也越来越复杂,再加上要渲染的实体越来越多,游戏在移动端的帧率有下降趋势(实际上,游戏在电脑网页上能够200满帧运行,然而移动端的性能比较差,可能会不足60帧或者有略微掉帧情况)。

我们先看看游戏在调试模式下的帧率信息

image

其中FPS就是Frame per Second(每秒的帧数),一般来说帧率在60左右就足够了,实际上Cocos Creator将游戏锁定在了60帧左右,通过修改程序或者调试模式下更改游戏画面上方的配置,能提升游戏帧率上限。

image

现在我们看到,游戏其实是相当流畅的。然而电脑上的流畅不代表移动端的流畅,经过我的一些调试,我发现其实帧率能够在170以上,移动端就可以比较流畅了。实际上,上方图片还隐藏了很多信息,下面我一一举出。

  • Frame Time 游戏运行一帧所需时间
  • FPS 每秒帧数
  • Draw Call CPU打包发送给GPU去渲染的打包数量
  • Game Logic 运行在CPU上的游戏处理逻辑每帧所需时间
  • Renderer GPU渲染一帧画面所需时间
  • WebGL 是否启用WebGL

实际上,Frame Time = Game Logic + Renderer,从上面的数据你也可以看出来。而FPS与Frame Time成反比,所以我们想要提高游戏的帧率,必须从Game Logic 和 Renderer下手。

优化Game Logic
使用缓存、算法

首先,在Game Logic方面,其实是游戏的脚本运行在CPU上的耗时,我通过缓存游戏对象,自动销毁无用的节点以及挂载并运行在其上的脚本来提升游戏逻辑方面的性能。

在游戏加载时,就把必要的序列化对象全部加载,并且缓存到一个列表里面

image

每次获取刚体对象都要调用node.getComponent方法,不如提前缓存了

image

调用过的方法尽量都缓存下来下次使用,这样就不需要重复调用方法,大大提高效率。另外,其它的一些算法也可以来优化游戏性能,例如检测并作用一个范围内的碰撞体,可以使用四叉树算法(如果是三维则用八叉树算法)。

使用Prefab

除此之外,游戏中经常出现的或者需要频繁重复生成的节点,利用Cocos Creator的序列化功能,提前把这个节点序列化,生成一个Prefab文件,在需要使用时可以通过 cc.instantiate 方法进行反序列化,这样就可以更加高效的获得一个节点对象。需要注意的是秒如果这个prefab需要反复多次频繁反序列化,需要将这个prefab设置为这样

image

优化 Renderer
自动图集

使用自动图集能够让Cocos Creator自动把一系列碎图打包为整图,使用自动图集的优势非常大!一张整图相比碎图来说网络请求次数更少,或者不需要重复进行硬盘读写,从这一方面来说,使用自动图集能够减轻CPU、影片、网络等方面的压力。更为重要的是,使用整图可以让GPU一次渲染完成,避免CPU重复调用GPU进行渲染,这样可以降低Draw Call,而Draw Call其实非常大的影响了游戏图像的渲染效率,所以使用自动图集能够提升渲染效率。

减少节点数

减少节点数同样是通过降低Draw Call来提升游戏性能,所以当一个节点在地图外不可见,或者以及无用,则考虑将这个节点自动销毁。

优化纹理(Materials和Effects)

每个Materials可以挂载一个Effects,而Effects说白了就算着色器(Shader),着色器分为顶点着色器和片元着色器,它们是直接运行在GPU上的程序,能够在GPU层面直接对图像进行操作,效率非常高。在合适的时候使用着色器来实现效果,能够极大的提升游戏性能。然而,着色器虽然跑的很卡,也要适可而止,移动端的GPU性能远低于电脑,在电脑上高清无码满帧,可能在手机上就卡成PPT,所以复杂的需要大量运算的着色器可能会拖慢游戏图像的渲染速度,使游戏性能大大降低。例如下面这个着色器,在电脑上满帧,手机上会卡成PPT。

游戏的动效和物理效果

非线性动效

非线性动效相比线性的、匀速的动效在视觉观感上会好很多,这在现在的手机系统动画以及一些优秀的PPT内应用非常广泛。这个游戏在很多方面多次使用了一种非线性函数实现动效,以达到流畅、柔和的视觉效果。

这个非线性函数其实就是这个微分方程 y'=y(1-y) 的解 ,这是一个非常有用的非线性函数,在各种领域作用广泛。例如开源的Python数学可视化引擎Mainm里就有它的身影,最近开源的动画引擎movy.js也有它的身影,除此之外这个魔幻的函数还在高中生物的种群密度、人口学、统计学等多个领域大显用途。我们先解一下这个微分方程。

\begin{align} \frac{\mathrm{d} y}{\mathrm{d} x}&=y(1-y)\\ \frac{\mathrm{d} y}{y(1-y)}&=\mathrm{d} x\\ \int\frac{\mathrm{d} y}{y(1-y)}&=\int\mathrm{d} x\\ \int\left (\frac{1}{y}+\frac{1}{1-y}\right ) \mathrm{d} y&=\int\mathrm{d} x\\ \ln\left ( \frac{y}{1-y}\right )&=x+C \\ \frac{y}{1-y}&=\exp(x+C) \end{align}

如果常数 C0 ,最后可以化成 y=\frac{1}{\exp(-x)+1} ,这其实就是大名鼎鼎的sigmoid函数,其还在人工智能领域应用广泛,这个函数曲线是S形的,所以高中生物也称作S形曲线。它也被称作Logistic函数,在最近的疫情分析,传染病模型里面也有它的身影,它的形状是这样的。我们可以看到,它的定义域是实数集,值域是 [0,1] ,并且从0到1变化非常缓和。

image

好了,回归正题,我在Cocos Creator的游戏开发中频繁的使用了这个优美的函数曲线,达到了比较赏心悦目的平滑动画效果,它在程序里的实现方式是这样的 y\leftarrow y*(2-y) 。更通俗一点来说,如果我想让一个值 t 平滑的从min变化到max可以在update函数里这样写。

t += (max-t)*dt*speed;

反之,如果我想让一个值 t 平滑的从max变化到min可以在update函数里这样写。

t -= (t-min)*dt*speed;

例如,我使用这种方法实现了根据触摸改变的非常平滑的摄影机缩放效果

除此之外,根据地球到太阳的距离自动显示距离箭头,并且根据触摸自动隐藏或者显示,也利用了这个特性。

除此之外,游戏中为了实现平滑而不生硬的视觉效果,我均采用改变游戏中实体的受力的方式而避免直接改变速度,因为直接改变速度往往是在撞击时发生的,在其它情况尽量少发生。例如,游戏中操控地球的前进,是给予地球刚体一个根据触摸位置而发生改变的持续力。而改变地球的旋转角度,则避免直接改变地球的旋转角,而是给予地球边缘一个小的持续力,以改变地球的角动量,从而实现改变地球的旋转角。

除此之外,我避免摄影机和背景直接跟随摄影机,而是通过给予它们对应刚体的受力,来实现一种错位的立体感和平滑移动感。

归一化

一个向量的归一化就是这个向量的单位向量,在Cocos Creator里面,有 cc.Vec2cc.Vec3两种类型的向量,它们都有 normalize 这个方法获得单位向量。但是有时候我想把一个实数归一化怎么办?聪明的读者可能已经想到了前文所提及的 sigmoid 函数,但是除此之外,我们其实还可以用 \arctan 函数来对一个实数归一化,让它的值介于0到1之间。实际上这个函数作为激活函数在人工智能领域也和 sigmoid 一样应用广泛。

image

将一个实数归一化之后,我们能够较为容易的对这个数进行操作,这种方法在图形学,例如Shader程序设计里面应用广泛。我在游戏脚本里也用了这种方法,使得后面的计算和操作更为简便。

物理效果

在游戏主函数里,有大量向量之间的运算,用来计算受力的大小和方向。例如,在旋转地球时,通过给予地球边缘一个持续力来改变地球的角动量,从而改变旋转角,是一种非常优美的方法。

以黑洞为例,每个天体和地球之间都会形成大小相同,方向相反的力,符合牛顿第三定律

在物理引擎中,设置太空为失重环境,并且置地球的线性速度衰减为0,符合牛顿第一定律

image

image

而天体与地球的引力通过牛顿的万有引力公式计算,符合万有引力定律

image

除此之外,为了考虑狭义相对论的长度收缩效应,地球在高速移动的时候,会在与速度垂直的方向收缩,这样地球会一定程度上变成椭球形。

另外游戏中的所有天体都会给地球一个力,因此地球所处的引力场其实非常复杂,这在一定程度上来说带给了玩家挑战。游戏中的黑洞会有远高于其它天体的强引力,而游戏中的白洞会有强斥力,玩家可以充分发挥自己的能力,利用这样特性来实现一些高难度技巧性操作。

游戏在设计时的细节非常多,并且还在不停的打磨和完善,例如黑洞吸进了天体会膨胀,两个黑洞碰撞会形成更大的黑洞,而黑洞和白洞相撞会湮灭。实际上玩家想要看到这些效果的概率较低,但是通过这些特性,相信玩家能够进行许多有趣的操作。

游戏中可以发射核弹,并且这些核弹会自动寻找距离最近的可打击天体然后毁灭之。

而游戏中的彗星在撞击时冲量很大时会自动销毁,被恒星或者黑洞捕获也会自动销毁。

地球的每次撞击都会调用震动相关API,以实现更加真实的效果

image

游戏的相关奖罚机制在不断调整参数,以达到更好的效果

游戏中充分利用了粒子,以实现更好的视觉效果,这一点在生成星空、彗星的尾巴和火焰时显得尤为重要。

image

视觉效果

视觉效果这一方面,着色器的作用非常大,然而移动端GPU太垃圾,好多着色器运行都会很卡,这是非常可惜的,这里我还是介绍一下Cocos Creator的材质Materials和效果Effects,在Cocos Creator里,每个材质可以绑定一个Texture纹理和Effects,Effects是Cocos Creator特有的一种程序,里面是包含了配置信息、顶点着色器和片元着色器,其中片元着色器非常强大。例如你可以用 shader程序生成一片动态的立体星空:

image

利用shader生成一颗旋转的恒星

image

甚至一颗立体的、动态的黑洞

image

或者生成一个银河系等

image

然而这些目前由于手机性能的原因,暂时无法加入游戏,实在太可惜了!

结尾

说的这些只是我最近研究并实现的一部分内容,其实还有不少细节,但写的实在有点累了,所以就暂时写这么多吧!最后非常高兴能够和cyb和lby合作这个游戏项目,希望我们能够一起加油把这个项目做好,加油!

PS

上文所提及的着色器技术依赖于较好的GPU,然而移动平台的GPU性能实在鸡肋,所以那些非常好的效果有可能将无法出现在移动端。

最后,大家有啥建议或者点子都可以告诉我,随着游戏的迭代,我们都尽可能将其实现。
原文发表在我的临时博客 烧风的小站 ,希望大家支持,感谢!

烧风 2021年1月30日

4赞
粤 ICP 备 2020080455 号