有一个练练习手速反应小游戏的网页上的小游戏,是出现很多红点,用鼠标去点

&figure&&img src=&https://pic4.zhimg.com/v2-896b796a0a9b5c67e6ac1a3cc890d546_b.jpg& data-rawwidth=&990& data-rawheight=&260& class=&origin_image zh-lightbox-thumb& width=&990& data-original=&https://pic4.zhimg.com/v2-896b796a0a9b5c67e6ac1a3cc890d546_r.jpg&&&/figure&&p&最近半年赶项目的事情一直很忙,好不容易上周末的时候有空做点渲染的东西玩,于是尝试了一下&a href=&https://link.zhihu.com/?target=https%3A//docs.unrealengine.com/en-us/Engine/Rendering/LightingAndShadows/BentNormalMaps& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Bent Normal Maps&/a&。&/p&&p&这是UE 4.17发布的功能之一,可以拿来解决间接光照漏光;工具部分Substaince Designer已经支持&a href=&https://link.zhihu.com/?target=https%3A//support.allegorithmic.com/documentation/display/SDDOC/Bent%2BNormals%2Bfrom%2Bmesh& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&利用高模烘焙Bent Normal&/a&。效果图对比来自UE4文档:&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-1f29359bfb283d84fe845a43d2a730ef_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&847& data-rawheight=&716& class=&origin_image zh-lightbox-thumb& width=&847& data-original=&https://pic4.zhimg.com/v2-1f29359bfb283d84fe845a43d2a730ef_r.jpg&&&/figure&&p&&br&&/p&&h2&&b&算法原理&/b&&/h2&&p&为了实现这个,首先要理解一下思路。这货我在网上找了半天介绍,最后发现其实是一个『老概念』了… &a href=&https://link.zhihu.com/?target=http%3A//developer.download.nvidia.com/books/HTML/gpugems/gpugems_ch17.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GPU Gems Chap. 17 Ambient Occlusion&/a&里面是这么描述的&/p&&blockquote&The approach can also be extended to produce the average unoccluded direction, or bent normal. We can use a shader to calculate the direction to the light multiplied by the shadow value, and then copy the result to the RGB output color. The occlusion information can be stored in the alpha channel. We accumulate these RGB normal values in the same way as the occlusion value, and then we normalize the final result to get the average unoccluded normal. Note that a half (16-bit) floating-point accumulation buffer may not have sufficient precision to represent the summation of these vectors accurately.&/blockquote&&p&游戏里的低模带上法线代表的是高模的法线,但是这里其实没有考虑周围Mesh的遮蔽影响。如果间接光照直接使用普通法线,就可能出现『暗部漏光』的现象&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-1eafa11dd52_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&250& data-rawheight=&138& class=&content_image& width=&250&&&/figure&&p&偷懒的童鞋可以参考一份中文介绍 &a href=&https://link.zhihu.com/?target=https%3A//blog.csdn.net/BugRunner/article/details/7272902& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Bent Normal (环境法线?)&/a&。&/p&&h2&&b&基于Unity的Bent Normal Baker&/b&&/h2&&p&虽然Substaince Designer可以直接烘焙(话说也可以用SSAO后处理,不过弄手游的暂时就不贪心了…),但出于做着玩的角度,肯定是尝试在Unity里实现烘焙。&/p&&p&GPU Gems里提到的是&i&This method is based on a view-independent preprocess that computes occlusion information with a ray tracer and then uses this information at runtime to compute a fast approximation to diffuse shading in the environment.&/i& 我脑洞了一下:是否可以用Unity的ShadowMap来替代可见性计算:&/p&&ul&&li&生成一个球状平行光分布(真正烘焙的时候会比这个密很多&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-af0cc4eaadf2_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&736& data-rawheight=&641& class=&origin_image zh-lightbox-thumb& width=&736& data-original=&https://pic3.zhimg.com/v2-af0cc4eaadf2_r.jpg&&&/figure&&ul&&li&每次从不同角度渲染物体,利用Shadow Map可以得到每个像素可见性。有几个需要注意的地方:&/li&&ul&&li&输出到2UV(这个技巧可以参考之前博客 &a href=&https://link.zhihu.com/?target=https%3A//www.qiankanglai.me//sgmodelinspector/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&扩展Unity模型编辑器&/a&),记得关掉Cull/ZTest等;&/li&&li&Light上用Hard Shadow,需要的话调整下Bias等参数;&/li&&li&不要用Screen Space Shadow;&/li&&li&注意相机位置、模型大小,让Shadow Map利用率最高;&/li&&/ul&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-fdf34a170e006b115b56ad4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1100& data-rawheight=&672& class=&origin_image zh-lightbox-thumb& width=&1100& data-original=&https://pic3.zhimg.com/v2-fdf34a170e006b115b56ad4_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&ps. 我一开始是使用&code&Graphics.DrawMeshNow&/code&直接绘制到RenderTexture的,后来发现很多变量引擎不会自动传过去特别闹心… 最后换了个路子,直接设置&code&Camera.targetTexture&/code&然后&code&Camera.Render&/code&省心多了。&/p&&ul&&li&新建一张float的RenderTexture,然后Blend One One情况下各个角度渲染一遍叠加:如果没有影子,RGB输出光源方向,A输出1计数;最后RGB/A保存下来即可。&/li&&li&最后生成的结果需要做几遍Dilation 解决边缘采样问题&/li&&/ul&&h2&&br&&b&Bake结果对比&/b&&/h2&&p&和SD出的图比了下,只能说方向上没问题,但是还有不少细节差距很大(人家毕竟商业软件,我整这个加起来不超过3h,逃)&/p&&figure&&img src=&https://pic2.zhimg.com/v2-599eec4c9aacfa5fd12c08d_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&630& data-rawheight=&469& class=&origin_image zh-lightbox-thumb& width=&630& data-original=&https://pic2.zhimg.com/v2-599eec4c9aacfa5fd12c08d_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&有些奇怪的噪声来自于采样严重不足;&/li&&li&严重怀疑Substaince Designer做了一些图像空间的操作,因为它生成的BentNormal有些地方根本没有2UV对应也有值,这就很有意思了;&/li&&li&Substaince Designer使用的是ZB高模,这个Unity导入就费劲…&/li&&/ul&&p&不过好处也是有的:Substaince Designer导出的是normalized Bent Normal;我自己生成的时候B通道拿来存了AO Strength,还可以当成Mesh AO使用。另外就是在Unity里烘焙确实流程简单+迭代起来快。 放在自己项目里比较了下背光时候Diffuse IBL部分的效果(暂时只用了天光地光),因为法线平滑了很多所以漏光好了很多:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-8eda6d72a518dc_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&994& data-rawheight=&630& class=&origin_image zh-lightbox-thumb& width=&994& data-original=&https://pic2.zhimg.com/v2-8eda6d72a518dc_r.jpg&&&/figure&&h2&&b&后续工作&/b&&/h2&&p&目前的结论是在手游上可以尝试使用一下的,反正低配一个keyword关掉就行了。&/p&&p&接下来有精力的话还要好好迭代下预处理烘焙这块。 Unity这套Baker方案扩展一下其实有非常多二次开发的空间,譬如AO、Normal等完全可以实现自定义的烘焙(当然另外一条路就是写C++的Ray Tracing来搞Baker)。&/p&&p&SD/SP/Max等美术DCC工具最大问题是public API不是很多,如果想定制输出不是很方便;若只是最终结果的encode还好办,如果想拿中间结果就很费劲。在Unity里搞Baker的最大意义即在于此。&/p&&p&顺便po一张图:三个面光源下,Max里Vray烘焙到贴图和Unity里Enlighten烘焙到贴图的对比,看上去还是比较有搞头的(左边模型鞋子/腰带上的问题其实是Max里哪里出错了,然而我对这货不熟-.-)&/p&&figure&&img src=&https://pic4.zhimg.com/v2-4c09fbe1b4ca191eef44_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&572& data-rawheight=&631& class=&origin_image zh-lightbox-thumb& width=&572& data-original=&https://pic4.zhimg.com/v2-4c09fbe1b4ca191eef44_r.jpg&&&/figure&&p&这样最大的好处是提高定制性和整合工作流顺畅度(虽然目前来看鸽的概率太大了)。&/p&&p&不说了继续写Lua去了,不YY了(逃&/p&
最近半年赶项目的事情一直很忙,好不容易上周末的时候有空做点渲染的东西玩,于是尝试了一下。这是UE 4.17发布的功能之一,可以拿来解决间接光照漏光;工具部分Substaince Designer已经支持。效果图对比来自UE4文档…
&figure&&img src=&https://pic4.zhimg.com/v2-782b88d1c178f5fbaa47_b.jpg& data-rawwidth=&1538& data-rawheight=&735& class=&origin_image zh-lightbox-thumb& width=&1538& data-original=&https://pic4.zhimg.com/v2-782b88d1c178f5fbaa47_r.jpg&&&/figure&&h2&Unity实现(Github地址):&/h2&&p&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/001CommandPattern& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&1. 命令模式&/a& &a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/002FlyweightPattern& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&2. 享元模式&/a& &a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/003ObserverPattern& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&3. 观察者模式&/a& &a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/004PrototypePattern& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&4. 原型模式&/a& &a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/005SingletonPattern& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&5. 单例模式&/a& &/p&&p&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/006StatePattern& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&6. 状态模式&/a& &a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/007SequencingPatterns& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&7. 序列模式&/a& &a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/008BehavioralPatterns& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&8. 行为模式&/a& &a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/009DecouplingPatterns& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&9. 解耦模式&/a& &a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/010OptimizationPatterns& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&10. 优化模式&/a&&/p&&hr&&figure&&img src=&https://pic4.zhimg.com/v2-efeaa63df1b749ac85d460a3ce901223_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1880& data-rawheight=&1254& class=&origin_image zh-lightbox-thumb& width=&1880& data-original=&https://pic4.zhimg.com/v2-efeaa63df1b749ac85d460a3ce901223_r.jpg&&&/figure&&h2&笔记部分&/h2&&ul&&li&以下部分只包含笔记,具体实现及项目说明可查看&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Github&/a&&/li&&li&笔记中很多都是个人理解,目的是尽量让原本抽象的概念更易懂一些&/li&&li&关于书名——书名直译是《游戏编程模式》,但在&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/acknowledgements.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&中文版致谢页&/a&中,被翻译成了《游戏设计模式》,“设计模式”一词来源于&a href=&http://link.zhihu.com/?target=https%3A//baike.baidu.com/item/GoF/6406151& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GOF&/a&。此书(GPP)虽与编程有关,但更本质上是设计思想,所以更倾向“设计模式”这个翻译&/li&&li&&b&链接&/b&&/li&&ul&&li&&a href=&http://link.zhihu.com/?target=http%3A//gameprogrammingpatterns.com/contents.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&英文原版&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&中文版&/a&&/li&&/ul&&/ul&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/command.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&命令模式&/a&&/h2&&h2&是什么(个人理解)&/h2&&p&将命令封装,与目标行为解耦,使命令由流程概念变为对象数据&/p&&h2&为什么&/h2&&p&既然命令变成了数据,就是可以被传递、存储、重复利用的:&/p&&ul&&li&通过命令数据队列或栈可以轻易实现撤销、重做、时光倒流&/li&&li&命令数据还可以形成日志,用于复现用户行为,便于重复测试同样序列命令对各种目标的影响&/li&&li&这些命令数据可以发送给不同的目标,比如同样的“出发,5分钟后,停止”,发送给飞机就可以变成“起飞,5分钟后,降落”,发送给轮船就成了“离港,5分钟后,抛锚”&/li&&/ul&&h2&怎么做(U3D示例)&/h2&&h2&类图如下:&/h2&&figure&&img src=&https://pic1.zhimg.com/v2-3dfdff2ef7fe4b1b5c3d8_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&587& data-rawheight=&351& class=&origin_image zh-lightbox-thumb& width=&587& data-original=&https://pic1.zhimg.com/v2-3dfdff2ef7fe4b1b5c3d8_r.jpg&&&/figure&&h2&具体实现:&/h2&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/001CommandPattern& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-21e727e46e1fe8d2fbdf8e_ipico.jpg& data-image-width=&420& data-image-height=&420& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TYJia/GameDesignPattern_U3D_Version&/a&&h2&缺陷&/h2&&p&可能会导致大量的实例化,从而浪费内存&/p&&h2&拓展&/h2&&p&可用享元模式代替大量的实例化&/p&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/flyweight.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&享元模式&/a&&/h2&&h2&是什么(个人理解)&/h2&&p&不同的实例共享相同的特性(共性),同时保留自己的特性部分&/p&&h2&为什么&/h2&&ul&&li&传递的信息享元化,可以节约计算时间&/li&&li&存储的信息享元化,可以节约占用的空间&/li&&li&所以享元模式可以减少时间与空间上的代价&/li&&/ul&&h2&怎么做&/h2&&h2&类图如下:&/h2&&figure&&img src=&https://pic2.zhimg.com/v2-39192b2daf28aa8f412a5bff7dcb45a1_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&600& data-rawheight=&403& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic2.zhimg.com/v2-39192b2daf28aa8f412a5bff7dcb45a1_r.jpg&&&/figure&&p&左半部分为享元模式下,只有一个CubeBase,通过ObjInstancing(int num)将共享的网格、材质及一个Transform信息表传递给GPU,只有一个Draw Call,所以效率极高&/p&&p&右半部分为关闭享元模式后的做法,每生成一个Cube都会重新实例化一个立方体,并向GPU发送一次网格、材质和位置信息,所以1000个立方体就需要1000个Draw Call,效率极低&/p&&h2&具体实现:&/h2&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/002FlyweightPattern& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-21e727e46e1fe8d2fbdf8e_ipico.jpg& data-image-width=&420& data-image-height=&420& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TYJia/GameDesignPattern_U3D_Version&/a&&h2&拓展&/h2&&p&可与对象池联动,进一步减少内存的开销&/p&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/observer.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&观察者模式&/a&&/h2&&h2&是什么(个人理解)&/h2&&p&事件与其他对象行为的解耦——例如一个代码描述了日本核电站爆炸的事件,世界人民买盐这种行为显然不应该由核电站爆炸直接调用,而是通过卫星电视告诉广大群众,群众想买盐还是想买仙人掌就由他们自己决定了~&/p&&h2&为什么&/h2&&ul&&li&解耦,物价局改了粮价不需要挨家挨户通知公民,只需要让电视台播个新闻就好&/li&&li&如果要挨家挨户通知,物价局必须有每个公民的地址,这显然不合理,也会浪费很多资源&/li&&li&扩展困难——如果公民改了地址或者有新公民出生了,那还需要告诉物价局,这也很荒唐&/li&&/ul&&h2&怎么做&/h2&&h2&类图如下:&/h2&&figure&&img src=&https://pic1.zhimg.com/v2-ddaad46ef76f43ef30cc60_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&600& data-rawheight=&205& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic1.zhimg.com/v2-ddaad46ef76f43ef30cc60_r.jpg&&&/figure&&p&射手(Shooter,观察者,这里是听众)告诉广播电台(Radio)自己要听发射气球的广播&/p&&p&吹气球的人(Emitter) 向上发出气球,并告诉广播电台自己发射了气球&/p&&p&广播电台广播发射了气球的消息,所有射手向气球射击&/p&&p&这个例子中吹气球的人不会关心谁是射手,射手也不用在意谁是吹气球的人&/p&&h2&具体实现:&/h2&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/003ObserverPattern& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-21e727e46e1fe8d2fbdf8e_ipico.jpg& data-image-width=&420& data-image-height=&420& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TYJia/GameDesignPattern_U3D_Version&/a&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/prototype.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&原型模式&/a&&/h2&&h2&是什么(个人理解)&/h2&&p&将一个或多个对象当做原型,通过统一的生成器克隆出很多类似原型的对象,同时可以通过配置表更改克隆体属性,制造出很多具有自身个性的对象。&/p&&h2&为什么&/h2&&ul&&li&复用生成器,而非针对每一个不同的对象做一个生成器&/li&&li&与享元模式结合,通过配置表来实现对象的个性,将不同配置与代码解耦&/li&&/ul&&h2&怎么做&/h2&&h2&类图如下:&/h2&&figure&&img src=&https://pic3.zhimg.com/v2-3fb324fd474611cadb1a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&600& data-rawheight=&401& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic3.zhimg.com/v2-3fb324fd474611cadb1a_r.jpg&&&/figure&&ul&&li&Unity中Prefab本质就是此模式里的原型,而Spawner要做的只是调用Instantiate方法&/li&&li&新的Prefab被生成以后,通过读取Dragons.txt里配置的信息来设置克隆体的名称和尺寸&/li&&/ul&&blockquote&注:这里为了快速实现使用txt记录配置表(我在偷懒),但实际项目里,往往使用SQL、Json、csv等方式进行配置&/blockquote&&h2&具体实现:&/h2&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/004PrototypePattern& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-21e727e46e1fe8d2fbdf8e_ipico.jpg& data-image-width=&420& data-image-height=&420& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TYJia/GameDesignPattern_U3D_Version&/a&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/singleton.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&单例模式&/a&&/h2&&h2&是什么(个人理解)&/h2&&p&使用单例意味着这个对象只有一个实例,这个实例是此对象自行构造的,并且可向全局提供&/p&&h2&为什么&/h2&&ul&&li&减少代码复用,让专门的类处理专门的事情——例如让TimeLog类来记录日志,而不是把StreamWriter的代码写到每一个类里&/li&&li&快速访问,任何其他类都可以通过ClassName.Instance来访问单例,使用它的公开变量和方法&/li&&/ul&&h2&缺陷&/h2&&ul&&li&因为实现简单,而且使用方便,所以有被滥用的趋势&/li&&li&滥用单例会促进耦合的发生,因为单例是全局可访问的,如果不该访问者访问了单例,就会造成过耦合——例如如果播放器允许单例,那石头碰撞地面后就可以直接调用播放器来播放声音,这在程序世界并不合理,而且会破坏架构&/li&&li&如果很多很多类和对象调用了某个单例并做了一系列修改,那想理解具体发生了什么就困难了&/li&&li&对多线程不太友好——每个线程都可以访问这个单例,会产生初始化、死锁等一系列问题&/li&&/ul&&h2&怎么做&/h2&&p&U3D中利用MonoBehaviour初始化单例非常简单,只要在Awake中加入Instance = this,不过要注意的是,别的类不能在Awake里使用这个单例&/p&&p&单例在普通C#中还有其他做法,甚至有些泛型、线程安全的扩展,也都不复杂,可以自行查询&/p&&h2&类图如下:&/h2&&figure&&img src=&https://pic4.zhimg.com/v2-252f5c76b5e601fd35ad5b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&413& data-rawheight=&150& class=&content_image& width=&413&&&/figure&&h2&具体实现:&/h2&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/005SingletonPattern& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-21e727e46e1fe8d2fbdf8e_ipico.jpg& data-image-width=&420& data-image-height=&420& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TYJia/GameDesignPattern_U3D_Version&/a&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/state.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&状态模式&/a&&/h2&&h2&是什么(个人理解)&/h2&&p&现在状态和条件决定对象的新状态,状态决定行为(Unity内AnimationController就是状态机)&/p&&h2&为什么&/h2&&ul&&li&使流程清晰化、结构化&/li&&li&简化判断逻辑,比如嘴的状态是洗牙,那就不应该做出咀嚼的行为;必须是在憋气,那就不应该做出呼吸的行为&/li&&/ul&&h2&注解&/h2&&ul&&li&状态机(自动机)是我最喜欢的一种设计模式,因为这样设计的程序逻辑清晰,稳定性也很强&/li&&li&作者对switch case下的状态机理解并不深刻,一般情况下,状态机需要两个switch case,一个用于处理状态变化,另一个用来处理状态行为&/li&&li&相比状态类,个人更喜欢switch case的方法,虽然状态类有其有点,但缺点也非常明显——当状态量较大时,代码量激增,可读性也很差,状态变化和状态行为都需要大量的信息传递,十分不便&/li&&/ul&&h2&怎么做&/h2&&p&这次我实现了两个版本:&/p&&ol&&li&SwitchCase版本,用按键控制一盏冷暖灯,关灯状态下,按一次打开暖光,再按切换为白光,再按变为暖白光,再按关闭&/li&&li&状态类版本,交通灯 停止、通行、闪烁、等待的切换&/li&&/ol&&p&另外自动机用类图描述不是好方法,应该用自动机专门的图来说明才对&/p&&h2&SwitchCase版本类图及自动机如下:&/h2&&figure&&img src=&https://pic4.zhimg.com/v2-252f5c76b5e601fd35ad5b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&413& data-rawheight=&150& class=&content_image& width=&413&&&/figure&&figure&&img src=&https://pic1.zhimg.com/v2-aa9f5d94685bbd7da5c16ebc3698653c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&834& data-rawheight=&622& class=&origin_image zh-lightbox-thumb& width=&834& data-original=&https://pic1.zhimg.com/v2-aa9f5d94685bbd7da5c16ebc3698653c_r.jpg&&&/figure&&h2&StateClass版本类图及自动机如下:&/h2&&figure&&img src=&https://pic3.zhimg.com/v2-42dd93a0ea_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&600& data-rawheight=&272& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic3.zhimg.com/v2-42dd93a0ea_r.jpg&&&/figure&&figure&&img src=&https://pic2.zhimg.com/v2-5f5e2ec779aa06f9e6dcd_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&876& data-rawheight=&612& class=&origin_image zh-lightbox-thumb& width=&876& data-original=&https://pic2.zhimg.com/v2-5f5e2ec779aa06f9e6dcd_r.jpg&&&/figure&&h2&具体实现:&/h2&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/006StatePattern& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-21e727e46e1fe8d2fbdf8e_ipico.jpg& data-image-width=&420& data-image-height=&420& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TYJia/GameDesignPattern_U3D_Version&/a&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/sequencing-patterns.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&序列模式&/a&&/h2&&h2&是什么、为什么(个人理解)&/h2&&p&包含了&/p&&ul&&li&双缓冲模式&/li&&ul&&li&当一个缓冲准备好后才会被使用——就像一个集装箱装满才会发货一样;当一个缓冲被使用时另一个处于准备状态,就形成了双缓冲&/li&&li&在渲染中广泛使用,一帧准备好后才会被渲染到屏幕上——所以准备时间太长就会导致帧率下降&/li&&/ul&&li&游戏循环&/li&&ul&&li&可参考&a href=&http://link.zhihu.com/?target=https%3A//docs.unity3d.com/Manual/ExecutionOrder.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&脚本生命周期&/a&&/li&&/ul&&li&更新方法&/li&&ul&&li&同上,实际上是Unity通过反射在生命周期不同时刻调用MonoBehaviour中的相关方法&/li&&/ul&&/ul&&p&这三者一定程度上是相辅相成的,在Unity中都已在底层实现,双缓冲可以通过FrameDebugger体会,而游戏循环、更新方法则与脚本生命周期和MonoBehaviour相关&/p&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/behavioral-patterns.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&行为模式&/a&&/h2&&h2&是什么、为什么(个人理解)&/h2&&p&包含了&/p&&ul&&li&字节码&/li&&ul&&li&享元模式、原型模式中,不同的属性被存储在数据库中,而字节码是将行为存在数据库中&/li&&li&可用于实现可视化脚本编辑工具&/li&&/ul&&li&子类沙箱&/li&&ul&&li&子类使用基类方法,或在基类方法上扩展&/li&&/ul&&li&类型对象&/li&&ul&&li&其实是享元模式、原型模式的一种应用,以不同数据(而不是不同类)区分对象类型&/li&&/ul&&/ul&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/decoupling-patterns.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&解耦模式&/a&&/h2&&h2&是什么、为什么(个人理解)&/h2&&p&包含了&/p&&ul&&li&组件模式&/li&&ul&&li&本质上是功能的模块化,延伸了面向对象的解耦思想&/li&&li&U3D的编程思想就是面向组件的,MonoBehaviour的子类都可作为组件挂在GameObject上&/li&&/ul&&li&事件序列&/li&&ul&&li&就像银行办事需要排号一样——每个顾客要处理的事都是一个事件,编号后就形成了天然的事件序列,银行会按一定规则来依次处理队列中的事件&/li&&li&一般在底层实现,但宏观上依然存在,例如RTS游戏中通过Shift对一些单位下达前往不同位置的命令&/li&&li&Unity中协程可以用来做消息队列,防止同帧产生大量的计算&/li&&/ul&&li&服务定位器&/li&&ul&&li&类似单例模式,在运行时寻找组件(而不是运行前赋值)&/li&&li&Unity中GetComponent,FindObjectOfType,Find等方法都可帮助实现相关服务的查找,但此类反射方法要避免在运行时高频循环调用&/li&&li&拓展——还可以建立一个运行前赋值的服务注册中心(当然也可运行中赋值),其他需要服务的对象在运行时去注册中心查找相关服务,这样做一方面可以避免全局反射的恶果,一方面可以保留服务定位器带来的解耦优势——单例模式也可使用这样的方法来替换(对象注册中心)&/li&&/ul&&/ul&&h2&怎么做(事件队列)&/h2&&p&点击鼠标时在Queue中添加一个红点,当目标点为空时从Queue中取出第一位位作为目标点,让Player移向目标点,到达目标点时删除目标点&/p&&h2&具体实现:&/h2&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/009DecouplingPatterns& data-draft-node=&block& data-draft-type=&link-card& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com/TYJia/GameDe&/span&&span class=&invisible&&signPattern_U3D_Version/tree/master/Assets/009DecouplingPatterns&/span&&span class=&ellipsis&&&/span&&/a&&hr&&h2&&a href=&http://link.zhihu.com/?target=https%3A//gpp.tkchu.me/optimization-patterns.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&优化模式&/a&&/h2&&h2&是什么、为什么(个人理解)&/h2&&p&包含了&/p&&ul&&li&数据局部性&/li&&ul&&li&CPU缓存读写速度大于内存读写速度,所以要尽量减少缓存不命中(CPU从内存读取信息)的次数&/li&&li&用连续队列代替指针的不断跳转&/li&&li&不过此模式会让代码更复杂,并伤害其灵活性&/li&&/ul&&li&脏标识模式&/li&&ul&&li&需要结果时才去执行工作——避免不必要的计算或传输开销&/li&&li&一种是被动状态变化时才计算,否则使用缓存;另一种是主动变化标识,否则不执行(例如存盘)&/li&&/ul&&li&对象池模式&/li&&ul&&li&对象池就像一包不同颜色的水彩笔,当我们使用时就拿出来,不用时就放回去——而不是使用时就买一只,不用时就扔进垃圾桶&/li&&li&可以减少内存碎片,减少实例化与回收对象所面临的开销&/li&&/ul&&li&空间分区&/li&&ul&&li&建立细分空间用于存储数据(对象),可以帮助告诉定位对象,降低算法复杂度&/li&&li&例如邮局寄信,如果只按身份证号邮寄,那就麻烦了,每封信平均要拿给几亿人确认是否是ta的;但是按空间分区后,就简单了——省份、城市、街道、小区、楼栋、单元、房号,于是很快就能定位到个人。&/li&&/ul&&/ul&&h2&怎么做(对象池)&/h2&&p&用对象池对之前实现的&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/009DecouplingPatterns& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&例子&/a&做了优化:&/p&&ul&&li&之前每次点击鼠标会生成一个目标点,Player到达目标点后会将目标点回收(Destroy)&/li&&li&优化后点击鼠标,先会尝试从对象池“未激活列表”获取对象,无法获取才会生成新对象并放入对象池中的“已激活列表”;Player到达目标点后,会把对象从已激活列表放入未激活列表,并执行SetActive(false)方法&/li&&/ul&&h2&怎么做(空间分区)&/h2&&ul&&li&这里我实现了一个八叉树简单示例,用来寻找最近的点&/li&&li&建立&/li&&ul&&li&先寻找空间边界,建立父节点长方体&/li&&li&若父节点中点数超过阈值,则分割成八个子节点长方体&/li&&/ul&&li&寻找最近的点&/li&&ul&&li&在点所在的和临近的立方体中寻找最近的点&/li&&/ul&&/ul&&blockquote&因为只是示例,所以并未完善临近立方体的查找,目前只用了八叉树结构临近的立方体,而非空间临近,有兴趣的同学可以进一步优化&/blockquote&&ul&&li&更新点&/li&&ul&&li&先看点是否在之前的长方体里,如果不在,则从当前节点移除,并查询是否在父节点里&/li&&li&如果在父节点里,则向下查询在哪一个子节点里此示例只能更新点的位置,也就是八叉树中的内容,不能更新八叉树的结构,大家可以自行思考如何更新结构&/li&&/ul&&/ul&&blockquote&此示例只能更新点的位置,也就是八叉树中的内容,不能更新八叉树的结构,大家可以自行思考如何更新结构&/blockquote&&h2&具体实现:&/h2&&a href=&http://link.zhihu.com/?target=https%3A//github.com/TYJia/GameDesignPattern_U3D_Version/tree/master/Assets/010OptimizationPatterns& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-21e727e46e1fe8d2fbdf8e_ipico.jpg& data-image-width=&420& data-image-height=&420& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TYJia/GameDesignPattern_U3D_Version&/a&&hr&&p&前几天有亲人离世,愿安息&/p&&p&望生者多体验些人间的美好&/p&
Unity实现(Github地址):
笔记部分以下部分只包含笔记,具体实现及项目说明可查看笔记中很多都是个人理解,目的是尽量让…
&p&让你见识见识 三个非洲叔叔如何从狮群口中夺取食物&/p&&br&&a class=&video-box& href=&//link.zhihu.com/?target=https%3A//www.zhihu.com/video/697856& target=&_blank& data-video-id=&& data-video-playable=&true& data-name=&& data-poster=&https://pic2.zhimg.com/v2-ef0d5f2fb55c742b4fa2e.jpg& data-lens-id=&697856&&
&img class=&thumbnail& src=&https://pic2.zhimg.com/v2-ef0d5f2fb55c742b4fa2e.jpg&&&span class=&content&&
&span class=&title&&&span class=&z-ico-extern-gray&&&/span&&span class=&z-ico-extern-blue&&&/span&&/span&
&span class=&url&&&span class=&z-ico-video&&&/span&https://www.zhihu.com/video/697856&/span&
&/a&&br&视频来自BBC的纪录片
让你见识见识 三个非洲叔叔如何从狮群口中夺取食物 视频来自BBC的纪录片
&figure&&img src=&https://pic1.zhimg.com/v2-550abeed9b340_b.jpg& data-rawwidth=&900& data-rawheight=&500& class=&origin_image zh-lightbox-thumb& width=&900& data-original=&https://pic1.zhimg.com/v2-550abeed9b340_r.jpg&&&/figure&&p&原文发布于 Cocos 官方公众号&/p&&a href=&http://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/GiA5SFcD_ouvSZwIfOyVug& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic1.zhimg.com/v2-19bcbc2dca740ada502be8_180x120.jpg& data-image-width=&830& data-image-height=&467& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&腾讯用 Cocos Creator 改造教育app,尽显高性能跨平台优势&/a&&p&&br&&/p&&p&2018 年 6 月 GMTC 全球移动技术大会在北京举办,大会旨在通过聚焦前沿技术与时间经验帮助参会者了解移动开发、前端领域最新的技术趋势与最佳实践,专为中国3-8岁儿童设计的英语学习移动应用 ABCmouse 也参与了本次盛事。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-b90ed755d5_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1280& data-rawheight=&960& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-b90ed755d5_r.jpg&&&/figure&&p&今日 Cocos 特邀来自&b&腾讯社交平台部的高级工程师 josephpan(潘伟洲)&/b&为大家带来 &b&ABCmouse 基于 Cocos 的高性能跨平台开发方案&/b&,他将通过项目背景和具体实践同各位开发者分享经验,以下内容已获作者授权,篇幅较长干货满满,预计阅读所需时间:10 分钟。&/p&&hr&&h2&&b&《基于 Cocos 的高性能跨平台开发方案》&/b&&/h2&&p&原作者:腾讯社交平台部高级工程师
josephpan(潘伟洲)&/p&&p&&br&&/p&&p&大概从去年九月份开始,我们选择使用 Cocos 来作为我们一款产品 ABCmouse 的跨平台应用开发方案,在这个过程中,我们做了一系列的优化,也踩了一些坑,本文将对这个过程做一个回顾和总结。&/p&&p&&br&&/p&&p&本文的内容主要分三块来讲:首先简单介绍一下项目背景;接下来具体介绍下我们的实践过程,分享一些经验;最后给出新的开发方案和以前的效果对比&b&。&/b&&/p&&p&&br&&/p&&p&&br&&/p&&p&&b&一、项目背景
&/b&&/p&&p&&br&&/p&&p&首先介绍一下我们的产品,&b&ABCmouse 是美国知名的儿童英语在线学习领导品牌,在美国有超过百万家庭在使用,也获得了7万多个教师的推荐。&/b&&/p&&p&&br&&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-6094fee5f0effcb0f011_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&583& data-rawheight=&334& class=&origin_image zh-lightbox-thumb& width=&583& data-original=&https://pic2.zhimg.com/v2-6094fee5f0effcb0f011_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&这个应用采用的是典型的 Hybrid App 跨平台开发方案,里头基本全是 H5 的页面。&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-8ca6f43b96b46fd656f95e_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&588& data-rawheight=&236& class=&origin_image zh-lightbox-thumb& width=&588& data-original=&https://pic3.zhimg.com/v2-8ca6f43b96b46fd656f95e_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&Hybrid App 最大的问题就是性能问题,用户经常会在页面加载上等待非常多时间。我们统计了 ABCmouse 各个场景的平均加载耗时,发现平均都要花费大约三到四秒的时间,漫长的等待时间也对用户的学习积极性带来影响。&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-ae22a1d85a06b78c26c2c5_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&574& data-rawheight=&303& class=&origin_image zh-lightbox-thumb& width=&574& data-original=&https://pic2.zhimg.com/v2-ae22a1d85a06b78c26c2c5_r.jpg&&&/figure&&p&&br&&/p&&p&从去年九月份开始,团队与 ABCmouse 的研发公司 Age of Learning 公司开展了战略合作,我们希望能够开发出一款针对中国儿童的英语学习应用——我们称之为 ABCmouse 腾讯版。我们希望它能提供更符合中国儿童使用习惯的学习路径,并在里头融入腾讯的社交元素,从而带动儿童外语学习的积极性。&/p&&p&&br&&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-903e71dadcef0_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&346& data-rawheight=&303& class=&content_image& width=&346&&&/figure&&p&&br&&/p&&p&&b&从技术上,我们希望新版的 ABCmouse 能够在表现力、性能、效率和社交四大方面都能有更好的表现&/b&(这里的表现力指的是产品的界面和交互,能够做到更吸引中国的小朋友)。&/p&&p&&br&&/p&&p&通过初期技术预研后,我们决定使用 Cocos 来改造这个项目:&/p&&p&&br&&/p&&ol&&li&跨平台。Cocos 支持使用同一套代码构建生成 Web、iOS、Android 等几个端,最新的版本还支持发布到微信小游戏、Facebook Instant Games 和 QQ 玩一玩;&/li&&li&性能。Cocos 的原理是在 Activity 中绘制一个 OpenGL 的 SurfaceView ,并由其完成页面的渲染的。与基于 WebView 渲染的 Hybrid 应用相比,Cocos 的渲染速度更快,性能更好。&/li&&li&效率。借助可视化的 Cocos Creator 工具,界面的开发和资源的管理非常便捷,设计团队也可以参与进来设计界面和动效,提升开发效率。&/li&&li&表现力。ABCmouse 中包含了很多诸如游戏、画图、音乐等带游戏和娱乐性质的场景,而 Cocos 本身是为游戏开发设计的,更适合用在我们的产品中。&/li&&/ol&&p&&br&&/p&&p&&br&&/p&&p&&b&二、实践过程
&/b&&/p&&p&&br&&/p&&p&在具体实践这一块,我准备分成&b&架构篇、甜头篇、踩坑篇、优化篇&/b&四个部分来介绍。&br&&/p&&p&&br&&/p&&p&&b&架构篇&/b&&/p&&p&一图胜千言。我们整个系统架构可以用这张图来概括。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-daeca84daf3_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&545& data-rawheight=&227& class=&origin_image zh-lightbox-thumb& width=&545& data-original=&https://pic4.zhimg.com/v2-daeca84daf3_r.jpg&&&/figure&&p&&br&&/p&&p&我们自底向上看,最底层是 native 层,Cocos2d-x 开发框架,在这一层提供了对 JavaScriptCore、SpiderMonkey、V8、ChakraCore 等多种可选的 JS 执行引擎的封装。在这基础上又架设了一层 JSB ,主要起到桥接作用。我们的应用也在底层封装了多种基础能力,包括支持直出的webview、自定义的视频播放器、音频播放器、支付、推送等。&/p&&p&&br&&/p&&p&再往上是 JS 层,在这一层 Cocos 提供了丰富的开发组件和 API,我们也扩展了多种组件,包括一些通用的UI组件、一个多端通用的音频播放器、一个带缓存和内存回收功能的图片加载器、常驻节点、上报、日志等组件。有些组件是依赖 native 层的。&/p&&p&&br&&/p&&p&Cocos 层和 Native 层就通过 callStaticMethod 和 evalString 来完成互相调用。&/p&&p&&br&&/p&&p&有了这些基础后,再往上则可以开展具体的场景开发了。&/p&&p&&br&&/p&&p&为了帮助大家更好地理解 Cocos 的跨平台原理,我们可以拿 Cocos 的渲染原理和 React Native 做一个对比。&/p&&p&&br&&/p&&p&Cocos 的渲染原理是在 UI 线程将场景文件理解成场景树,然后交给 GL 线程渲染。也就是说,用户看到的大部分场景都是使用 OpenGL 或者 WebGL 绘制的,即使在不同的平台,也能够有完全相同的表现。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-d524ea1d782b07fef536ba6_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&530& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic3.zhimg.com/v2-d524ea1d782b07fef536ba6_r.jpg&&&/figure&&p&&br&&/p&&p&而 React Native 的渲染原理是将 JS/JSX 理解成 Virtual DOM,然后调用各自平台的 Widget 。由于不同的平台,底层的 Widget 表现是不同的,因此使用上可能会存在差异。这也是 React Native 为人诟病的一点。&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-313dd17ea39b2b7c02bab73e39554b4a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&455& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic3.zhimg.com/v2-313dd17ea39b2b7c02bab73e39554b4a_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&&b&甜头篇&/b&&/p&&p&&br&&/p&&p&采用 Cocos 作为我们的跨平台开发框架后,我们尝到了不少甜头。&br&&/p&&p&&b&首先是跨平台带来的便利。&/b&我们使用一套代码可以生成到安卓、iOS、Web、微信小游戏等多种平台,并且在多个端达到了高度一致的体验。在 React Native 上经常遇到的 UI 体验不一致的问题,在 Cocos 开发中基本没有遇到过。&/p&&p&&br&&/p&&p&由于Cocos支持构建小游戏版本的应用,所以我们的项目也提供了小游戏版本。上周末已经有很多爸爸在微信小游戏里收到了他们的孩子使用 ABCmouse 制作的贺卡。值得一提的是,小游戏版本是我们两个开发在花了一周左右的时间内移植完成的。这里头主要的移植工作在于接入微信小游戏的登录授权,接入 VideoPlayer 和 InnerAudioContext 以分别支持视频播放和音频播放。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-9de63f28dbd54_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&298& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic1.zhimg.com/v2-9de63f28dbd54_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&&b&第二个甜头是开发效率的提升。&/b&&/p&&p&首先,Cocos 提供了可视化的 Cocos Creator ,使用它来管理和构建工程非常轻松。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-f_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&183& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic4.zhimg.com/v2-f_r.jpg&&&/figure&&p&其次,设计萌妹子也能直接使用 Cocos Creator 编辑动效,输出动效资源给开发,提高协作效率。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-710d4de768e74beb868027fb_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&283& data-rawheight=&227& class=&content_image& width=&283&&&/figure&&p&另外,Cocos Creator 支持直接在浏览器中预览调试场景,节省了大把构建编译的耗时。&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-010d7fcd293deee8460f9_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&294& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-010d7fcd293deee8460f9_r.jpg&&&/figure&&p&&br&&/p&&p&&b&第三个甜头是热更新带来的便利。&/b&&/p&&p&Cocos 同时支持脚本和资源的热更新,这给我们修复线上问题、发布运营活动带来了很大便利。此外,Cocos 的热更新可以做到 hot reload,无需冷重启,很好的保证了用户的体验。并且,Cocos 的热更新支持高度可定制,可以很方便的定制满足业务需要的热更新流程。&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-106bacb254cb8bef6557ed3_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&394& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic4.zhimg.com/v2-106bacb254cb8bef6557ed3_r.jpg&&&/figure&&p&&b&第四个甜头是 Cocos 提供的强大的社区支持。&/b&&/p&&p&Cocos 的开发团队来自中国,有着非常活跃的中文社区。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-11bd99e00b7f45dce6f552_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&529& data-rawheight=&419& class=&origin_image zh-lightbox-thumb& width=&529& data-original=&https://pic3.zhimg.com/v2-11bd99e00b7f45dce6f552_r.jpg&&&/figure&&p&&br&&/p&&p&另外,&b&使用 Cocos 开发小游戏也成了最主要的方式&/b&,可见 Cocos 的受欢迎程度,也侧面证明了这套开发框架的生命力。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-d60f60b986269ade3be6dfd_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2226& data-rawheight=&1248& class=&origin_image zh-lightbox-thumb& width=&2226& data-original=&https://pic2.zhimg.com/v2-d60f60b986269ade3be6dfd_r.jpg&&&/figure&&p&&br&&/p&&p&&b&踩坑篇&/b&&/p&&p&&br&&/p&&p&跨平台开发虽然方便,但是在一些具体的实践中难免也会踩到坑。&br&&/p&&p&首先,Cocos 主要是面向游戏开发的,要使用它来开发应用,少不了需要开发一些 UI 组件。因此,我们在 Cocos 层开发了一系列的通用 UI 组件,包括对话框、选择器、表单、按钮、toast、loading 等组件,这些组件遵循一套规范化的接口标准,使用起来非常便捷灵活。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-4e5201ff55cef70b6ec0d24cfaead6b9_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&242& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-4e5201ff55cef70b6ec0d24cfaead6b9_r.jpg&&&/figure&&p&开发完 UI 组件后,我们发现这些组件的加载也存在问题。和原生应用开发不同,这些UI组件本质上都是挂载在场景里头的节点,如果没有调度的话,可能存在同时弹出多种弹窗和对话框的情况,整个场景就会变得很混乱。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-fb0b32d7935_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&378& data-rawheight=&308& class=&content_image& width=&378&&&/figure&&p&&br&&/p&&p&为了解决这种问题,我们写了一个针对 Cocos 的弹窗调度器,统一由它来调度弹窗,避免了弹窗的混乱。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-c5be8cef3fcad66d33c0fff6f527f579_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&499& data-rawheight=&555& class=&origin_image zh-lightbox-thumb& width=&499& data-original=&https://pic2.zhimg.com/v2-c5be8cef3fcad66d33c0fff6f527f579_r.jpg&&&/figure&&p&&br&&/p&&p&我们接下来遇到的另一个坑是 VideoPlayer 的置顶问题。&/p&&p&&br&&/p&&p&前面提到,Cocos 的场景是在 GL 上绘制的。例如,对于 Android 平台,Cocos 开启了一个 OpenGL 的 SurfaceView 来进行场景绘制。而这个 GLSurfaceView 不能直接支持渲染视频,所以,Cocos 提供了一个 VideoPlayer 组件用于播放视频。这个 VideoPlayer 是独立且置顶的一层。&/p&&p&&br&&/p&&p&这带来的一个问题是:无法在视频上绘制 UI 。&/p&&p&&br&&/p&&p&比如我们希望视频播放器里头能加上我们自定义的按钮、进度条,如果是直接在 Cocos 层对 VideoPlayer 进行封装的话,会发现这些 UI 元素会被视频本身遮盖,达不到定制界面的目的。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-a5adb947fb2ed2ce55a405abd01e9545_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&174& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-a5adb947fb2ed2ce55a405abd01e9545_r.jpg&&&/figure&&p&&br&&/p&&p&最终我们放弃了直接使用 Cocos 提供的 VideoPlayer 组件,而是在底层为各个端开发视频播放器,并各自实现界面的定制。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-c764eac3df5ca_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&171& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic3.zhimg.com/v2-c764eac3df5ca_r.jpg&&&/figure&&p&&br&&/p&&p&视频播放问题解决了,我们又遇到了音频播放的问题。&/p&&p&&br&&/p&&p&由于应用中有非常多的音乐、音效、语音,为了减小包大小,大部分的语音素材放在 CDN 上,需要的时候才从 CDN 上拉取播放。少部分常见的音效会直接打进应用包中。而 Cocos 自带的 AudioEngine 组件在 Native 端只支持本地资源的播放。&/p&&p&&br&&/p&&p&因此,我们又封装了一个跨平台的音频播放器,可以自动根据指定的音频路径决定使用播放方式:&/p&&p&&br&&/p&&ul&&li&对于 Web 端或者 Native 端的本地资源文件,直接使用 AudioEngine 来播放。&/li&&li&对于 Native 端的远程音频,使用 Native 的播放器来播放。&/li&&li&对于小游戏环境,则使用小游戏的 InnerAudioContext 来播放。&br&&/li&&/ul&&p&由于对外的接口只有一套,开发者无需考虑具体的平台和底层播放器的选择。并且可以使用同样的接口来统一管理不同的音频。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-84fd4efd04db35f1f83e7f_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&288& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic3.zhimg.com/v2-84fd4efd04db35f1f83e7f_r.jpg&&&/figure&&p&最后我们遇到的一个比较严重的问题是 local reference table overflow error 问题。&/p&&p&&br&&/p&&p&为了复用 Native 端的能力,我们在 Cocos 层大量地使用反射机制来调用 Native 端提供的方法。然而,我们经常会遇到 local reference table overflow error 错误导致的界面卡死问题。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-6de9c69148e53fec5d06bcb_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&546& data-rawheight=&486& class=&origin_image zh-lightbox-thumb& width=&546& data-original=&https://pic4.zhimg.com/v2-6de9c69148e53fec5d06bcb_r.jpg&&&/figure&&figure&&img src=&https://pic2.zhimg.com/v2-22df5b21d5bdfa30cff25_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&546& data-rawheight=&551& class=&origin_image zh-lightbox-thumb& width=&546& data-original=&https://pic2.zhimg.com/v2-22df5b21d5bdfa30cff25_r.jpg&&&/figure&&p&&br&&/p&&p&最初,我们怀疑是反射调用使用得太频繁导致。因此,我们对诸如打 log、事件上报等 Native 方法进行了频率限制,例如使用缓冲的方法将多个 log 合并后再打印。然而,虽然这个做法减少了界面卡死的发生,但依然没有彻底杜绝问题的再次出现,就像是一个定时炸弹一样,威胁着我们应用的稳定性。&/p&&p&&br&&/p&&p&通过阅读引擎的代码,我们发现 Cocos 的引擎在反射阶段处理字符串参数时,使用了 NewStringUTF() 方法将其转换为 JNI 层的字符串,然而在调用执行完成后并没有相应地使用 DeleteLocalRef() 释放该字符串的引用,从而导致了引用表的溢出。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-f3eaffd2fccc3ba70eff93a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&496& data-rawheight=&790& class=&origin_image zh-lightbox-thumb& width=&496& data-original=&https://pic3.zhimg.com/v2-f3eaffd2fccc3ba70eff93a_r.jpg&&&/figure&&p&&br&&/p&&p&了解到这个原因后,我们给 Cocos 的引擎提交了一个 pull request,修复了这个问题。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-c2cd30f45f7afc7e04dfa2d_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&318& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-c2cd30f45f7afc7e04dfa2d_r.jpg&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-e13c8902dfd1abcda08dc3a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&321& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic3.zhimg.com/v2-e13c8902dfd1abcda08dc3a_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&&b&优化篇&/b&&/p&&p&&br&&/p&&p&虽然 Cocos 比起纯 Hybrid 的方案在性能上已经占据了优势,但是比起 native 还是有一些差距的。下面就说说&b&我们在开发过程中尝试过的一些优化,让我们的应用做到接近原生的体验。&/b&&/p&&p&&br&&/p&&p&1.高性能的 ScrollView&/p&&p&官方 ScrollView 组件需要配合 layout 组件,当一次加载大量的子节点组件,或者分帧加载单个子节点组件时,初始化 ScrollView 节点视图会比较慢,在加载完成前存在拖动掉帧的问题。另外,一次性加载所有节点,也会导致内存资源的浪费。&br&&/p&&p&下图这个场景是 ABCmouse 里的二级资源页,由于一次性加载了太多子节点,当屏幕滚动时,帧率降到了 8 fps 左右,给人的感受是非常卡顿。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-0eb8ea906736dba985d7_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&218& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic4.zhimg.com/v2-0eb8ea906736dba985d7_r.jpg&&&/figure&&p&&br&&/p&&p&我们对 ScrollView 进行了重写,基本的优化思路是:一次仅加载页面可容纳的少量数目子节点。并在滚动过程中,回收不可视的子节点组件并重用。&/p&&p&具体来说,ScrollView 大多数情况下表现为列表组件和宫格组件,以列表组件为例,可以根据子节点数目和子节点大小,计算出整个 ScrollView 内容的宽高,同时计算出屏幕可视区域最多可以容纳的子节点行数 rows,加载时仅加载 rows + 2 个子节点组件,其中添加的 2 个字节点组件作为滚动回收缓冲。&/p&&p&下图是对上述思路的图例。当手势向上,内容往下滚动时,一旦最上排的子节点组件不可视,就立马将它们回收掉并将其重用于将要渲染的子节点组件中。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-32d5b3daab41a4e0e5710_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&303& data-rawheight=&298& class=&content_image& width=&303&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-b245f8ef8d953728cfa5e36e0eae56c7_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&303& data-rawheight=&312& class=&content_image& width=&303&&&/figure&&p&&br&&/p&&p&这么做的优点在于:一次仅加载页面可容纳的少量数目子节点,并且逐帧加载,能极大提升展示和滚动性能,另外大大减少了内存占用。&/p&&p&经过优化后,不管二级资源页场景里有多少元素需要展示,整体的帧率都维持在 60 fps 左右,非常流畅。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-c54ff01af_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&226& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic4.zhimg.com/v2-c54ff01af_r.jpg&&&/figure&&p&&br&&/p&&p&2.内存优化&/p&&p&内存占用过高也是 Cocos 开发过程中很容易遇到的问题。如果没有优化好内存占用,很可能就会引发黑屏或者 OOM。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-2bdfedfcf630108_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&305& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic1.zhimg.com/v2-2bdfedfcf630108_r.jpg&&&/figure&&p&要优化内存占用,有几个思路:&/p&&p&&br&&/p&&p&&b&第一个思路是把内存消耗大以及没有回收的元凶先找出来对症下药。&/b&&/p&&p&&br&&/p&&p&于是,我们仿照 Cocos 的监视器也写了一个内存监视器,利用它来找出疑似存在内存泄漏的场景。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-f9d020e72cd88a9a62a484a806be273c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&190& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic1.zhimg.com/v2-f9d020e72cd88a9a62a484a806be273c_r.jpg&&&/figure&&p&&br&&/p&&p&对于每一个场景,我们也对每个节点的内存占用做了一个排名,找出靠前的,分析是否合理,并进行针对性的优化。比如把原图缩小,把无需透明像素的png图转换成JPG图,等等。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-47fcbecb679a7d7aa2ba3f_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&250& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-47fcbecb679a7d7aa2ba3f_r.jpg&&&/figure&&p&&br&&/p&&p&&b&第二个思路是为图片渲染开启纹理压缩,从而大幅度降低图片渲染的内存占用。&/b&Cocos 提供了 ETC1、PVR 等几种纹理压缩方案,其中,PVR 兼容性最好,内存消耗也最低,但是质量较差;ETC1 不支持 iOS 的低端机型,质量也较差。我们又对 Cocos2d-x 进行扩展,增加了 ETC2 纹理压缩,这种方案的优势比起 ETC1 而言,压缩质量更好。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-293d2ba9d0d829e28d8f643a85a003f4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&141& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic1.zhimg.com/v2-293d2ba9d0d829e28d8f643a85a003f4_r.jpg&&&/figure&&p&&br&&/p&&p&下图可以看到 ETC2 和 PVR 压缩质量和内存占用的直观对比。对比原图,我们可以看出 ETC2 的压缩结果与原图相差不大,但内存减少了 75% 。而 PVR 的压缩结果相比 ETC2 言在细节方面少了很多,内存则减少了 87.5% 。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-ec2f21d6fb8ff3a7b7b32_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&184& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic3.zhimg.com/v2-ec2f21d6fb8ff3a7b7b32_r.jpg&&&/figure&&p&针对兼容性问题,我们设计了一种混合纹理压缩方案:对于高质量要求的纹理,如果该机型能支持ETC2,就使用ETC2纹理压缩;如果不支持,就将该纹理进行大小减半压缩;对于低质量要求的纹理,使用兼容性好的PVR纹理压缩。单图渲染的内存消耗可以降低接近 75%~87.5%。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-da5c1d3de08c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&193& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic1.zhimg.com/v2-da5c1d3de08c_r.jpg&&&/figure&&p&纹理压缩是一项耗时的任务,所以我们把这项任务放在项目构建完后进行,而不是在客户端运行的时候才动态压缩。&/p&&p&&br&&/p&&p&我们编写了一个扩展工具,在构建完成后自动进行纹理压缩任务。后面我们发现这个工具压缩完一遍纹理要花费大概3分钟的时间,我们又改进成了增量压缩的方式,一次压缩任务缩短到10秒左右。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-97891abbb883d2e22e0dac_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&127& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic1.zhimg.com/v2-97891abbb883d2e22e0dac_r.jpg&&&/figure&&p&&br&&/p&&p&3.drawcall优化&/p&&p&每一帧的渲染耗时直接影响到整个应用的性能,而和渲染耗时相关的操作是 drawcall 。&/p&&p&&br&&/p&&p&什么是 drawcall 呢?我们可以看这张图来了解一下。在一帧的渲染过程中,场景会先被解析成场景树。场景树的每一个节点依次加入渲染队列中等待交付 GPU 渲染。GPU 接收渲染指令并执行的操作就叫做一次 drawcall。在一帧里头,drawcall 越少,性能当然就越好。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-af521d805bddf4ccee5af1de1162210a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&374& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic3.zhimg.com/v2-af521d805bddf4ccee5af1de1162210a_r.jpg&&&/figure&&p&Cocos 针对 drawcall 优化已经提供了一种自动合并技术:比如,上图中的渲染指令 1、2 来自贴图 A,3、4 来自贴图 B ,5、6、7 来自贴图 C,这些指令会被分别合并优化,最终只产生 3 次 drawcall。我们要做的就是利用好这个自动合并技术。&/p&&p&&br&&/p&&p&首先可以找出浪费 drawcall 的节点对症下药。一般可以通过把节点的 active 属性设为 false 看看 drawcall 有没有大量减少来判断。&/p&&p&&br&&/p&&p&接下来我们可以利用好 Cocos 的合并技术。&/p&&ul&&li& 对于静态的 Sprite ,可以使用合并图集来减少 drawcall 。例如使用 Cocos Creator 自带的 AutoAtlas 或者第三方工具 TexturePacker 。&/li&&li&文本的动态绘制也是 drawcall 浪费的重灾区。对于 Label,可以使用 BMFont 位图字体来取代普通文本,减少 drawcall 。&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-e26fe0febb78bce69b6324a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&277& data-rawheight=&303& class=&content_image& width=&277&&&/figure&&figure&&img src=&https://pic3.zhimg.com/v2-ec63f2caf554ebf3a81d2_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&608& data-rawheight=&398& class=&origin_image zh-lightbox-thumb& width=&608& data-original=&https://pic3.zhimg.com/v2-ec63f2caf554ebf3a81d2_r.jpg&&&/figure&&p&&br&&/p&&p&目前这套优化方案还不能满足动态资源和动画的优化,我们也期待 Cocos 能够把 batching 技术做得更完善。&/p&&p&&br&&/p&&p&另外,还有另外一个需要注意的地方:小心避免跨层切换合图。Cocos 是按照节点层级顺序依次提交渲染指令的,如果不注重层级顺序,可能会导致贴图的切换从而浪费不必要的 drawcall 。&/p&&p&&br&&/p&&p&例如,下图中的渲染指令 4 使用的是贴图 C,直接卡在了渲染指令 3 和 5 之间,导致贴图 B 的渲染指令没法合并,从而浪费了多余的 drawcall。通过调整节点层级可以避免这个问题。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-91f2e8fe3492_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&326& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic3.zhimg.com/v2-91f2e8fe3492_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&4.Hybrid 页面优化&/p&&p&我们的应用里头目前依然存在一些原来的版本遗留下来的 H5 页面构成的场景,对于这些 H5 页面,我们也使用了一些比较常规的 Hybrid 优化技术,来达到首屏直出的要求。&/p&&p&&br&&/p&&p&因为已经有很多现有的优化方案了,所以这一块我并不打算细讲。简单为大家罗列几个技术点吧:&/p&&p&&br&&/p&&ul&&li&一个是使用离线缓存,对一些常用的 H5 场景,也可以离线打包进应用里头,优化首次启动速度。&/li&&li&一个是并行加载在 WebView 启动的同时并行地去拉资源,这样可以避免等待 WebView 初始化耗时对页面加载的影响。另外,还可以对一些 H5 页面进行预加载,减少等待。&/li&&li&一个是可以对页面进行少量标注,只增量更新需要动态变的部分。&/li&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-a98cda7f0f60fe253fe573_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&378& data-rawheight=&348& class=&content_image& width=&378&&&/figure&&p&通过这一系列的优化,我们的应用里头的 H5 页面的加载耗时也能够控制在 1 秒以内。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-a4590dffb_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&378& data-rawheight=&229& class=&content_image& width=&378&&&/figure&&p&&br&&/p&&p&&b&三、整体效果对比
&/b&&/p&&p&&br&&/p&&p&最后我们来看一下整体的改造效果:&br&&/p&&p&项目整体的 Cocos 化率目前占到了 56%,剩下的还有 40% 的 H5 的页面(主要是一些小游戏),还有像视频这种 native 场景。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-bf72c9df803f17efa3b219_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&348& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-bf72c9df803f17efa3b219_r.jpg&&&/figure&&p&对比原来的场景启动耗时,经过一系列改造和优化后的场景都能控制在 1 秒内启动。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-45ad00e29ea515fea08844ad_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&283& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-45ad00e29ea515fea08844ad_r.jpg&&&/figure&&p&&br&&/p&&p&直接看数据不够直观,我们可以看一下原来加载耗时最长的一个场景,经过改造后做到了秒开。&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-7fea84c85c2f7fa462ca27c43a8f3ef3_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&460& data-rawheight=&237& data-thumbnail=&https://pic4.zhimg.com/v2-7fea84c85c2f7fa462ca27c43a8f3ef3_b.jpg& class=&origin_image zh-lightbox-thumb& width=&460& data-original=&https://pic4.zhimg.com/v2-7fea84c85c2f7fa462ca27c43a8f3ef3_r.jpg&&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-c9f7bbdfb136a3a410cf9ec50f9bdbc8_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&460& data-rawheight=&237& data-thumbnail=&https://pic1.zhimg.com/v2-c9f7bbdfb136a3a410cf9ec50f9bdbc8_b.jpg& class=&origin_image zh-lightbox-thumb& width=&460& data-original=&https://pic1.zhimg.com/v2-c9f7bbdfb136a3a410cf9ec50f9bdbc8_r.jpg&&&/figure&&p&&br&&/p&&p&而腾讯版本的包大小也比原来的版本小了 64% 。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-a28a3dc20f0f30a2d53ae3_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&266& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic4.zhimg.com/v2-a28a3dc20f0f30a2d53ae3_r.jpg&&&/figure&&p&&br&&/p&&p&欢迎扫码体验新版本的 ABCmouse :&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-f8a1c7a7c47a54d1bdcd45a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&378& data-rawheight=&378& class=&content_image& width=&378&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&&br&&/p&&p&&b&更多精彩内容&/b&&/p&&a href=&http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMjM5ODAxNTM2NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D7fa3a33f8b34be306e2d41%26chksm%3Dbda298a18ad511b709d7eaec5f8f3ba28c6cdbf13edc5d03479daa885f6a10c9233%26scene%3D21%23wechat_redirect& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-2d9ceed78978badf52f685b50ced44c6_ipico.jpg& data-image-width=&358& data-image-height=&358& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&用 Creator 制作一款重力小游戏(水友分享)&/a&&a href=&http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMjM5ODAxNTM2NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D1bf288b74bdc448c1bb4e%26chksm%3Dbda298b08ad511a6f9d42ce1ca52ca8055acb4850edb848a8e147ef1d38d2d02b9%26scene%3D21%23wechat_redirect& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-2d9ceed78978badf52f685b50ced44c6_ipico.jpg& data-image-width=&358& data-image-height=&358& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&自制 Creator 地图编辑器,让地图设计变得更灵活&/a&&a href=&http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMjM5ODAxNTM2NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D9b90df09eb8f5bab0b1d2547cbd8ba99%26chksm%3Dbda298fb8ad511ed40c4c5d38da918d71d29d819d1bb66d34daf6f%26scene%3D21%23wechat_redirect& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-2d9ceed78978badf52f685b50ced44c6_ipico.jpg& data-image-width=&358& data-image-height=&358& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Cocos 3D 引擎即将发布,我们在行动&/a&&a href=&http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMjM5ODAxNTM2NA%3D%3D%26mid%3D%26idx%3D1%26sn%3Dd01deed0db9ae1e1fa2dbb%26chksm%3Dbdaee151a4fd2f46012e7abe31961bf%26scene%3D21%23wechat_redirect& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-2d9ceed78978badf52f685b50ced44c6_ipico.jpg& data-image-width=&358& data-image-height=&358& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Creator V1.9.2 正式版本发布&/a&&a href=&http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMjM5ODAxNTM2NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D39d47deebae8%26chksm%3Dbdaee388a8fe0a77aa67edcbd7b3d51c2ac1bdeb0a5eab9%26scene%3D21%23wechat_redirect& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-2d9ceed78978badf52f685b50ced44c6_ipico.jpg& data-image-width=&358& data-image-height=&358& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&稀缺物种CP聚会:谁说我们死了?&/a&&p&&/p&
原文发布于 Cocos 官方公众号 2018 年 6 月 GMTC 全球移动技术大会在北京举办,大会旨在通过聚焦前沿技术与时间经验帮助参会者了解移动开发、前端领域最新的技术趋势与最佳实践,专为中国3-8岁儿童…
&p&很多游戏都有火车关卡,看上去是角色在飞驰的火车上战斗,特别带感。&/p&&p&但为了制作起来更简单,这一类火车关卡往往是采用&b&火车不动、场景动&/b&的骗人技巧,让场景向后不断移动,给人一种火车向前行驶的错觉。&/p&&p&不过这样骗人也有缺陷。那就是火车只能走直线,玩家很容易发觉这趟火车跑起来四平八稳,真实感上不免要打些折扣。&/p&&p&直到2009年《神秘海域2》那个经典的火车关卡面世。&/p&&p&这一关里,火车依次通过了森林、湖泊、隧道、雪山四个场景,道路蜿蜒曲折,脚本演出和场景过渡都非常自然,营造出强烈的真实感。神海2虽诞生于09年,但这一段就是放在现在也相当出彩,称得上业界标杆。&/p&&p&那么,神海2火车关到底是怎么做出来的呢?我社最近的一篇文章就解释了这个问题。&/p&&a href=&//link.zhihu.com/?target=http%3A//www.yystv.cn/p/3008& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic2.zhimg.com/v2-acda05b9c6fcadd6ex120.jpg& data-image-width=&1000& data-image-height=&500& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《神秘海域2》当年惊世骇俗“火车关”是怎么做出来的? - 游研社&/a&&a class=&video-box& href=&//link.zhihu.com/?target=https%3A//www.zhihu.com/video/992064& target=&_blank& data-video-id=&& data-video-playable=&true& data-name=&& data-poster=&https://pic4.zhimg.com/80/v2-7c737a9cad977ef071cf3_b.jpg& data-lens-id=&992064&&
&img class=&thumbnail& src=&https://pic4.zhimg.com/80/v2-7c737a9cad977ef071cf3_b.jpg&&&span class=&content&&
&span class=&title&&&span class=&z-ico-extern-gray&&&/span&&span class=&z-ico-extern-blue&&&/span&&/span&
&span class=&url&&&span class=&z-ico-video&&&/span&https://www.zhihu.com/video/992064&/span&
&/a&&p&起因是YouTube上的一位游戏播主Freako最近制作了一期视频。他使用游戏中的调试菜单还原了神海2火车关真正的运行方式,揭示了藏在这个经典关卡背后的设计思路。&/p&&p&Freako通过调整摄像机的位置发现,神海2里的火车是实际运动的。&b&它在一个巨大的场景中沿着环形轨道不断循环,以这种方式解决了静态火车只能走直线的问题。&/b&&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-1f2f312cd962f1da9735e0_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&323& data-rawheight=&188& class=&content_image& width=&323&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/50/v2-1b98ac9ffdd0b5fed58e314e_b.jpg& data-size=&normal& data-rawwidth=&750& data-rawheight=&420& class=&origin_image zh-lightbox-thumb& width=&750& data-original=&https://pic2.zhimg.com/50/v2-1b98ac9ffdd0b5fed58e314e_r.jpg&&&figcaption&雪山场景也是一样的&/figcaption&&/figure&&p&接着,他又加速了游戏时间,发现&b&游戏场景不会随着时间推移而改变,四个场景并非直接相连&/b&。&/p&&p&火车首先会在一个巨大的环形轨道中不断循环,只要玩家不在火车中前进,场景就不会变化。而且这个场景非常大,火车跑上一圈需要很长时间,玩家就很难发现场景其实是重复的。&/p&&figure&&img src=&https://pic4.zhimg.com/50/v2-bc755cbedc_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&323& data-rawheight=&188& class=&content_image& width=&323&&&/figure&&p&&br&&/p&&p&但这样一来,就引出了另一个问题。如果这4个场景不是连在一起的,场景之间的切换如何能不被玩家看出破绽呢?&/p&&p&Freako再次通过调整机位找到了答案。&/p&&p&&b&顽皮狗将切换场景的触发点精妙地放置在过场动画中&/b&,比如从湖泊场景切换到隧道场景这里,会有一段德雷克开门的动画,此时以玩家的视角,是看不到外界环境的。就在这一瞬间,场景开始切换。&/p&&figure&&img src=&https://pic3.zhimg.com/50/v2-84dd7d54a5d995ef2557d_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&323& data-rawheight=&188& class=&content_image& width=&323&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/50/v2-d7e6c465b711dd9ab37c_b.jpg& data-size=&normal& data-rawwidth=&281& data-rawheight=&174& class=&content_image& width=&281&&&figcaption&玩家视角是啥也看不见的&/figcaption&&/figure&&figure&&img src=&https://pic4.zhimg.com/50/v2-5bdb20ce474_b.jpg& data-size=&normal& data-rawwidth=&281& data-rawheight=&174& class=&content_image& width=&281&&&figcaption&此时切换完毕,进入隧道&/figcaption&&/figure&&p&几乎每一次场景切换都会伴随过场动画,而此时,火车、飞机、角色的位置都是固定的,玩家的位置对动画没有任何影响。由于衔接和过渡做得好,玩家很难看出破绽。&/p&&p&比如从森林进入湖泊:&/p&&figure&&img src=&https://pic1.zhimg.com/50/v2-d54a9d295f17816fdeab92_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&281& data-rawheight=&174& class=&content_image& width=&281&&&/figure&&p&比如从隧道进入雪山:&/p&&figure&&img src=&https://pic4.zhimg.com/50/v2-a2fc1fc42639b6dcf2b035e892de2865_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&281& data-rawheight=&174& class=&content_image& width=&281&&&/figure&&p&想要实现这一套东西,远没有看上去那么简单。顽皮狗的一位负责人曾在GDC2010的演讲中提到,&b&由于火车关没有采用常规的静态关卡,从角色移动、敌人AI到武器瞄准坐标等基础设计几乎全都要推倒重来。&/b&&/p&&p&为此,他们甚至开发出了一套名为动态物体遍历系统的技术,专门用于运动场景中。这一系列的复杂设计也使

我要回帖

更多关于 练手速的小游戏 的文章

 

随机推荐