如何改进unity手游3d手游启动速度

流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏《Shadow Blade》已经将在标准iPhone和iPad设备上实现每秒60帧视为一个重要目标。
以下是我们在紧凑的优化过程中提升游戏运行性能,并实现目标帧率时需要考虑的事项。
当基本游戏功能到位时,就要确保游戏运行表现能够达标。我们衡量游戏运行表现的一个基本工具是Unity内置分析器以及Xcode分析工具。使用Unity分析器来分析设备上的运行代码真是一项宝贵的功能。
我们总结了这种为将目标设备的帧率控制在60fps而进行衡量、调整、再衡量过程的中相关经验。
shadow blade()
一、遇到麻烦时要调用“垃圾回收器”(Garbage Collector,无用单元收集程序,以下简称GC)
由于具有C/C++游戏编程背景,我们并不习惯无用单元收集程序的特定行为。确保自动清理你不用的内存,这种做法在刚开始时很好,但很快你就公发现 自己的分析器经常显示CPU负荷过大,原因是垃圾回收器正在收集垃圾内存。这对移动设备来说尤其是个大问题。要跟进内存分配,并尽量避免它们成为优先数, 以下是我们应该采取的主要操作:
1.移除代码中的任何字符串连接,因为这会给GC留下大量垃圾。
2.用简单的“for”循环代替“foreach”循环。由于某些原因,每个“foreach”循环的每次迭代会生成24字节的垃圾内存。一个简单的循环迭代10次就可以留下240字节的垃圾内存。
3.更改我们检查游戏对象标签的方法。用“if (go.CompareTag (“Enemy”)”来代替“if (go.tag == “Enemy”)” 。在一个内部循环调用对象分配的标签属性以及拷贝额外内存,这是一个非常糟糕的做法。
4.对象库很棒,我们为所有动态游戏对象制作和使用库,这样在游戏运行时间内不会动态分配任何东西,不需要的时候所有东西反向循环到库中。
5.不使用LINQ命令,因为它们一般会分配中间缓器,而这很容易生成垃圾内存。
二、谨慎处理高级脚本和本地引擎C++代码之间的通信开销。
所有使用Unity3D编写的游戏玩法代码都是脚本代码,在我们的项目中是使用Mono执行时间处理的C#代码。任何与引擎数据的通信需求都要有一个进入高级脚本语言的本地引擎代码的调用。这当然会产生它自己的开销,而尽量减少游戏代码中的这些调用则要排在第二位。
1.在这一情景中四处移动对象要求来自脚本代码的调用进入引擎代码,这样我们就会在游戏玩法代码的一个帧中缓存某一对象的转换需求,并一次仅向引擎发送一个请求,以便减少调用开销。这种模式也适用于其他相似的地方,而不仅局限于移动和旋转对象。
2.将引用本地缓存到元件中会减少每次在一个游戏对象中使用 “GetComponent” 获取一个元件引用的需求,这是调用本地引擎代码的另一个例子。
三、物理效果
1.将物理模拟时间步设置到最小化状态。在我们的项目中就不可以将让它低于16毫秒。
2.减少角色控制器移动命令的调用。移动角色控制器会同步发生,每次调用都会耗损极大的性能。我们的做法是缓存每帧的移动请求,并且仅运用一次。
3.修改代码以免依赖“ControllerColliderHit” 回调函数。这证明这些回调函数处理得并不十分迅速。
4.面对性能更弱的设备,要用skinned mesh代替physics cloth。cloth参数在运行表现中发挥重要作用,如果你肯花些时间找到美学与运行表现之间的平衡点,就可以获得理想的结果。
5.在物理模拟过程中不要使用ragdolls,只有在必要时才让它生效。
6.要谨慎评估触发器的“onInside”回调函数,在我们的项目中,我们尽量在不依赖它们的情况下模拟逻辑。
7.使用层次而不是标签。我们可以轻松为对象分配层次和标签,并查询特定对象,但是涉及碰撞逻辑时,层次至少在运行表现上会更有明显优势。更快的物理计算和更少的无用分配内存是使用层次的基本原因。
8.千万不要使用Mesh对撞机。
9.最小化碰撞检测请求(例如ray casts和sphere checks),尽量从每次检查中获得更多信息。
四、让AI代码更迅速
我们使用AI敌人来阻拦忍者英雄,并同其过招。以下是与AI性能问题有关的一些建议:
1.AI逻辑(例如能见度检查等)会生成大量物理查询。可以让AI更新循环设置低于图像更新循环,以减少CPU负荷。
五、最佳性能表现根本就不是来自代码!
没有发生什么情况的时候,就说明性能良好。这是我们关闭一切不必要之物的基本原则。我们的项目是一个侧边横向卷轴动作游戏,所以如果不具有可视性时,就可以关闭许多动态关卡物体。
1.使用细节层次的定制关卡将远处的敌人AI关闭。
2.移动平台和障碍,当它们远去时其物理碰撞机也会关闭。
3.Unity内置的“动画挑选”系统可以用来关闭未被渲染对象的动画。
4.所有关卡内的粒子系统也可以使用同样的禁用机制。
六、回调函数!那么空白的回调函数呢?
要尽量减少Unity回调函数。即使敌人回调函数存在性能损失。没有必要将空白的回调函数留在代码库中(有时候介于大量代码重写和重构之间)。
七、让美术人员来救场
在程序员抓耳挠腮,绞尽脑汁去想该如何让每秒运行更多帧时,美术人员总能神奇地派上大用场。
1.共享游戏对象材料,令其在Unity中处于静止状态,可以让它们绑定在一起,由此产生的简化绘图调用是呈现良好移动运行性能的重要元素。
2.纹理地图集对UI元素来说尤其有用。
3.方形纹理以及两者功率的合理压缩是必不可少的步骤。
4.我们的美术人员移除了所有远处背景的网格,并将其转化为简单的2D位面。
5.光照图非常有价值。
6.我们的美术人员在一些关口移除了额外顶点。
7.使用合理的纹理mip标准是一个好主意(游戏邦注:要让不同分辨率的设备呈现良好的帧率时尤其如此)。
8.结合网格是美术人员可以发挥作用的另一个操作。
9.我们的动画师尽力让不同角色共享动画。
10.要找到美学/性能之间的平衡,就免不了许多粒子效果的迭代。减少发射器数量并尽量减少透明度需求也是一大挑战。
八、要减少内存使用
使用大内存当然会对性能产生负面影响,但在我们的项目中,我们的iPod由于超过内存上限而遭遇了多次崩溃事件。我们的游戏中最耗内存的是纹理。
1.不同设备要使用不同的纹理大小,尤其是UI和大型背景中的纹理。《Shadow Blade》使用的是通用型模板,但如果在启动时检测到设备大小和分辨率,就会载入不同资产。
2.我们要确保未使用的资产不会载入内存。我们必须迟一点在项目中找到仅被一个预制件实例引用,并且从未完全载入内存中实例化的资产。
3.去除网格中的额外多边形也能实现这一点。
4.我们应该重建一些资产的生周期管理。例如,调整主菜单资产的加载/卸载时间,或者关卡资产、游戏音乐的有效期限。
5.每个关卡都要有根据其动态对象需求而量身定制的特定对象库,并根据最小内存需求来优化。对象库可以灵活一点,在开发过程中包含大量对象,但知道游戏对象需求后就要具体一点。
6.保持声音文件在内存的压缩状态也是必要之举。
加强游戏运行性能是一个漫长而具有挑战性的过程,游戏开发社区所分享的大量知识,以及Unity提供的出色分析工具为《Shadow Blade》实现目标运行性能提供了极大帮助。(本文为游戏邦/编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦)
阅读(...) 评论() &4593人阅读
Unity3D移动端(7)
首先作为一篇VR手游开发笔记,第一章和VR没有任何关系,单纯讲环境配置
本章主要讲述怎样从零开始搭建android手机游戏开发环境,只用一台windows系统的PC机,将第一个示例工程成功运行在android虚拟机上。与单纯的app开发不同的是,游戏开发不再使用eclipse作为开发平台,游戏制作需要一个处理光照、特效、动画、物理效果等等的开发平台,即引擎,通俗来讲,引擎就是一个做菜的大锅,而组成游戏的各个元素(3D模型,动画特效,碰撞,音效,脚本等等)就是炒锅中的菜,经过引擎的处理,形成一款完整的游戏。
作为时下手游开发的主流,这里我们使用Unity3D作为游戏引擎。Unity5.1以后,Unity开始提供了对虚拟现实的第一方开发支持,作为VR手游开发,它再适合不过了。
1.Unity下载安装
Unity官方历史版本下载
注意!!这里千万不要直接下载Unity编辑器,而是下载Unity安装程序,然后通过安装程序去下载资源,因为直接下载的Unity编辑器是不带Android模块的。
下载完以后,打开,一路点击next,到达如下图所示界面,注意要勾选Android Build Support(同理如果是ios开发就勾选ios Build Support,当然对于安卓开发没必要勾选)
点击next。
出现下图所示,解释一下,上面两项是选择下载好的文件在安装完成后是否被删除,默认选第一项,然后选择好你Unity的安装位置,点击next,开始下载。
耐心等待下载完成后,还要对Unity进行破解,windows有通用的破解工具,附下载地址
选择好Unity的路径,点击PATCH按钮破解,完成后,注册一个Unity账号就可以打开Unity了。
2.Android环境搭建
android环境搭建是一个复杂的工作,先什么都不要干,下载两个东西
Windows x86版对应32位系统
Windows x64版对应64位系统
有两个选择,可以选择下载Android Studio&
也可以下载最原始的集合了eclipse和SDK的压缩包ADT Bundle
windows 64位系统
windows 32位系统
在教程里使用的是ADT Bundle。
在非系统盘新建两个文件,一个命名JDK,一个命名ADT
将刚刚下载完毕的JDK安装至JDK文件目录下,ADT安装至ADT文件目录下
解释一下:首先JDK全称Java Development Kit是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。Android上层是用java编写的,所以java开发工具包是必要的。ADT全称AndroidDevelopTool,他包含SDK和eclipse。SDK全称Software Development Kit,一般都是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。通俗一点讲,就是,要开发安卓程序,就要有安卓操作系统。
打开ADT文件夹,运行SDK Manager,在上方工具栏点击Tools,选择Options项,按下图所示填写完毕
下载Android系统的API是要连接谷歌服务器的,中国的关系,google的连接速度几乎没有,所以如下图填写下载设置,可以大幅改善速度。
接下来需要下载SDK中具体的某些开发工具了
首先,Tools栏下,下载勾选的这三个,安装完成后,右边显示Installed,就可以了
另外需要下载一个Android系统,需要下载Android5.0以上,建议下载Android 6.0(API 23)
最后Extra栏目下,下载这两个。其中Google USB Driver不多说了,是usb调试必备的,Intel x86 Emulator Accelerator是模拟器加速的工具。下载完成后还没结束,在ADT安装目录下的sdk/extras/Intel文件夹下找到刚刚装的Intel x86 Emulator Accelerator文件夹,进入以后运行intelhaxm-android安装程序,完成后才全部安装完毕。解释一下,Intel x86 Emulator Accelerator是intel采用的虚拟硬件加速技术,实现android模拟器的加速。强调!!Intel
x86 Emulator Accelerator一定要装,不然后面会出现问题
完成了必要组件的下载,接下来就是添加虚拟机,还是打开SDK manager,点击Tools栏下的Manage AVDs,点击create创建
因为装了intel加速,可以选择Intel Atom(x86)或者Intel Atom(x86_64)的CPU,(如果没装请选择ARM处理器,不然会报错虚拟机启动不了)
target项选择之前安装的安卓系统,RAM的话,如果电脑好的话,可以选择1024,反正这个就是越大虚拟机运行越流畅
最后勾选Use Host GPU项,Use Host GPU是使用主机GPU的意思(必须要勾选,不然程序会自动退出),左边Snapshot键是快照功能,这里没必要勾选,点击ok创建完毕
3.最后的连接步骤
打开Unity,新建工程,出现如下所示界面,这个就是我们现在的游戏场景,我们不做改动,直接将其作为场景保存,点击file-save scene保存到工程目录下
点击Edit-Preference出现下图界面,选择好JDK和SDK的目录
点击file-build settings,点击Add Open Scenes将当前场景添加到要发布的场景列表中,选择Android,点击左下switch Platform,发布平台就切换至安卓了。
继续点击player settings,在右边的inspcter面板里,修改Bundle Identifier和Install Location这两项的值,其中前者com.公司名.作品名 都是自己定义的,可随便写
最后点击build,导出apk文件
导出的时候,SDK当中的一些工具版本过低会报错,只需要更新一下即可
JDK没有配置环境变量会报错,解决方法
上述的Bundle Identifier属性没有修改会报错,按com.自定义公司名.自定义作品名修改好即可
4.运行你的作品
首先需要打开虚拟机,还是在AVD manager 下面,点击start按钮打开创建好的虚拟机
将apk文件拷贝到sdk\platform-tools目录下,打开控制台一直用cd 文件名语句,进入到platform-tools文件下,最后用adb install XX.apk进行安装,完成后,会显示success
最后apk文件就成功安装好了
运行一下,显示的是默认场景的画面
这样整一个环境已经搭建完毕了,当然有真机的同学也可以真机调试这样更加方便
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:20616次
排名:千里之外
原创:27篇
评论:27条
(1)(2)(3)(8)(6)(5)(1)(3)酷勤网 C 程序员的那点事!
当前位置: >
浏览次数:次
俗话说,用户体验不谈性能就是耍流氓。 在PC游戏上的性能问题并没有那么明显,加个内存换个CPU或者刷个主频就能轻松搞定;到了手游时代后情况则显得比较严峻,捉襟见肘的内存使得资源加载时如履薄冰,加上高中低不同配置的机型让性能问题显得更加突出,一个低端机型上的卡顿就可能造成一大批潘坑没У牧魇В獾比晃薹ū缓鍪印
在手游的浪潮之巅,腾讯对于手游品质的要求从1.0到2.0再到现在的3.0,不仅是玩法和内容,在游戏质量的审核上也始终如一的保持着高要求高标准。腾讯游戏的品质管理中心在Unity手游性能上进行了更深层次的挖掘,这是一个腾讯内部非常受欢迎的性能分析产品,无论你是否正在从事Unity相关的工作,听完这个良心产品的故事保证会让你增加90%的魅力值。但在此之前、先要看看你的&性&能到底行不行?
(下文有大量专业术语,有可能引起您的不适,请在家长指导下阅读。)
一.常见的Unity手游性能问题有哪些?
Unity手游的性能问题一直是被业内视为诟病,腾讯公司内部的TDR评审就是一个专门针对技术细节进行专家团评估的环节;早期的TDR评审关注的是内存是否超标、CPU是否饱和、网络流量是否过大等数据,经过近几年手游浪潮的洗礼,现在评审过程中会更加注重细分问题的研究和排查。
如果说左边是玩家经常会遭遇到的表面现象,那右边则是基于Unity引擎深挖后的问题本质。 它们对游戏的具体影响是什么呢?就拿最近比较火的《王者荣耀》来举例,我们有幸参与了它上线前后的几个优化版本的分析,先后遇到过的问题和优化方法主要有下面几个:
由于实时对战游戏的数据包数量巨大,早期版本的帧同步策略会导致比较明显的卡顿,通过进行数据包的合并与优化逐渐解决了卡顿问题;
频繁创建和销毁的小兵对象让CPU爆表了,大量的小兵如果采用实时内存的分配和回收,会产生大量的内存碎片和系统开销,解决方法之一就是采用高效的对象池进行优化,对每个内存对象的状态进行操作即可;
性能分析过程中,发现单人同屏和多人同屏时的开销都很大,通过视野裁剪技术,使得玩家视野外的不必要的特效和渲染可以全部关闭,极大降低了CPU、GPU和内存的开销;
在高中低三档机型上玩游戏时,分别加载不同层次的特效包,这也有助于降低CPU和内存的开销;
游戏内界面采用了UGUI的方式实现,但大量的实时UI变化使得副本内每帧会有230以上的drawcall,导致中低端机型感受到明显卡顿,最终采用UGUI+自研究UI的组合拳,重写了一套紧密结合游戏自身特性的UI来实现战斗血条和浮动文字的效果。
二.手游发布之前的性能分析
近年来,经过若干惨痛的教训之后,业内已经逐渐意识到手游性能已成为了生死存亡的关键,特别是对于做大DAU的手游来说尤为重要。腾讯对于手游性能的测试和监控也是多管齐下,在新版本发布之前会再三确认性能是否符合发布标准,拿王者荣耀这款实时竞技游戏来说,在测试阶段会采集大量的性能数据进行分析,测试经理对各项性能指标进行评估并给出最终质量结论。
如上图所示,首先,功能测试也就是通常所说的人肉测试,用于测试游戏的新、老功能点,测试工程师在工作过程中可以使用进行数据采集;自动化测试则是基于腾讯WeTestgautomator自动化框架来实现,功能类似于Robotium,在无须人力参与的情况下能覆盖到绝大部分技能、角色和关卡;灰度发布指的是在一个很小范围定点推送手游的新版本,并观察运营期的质量情况和玩家反馈。无论是哪种测试方法,在过程中都可以用Cube进行数据采集,在测试完成后,服务器会自动进行数据分析并给出多项性能数据结论;这些性能数据的结论来自于Unity官方的推荐标准值和腾讯游戏海量的经验库,如果同意机器给出的结论则可以巩固当前算法,当然也可以挑战自动分析的结论,帮助后台改进算法,最终版本质量结论还是来自于测试经理的判断。
看到这里是不是有一个疑问:不做性能分析行不行?当然行,并且你的产品照样能发布、能上线,带来的结果就是用户抱怨用户投诉用户流失。生病了、还得老老实实的去看病去吃药;冰冻三尺非一日之寒,一场大病的费用远比日常保养要贵的多,对应测试行业的名言就是&bug发现的越早、修复成本越小&。
三.Cube工具的简介
目前市面上大多数性能工具都还停留在操作系统级别的数据上, 在游戏自身的分析上似乎还缺少点什么,所以腾讯的工程师们觉得还可以往灵魂深处挖一挖,于是就研发了Cube这个手游性能分析工具,可以让用户以最小的成本在真机上进行游戏性能深度分析。常见的游戏质量改进的过程如下图,Cube能帮你完成的是前两个环节,至于第三步、解决问题当然还是要开发人员去改代码了。
通过Cube的深度分析,能够帮助开发者发现当前游戏内分类资源的占用情况。
如上图所示,在资源分析纬度上可以给出如下结论:
&资源使用总量是否在合理范围之内。
&一个场景内的资源重复率。
&资源对象拷贝的数量是否合理。
&场景切换时保留的资源详情。
&网格、纹理、音频、动画、GameObject等资源是否超标。
在性能分析纬度上,以腾讯的TDR标准为例,在高中低三档机型上会有不同的标准,在三档机型中做了自动的筛选和判定,便于开发人员能更加直观的发现问题。(如下图)
首先、在游戏场景内对于FPS、CPU、PSS的变化趋势是需要重点关注的;其次、对于mono这种只增不减的东西,当然也是关注的重点,mono堆内存的不断分配会直接导致PSS内存增长且不可逆;再次、对于和渲染有关的drawcall,也是手游需要关注的性能指标之一,drawcall太高会导致FPS陡降,造成视觉上的卡顿。
四.同类工具对比
MAT(MemoryAnalyzer Tool)的缺点:
l需要导入HPROF文件再分析;
l只能查看java层的内存情况,看不到native堆的详情;
xcodeinstrument的缺点:
l只能用于mac,ios;
l只能查看C++或object C的情况,看不到mono堆的详情;
Unity Profiler的缺点:
l需要单独编译develop版本;
l在PC上执行,没法捕获真机数据;
l内存数据跟实际真机的数据差异很大、多的时候有几十M差距;
l只能看到最近一段时间的数据,看不到总体的详情;
对于Unity大神和开发人员,你更关心的应该是详细的性能数据,都能满足你们。大神会说&我更喜欢看着Unity profiler直接调试啊&,那你还得腾出时间编译一个develop版本、还得重新跑一遍游戏、数据和真机还相差很多,关键是大神哪来那么多时间呢?
所以答案是肯定的,日常测试工作中加入了数据采集和数据分析功能,就可以提高很大的工作效率。
我们常见的产品质量改进流程无非是下面这四步:
1)测试人员发现问题;
2)提bug给开发人员;
3)开发人员编译develop版本;
4)开发人员用Unity profiler定位原因;
用Cube进行游戏测试能帮你省掉后面2个步骤,何乐而不为呢?
通常情况下,开发人员是间隔几个星期甚至几个月才会去做一次性能调优的工作,中间已经隔了N个版本,有很多问题会被埋的很深;基于&问题发现的越早修复成本越小&的硬道理,功能测试人员完全可以用进行日常的版本功能测试,让在后台默默的为你发现各种性能问题。
&即插即用、无须编译无须嵌入SDK、真机运行数据;
&提供mono内存分配信息和mono快照对比;
&能看到整个测试流程中的所有数据,而不仅仅是某一段时间;
&被误操作产生的对象拷贝数量;
&函数开销排名;
&关卡间保留的冗余资源;
五.性能优化的N种武器
作为一个以性能优化为己任的工具类产品,不仅致力于问题的发现和定位,也希望为开发人员提供更多更实用的性能优化方法。
l控制贴图大小,尽量不要超过;
l尽量使用2的n次幂大小的贴图,否则GfxDriver里会有2份贴图;
l尽量使用压缩格式减小贴图大小;
l若干种贴图合并技术;
l去除多余的alpha通道;
l不同设备使用不同的纹理贴图,分层显示;
l尽量控制模型的面数,小于1500会比较合适;
l不同设备使用不同的模型面数;
l尽量保持在30根骨骼内;
l一个网格不要超过3个material;
lN种动画压缩方法;
l尽量减少骨骼数量;
l采用压缩MP3和wav;
资源方面的优化:
l使用Resource.Load方法在需要的时候再读取资源;
l各种资源在使用完成后,尽快用Resource.UnloadAsset和UnloadUnusedAsset卸载掉;
l灵活运用AssetBundle的Load和Unload方法动态加载资源,避免主要场景内的初始化内存占用过高;(实现起来真的很难&)
l采用www加载了AssetBundle后,要用www.Dispose及时释放;
l在关卡内谨慎使用DontDestroyOnLoad,被标注的资源会常驻内存;
代码的优化:
l尽量避免代码中的任何字符串连接,因为这会给GC带来太多垃圾;
l用简单的&for&循环代替&foreach&循环;
l为所有游戏内的动态物体使用内存对象池,可以减少系统开销和内存碎片,复用对象实例,构建自己的内存管理模式,减少Instantiate和Destory;
l尽量不使用LINQ命令,因为它们一般会分配中间缓器,而这很容易生成垃圾内存;
l将引用本地缓存到元件中会减少每次在一个游戏对象中使用 &GetComponent& 获取一个元件引用的需求;
l减少角色控制器移动命令的调用。移动角色控制器会同步发生,每次调用都会耗损较大的性能;
l最小化碰撞检测请求(例如ray casts和sphere checks),尽量从每次检查中获得更多信息;
lAI逻辑通常会生成大量物理查询,建议让AI更新循环设置低于图像更新循环,以减少CPU负荷;
l要尽量减少Unity回调函数,哪怕是空函数也不要留着;(例如空的Update、FixedUpdate函数)
l尽量少使用FindObjectsOfType函数,这个函数非常慢,尽量少用且一定不要在Update里调用;
l千万一定要控制mono堆内存的大小;
性能优化就像海绵中的水,又或是内衣里的肉,挤一挤总会有的。同时,性能优化并不是一劳永逸的工作,而是一个漫长而具有挑战的任务;项目的各个阶段都会有性能上的问题,在用户体验的基础上持续进行打磨,持续保持产品的良好性能才能赢得好口碑。(和保持身体健康是一个道理)
& 相关主题:30326人阅读
Unity3D(20)
刚开始学习Unity3D时间不长,在看各种资料。除了官方的手册以外,其他人的经验也是非常有益的。偶尔看到老外这篇文章,觉得还不错,于是翻译过来和大家共享。原文地址:,下面是译文。
欢迎转载,请注明出处:。另外,欢迎各路高手加入我的QQ群:,切磋交流技术。
关于这些技巧
这些技巧不可能适用于每个项目。
这些是基于我的一些项目经验,项目团队的规模从3人到20人不等;框架结构的可重用性、清晰程度是有代价的——团队的规模和项目的规模决定你要在这个上面付出多少;很多技巧是品味的问题(这里所列的所有技巧,可能有同样好的技术替代方案);一些技巧可能是对传统的Unity开发的一个冲击。例如,使用prefab替代对象实例并不是一个传统的Unity风格,并且这样做的代价还挺高的(需要很多的preffab)。也许这些看起来有些疯狂,但是在我看来是值得的。
1、避免Assets分支
所有的Asset都应该只有一个唯一的版本。如果你真的需要一个分支版本的Prefab、Scene或是Mesh,那你要制定一个非常清晰的流程,来确定哪个是正确的版本。错误的分支应该起一个特别的名字,例如双下划线前缀:__MainScene_Backup。Prefab版本分支需要一个特别的流程来保证安全(详见Prefabs一节)。
2、如果你在使用版本控制的话,每个团队成员都应该保有一个项目的Second Copy用来测试
修改之后,Second Copy和Clean Copy都应该被更新和测试。大家都不要修改自己的Clean Copy。这对于测试Asset丢失特别有用。
3、考虑使用外部的关卡编辑工具
Unity不是一个完美的关卡编辑器。例如,我们使用来创建3D Tile-Based的游戏,这使我们可以获得对Tile友好的工具的益处(网格约束,90度倍数的旋转,2D视图,快速Tile选择等)。从一个XML文件来实例化Prefab也很简单。详见。
4、考虑把关卡保存为XML,而非scene
这是一种很奇妙的技术:
它可以让你不必每个场景都设置一遍;他可以加载的更快(如果大多数对象都是在场景之间共享的)。它让场景的版本合并变的简单(就算是Unity的新的文本格式的Scene,也由于数据太多,而让版本合并变的不切实际)。它可以使得在关卡之间保持数据更简便。
你仍就可以使用Unity作为关卡编辑器(尽管你用不着了)。你需要写一些你的数据的序列化和反序列化的代码,并实现在编辑器和游戏运行时加载关卡、在编辑器中保存关卡。你可能需要模仿Unity的ID系统来维护对象之间的引用关系。
5、考虑编写通用的自定义Inspector代码
实现自定义的Inspector是很直截了当的,但是Unity的系统有很多的缺点:
它不支持从继承中获益;它不允许定义字段级别的Inspector组件,而只能是class类型级别。举个例子,如果没有游戏对象都有一个ScomeCoolType字段,而你想在Inspector中使用不同的渲染,那么你必须为你的所有class写Inspector代码。
你可以通过从根本上重新实现Inspector系统来处理这些问题。通过一些反射机制的小技巧,他并不像看上去那么看,文章底部(日后另作翻译)将提供更多的实现细节。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
6、使用命名的空Game Object来做场景目录
仔细的组织场景,就可以方便的找到任何对象。
7、把控制对象和场景目录(空Game Objec)放在原点(0,0,0)
如果位置对于这个对象不重要,那么就把他放到原点。这样你就不会遇到处理Local Space和World Space的麻烦,代码也会更简洁。
8、尽量减少使用GUI组件的offset
通常应该由控件的Layout父对象来控制Offset;它们不应该依赖它们的爷爷节点的位置。位移不应该互相抵消来达到正确显示的目的。做基本上要防止了下列情况的发生:
父容器被放到了(100,-50),而字节点应该在(10,10),所以把他放到(90,60)[父节点的相对位置]。
这种错误通常放生在容器不可见时。
9、把世界的地面放在Y=0
这样可以更方便的把对象放到地面上,并且在游戏逻辑中,可以把世界作为2D空间来处理(如果合适的话),例如AI和物理模拟。
10、使游戏可以从每个Scene启动
这将大大的降低测试的时间。为了达到所有场景可运行,你需要做两件事:
首先,如果需要前面场景运行产生的一些数据,那么要模拟出它们。
其次,生成在场景切换时必要保存的对象,可以是这样:
myObject = FindMyObjectInScene();
if (myObjet == null)
myObject = SpawnMyObject();
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
11、把角色和地面物体的中心点(Pivot)放在底部,不要放在中间
这可以使你方便的把角色或者其他对象精确的放到地板上。如果合适的话,它也可能使得游戏逻辑、AI、甚至是物理使用2D逻辑来表现3D。
12、统一所有的模型的面朝向(Z轴正向或者反向)
对于所有具有面朝向的对象(例如角色)都应该遵守这一条。在统一面朝向的前提下,很多算法可以简化。
13、在开始就把Scale搞正确
请美术把所有导入的缩放系数设置为1,并且把他们的Transform的Scale设置为1,1,1。可以使用一个参考对象(一个Unity的Cube)来做缩放比较。为你的游戏选择一个世界的单位系数,然后坚持使用它。
14、为GUI组件或者手动创建的粒子制作一个两个面的平面模型
设置这个平面面朝向Z轴正向,可能简化Billboard和GUI创建。
15、制作并使用测试资源
为SkyBox创建带文字的方形贴图;一个网格(Grid);为Shader测试使用各种颜色的平面:白色,黑色,50%灰度,红,绿,蓝,紫,黄,青;为Shader测试使用渐进色:黑到白,红到绿,红到蓝,绿到蓝;黑白格子;平滑的或者粗糙的法线贴图;一套用来快速搭建场景的灯光(使用Prefa);
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
16、所有东西都使用Prefab
只有场景中的“目录”对象不使用Prefab。甚至是那些只使用一次的唯一对象也应该使用Prefab。这样可以在不动用场景的情况下,轻松修改他们。(一个额外的好处是,当你使用时,这可以用来创建稳定的Sprite
17、对于特例使用单独的Prefab,而不要使用特殊的实例对象
如果你有两种敌人的类型,并且只是属性有区别,那么为不同的属性分别创建Prefab,然后链接他们。这可以:
在同一个地方修改所有类型在不动用场景的情况下进行修改
如果你有很多敌人的类型,那么也不要在编辑器中使用特殊的实例。一种可选的方案是程序化处理它们,或者为所有敌人使用一个核心的文件/Prefab。使用一个下拉列表来创建不同的敌人,或者根据敌人的位置、玩家的进度来计算。
18、在Prefab之间链接,而不要链接实例对象
当Prefab放置到场景中时,它们的链接关系是被维护的,而实例的链接关系不被维护。尽可能的使用Prefab之间的链接可以减少场景创建的操作,并且减少场景的修改。
19、如果可能,自动在实例对象之间产生链接关系
如果你确实需要在实例之间链接,那么应该在程序代码中去创建。例如,Player对象在Start时需要把自己注册到GameManager,或者GameManager可以在Start时去查找Player对象。
对于需要添加脚本的Prefab,不要用Mesh作为根节点。当你需要从Mesh创建一个Prefab时,首先创建一个空的GameObject作为父对象,并用来做根节点。把脚本放到根节点上,而不要放到Mesh节点上。使用这种方法,当你替换Mesh时,就不会丢失所有你在Inspector中设置的值了。
使用互相链接的Prefab来实现Prefab嵌套。Unity并不支持Prefab的嵌套,在团队合作中第三方的实现方案可能是危险的,因为嵌套的Prefab之间的关系是不明确的。
20、使用安全的流程来处理Prefab分支
我们用一个名为Player的Prefab来讲解这个过程。
用下面这个流程来修改Player:
复制Player Prefab;把复制出来的Prefab重命名为__Player_Backup;修改Player Prefab;测试一切工作正常,删除__Player_Backup;
不要把新复制的命名为Player_New,然后修改它。
有些情况可能更复杂一些。例如,有些修改可能涉及到两个人,上述过程有可能使得场景无法工作,而所有人必须停下来等他们修改完毕。如果修改能够很快完成,那么还用上面这个流程就可以。如果修改需要花很长时间,则可以使用下面的流程:
第一个人:
复制Player Prefab;把它重命名为__Player_WithNewFeature或者__Player_ForPerson2;在复制的对象上做修改,然后提交给第二个人;
第二个人:
在新的Prefab上做修改;复制Player Prefab,并命名为__Player_Backup;把__Player_WithNewFeature拖放到场景中,创建它的实例;把这个实例拖放到原始的Player Prefab中;如果一切工作正常,则可使删除__Player_Backup和__Player_WithNewF
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
扩展和MonoBehaviourBase
21、扩展一个自己的Mono Behaviour基类,然后自己的所有组件都从它派生
这可以使你方便的实现一些通用函数,例如类型安全的Invoke,或者是一些更复杂的调用(例如random等等)。
22、为Invoke, StartCoroutine and Instantiate&定义安全调用方法
定义一个委托任务(delegate Task),用它来定义需要调用的方法,而不要使用字符串属性方法名称,例如:
public void Invoke(Task task, float time)
Invoke(task.Method.Name, time);
23、为共享接口的组件扩展
有些时候把获得组件、查找对象实现在一个组件的接口中会很方便。
下面这种实现方案使用了typeof,而不是泛型版本的函数。泛型函数无法在接口上工作,而typeof可以。下面这种方法把泛型方法整洁的包装起来。
//Defined in the common base class for all mono behaviours
public I GetInterfaceComponent&I&() where I : class
return GetComponent(typeof(I)) as I;
public static List&I& FindObjectsOfInterface&I&() where I : class
MonoBehaviour[] monoBehaviours = FindObjectsOfType&MonoBehaviour&();
List&I& list = new List&I&();
foreach(MonoBehaviour behaviour in monoBehaviours)
I component = behaviour.GetComponent(typeof(I)) as I;
if(component != null)
list.Add(component);
24、使用扩展来让代码书写更便捷
public static class CSTransform
public static void SetX(this Transform transform, float x)
Vector3 newPosition =
new Vector3(x, transform.position.y, transform.position.z);
transform.position = newP
25、使用防御性的GetComponent()
有些时候强制性组件依赖(通过RequiredComponent)会让人蛋疼。例如,很难在Inspector中修改组件(即使他们有同样的基类)。下面是一种替代方案,当一个必要的组件没有找到时,输出一条错误信息。
public static T GetSafeComponent&T&(this GameObject obj) where T : MonoBehaviour
T component = obj.GetComponent&T&();
if(component == null)
Debug.LogError(&Expected to find component of type &
+ typeof(T) + & but found none&, obj);
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &&
风格
26、避免对同一件事使用不同的处理风格
在很多情况下,某件事并不只有一个惯用手法。在这种情况下,在项目中明确选择其中的一个来使用。下面是原因:
一些做法并不能很好的一起协作。使用一个,能强制统一设计方向,并明确指出不是其他做法所指的方向;团队成员使用统一的风格,可能方便大家互相的理解。他使得整体结构和代码都更容易理解。这也可以减少错误;
几组风格的例子:
协程与状态机(Coroutines vs. state machines);嵌套的Prefab、互相链接的Prefab、超级Prefab(Nested prefabs vs. linked prefabs vs. God prefabs);数据分离的策略;在2D游戏的使用Sprite的方法;Prefab的结构;对象生成策略;定位对象的方法:使用类型、名称、层、引用关系;对象分组的方法:使用类型、名称、层、引用数组;找到一组对象,还是让它们自己来注册;控制执行次序(使用Unity的执行次序设置,还是使用Awake/Start/Update/LateUpdate,还是使用纯手动的方法,或者是次序无关的架构);在游戏中使用鼠标选择对象/位置/目标:SelectionManager或者是对象自主管理;在场景变换时保存数据:通过,或者是在新场景加载时不要销毁的对象;组合动画的方法:混合、叠加、分层;
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
27、维护一个自己的Time类,可以使游戏暂停更容易实现
做一个“Time.DeltaTime”和&&Time.TimeSinceLevelLoad&的包装,用来实现暂停和游戏速度缩放。这使用起来略显麻烦,但是当对象运行在不同的时钟速率下的时候就方便多了(例如界面动画和游戏内动画)。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
28、不要让游戏运行时生成的对象搞乱场景层次结构
在游戏运行时,为动态生成的对象设置好它们的父对象,可以让你更方便的查找。你可以使用一个空的对象,或者一个没有行为的单件来简化代码中的访问。可以给这个对象命名为“DynamicObjects”。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &&
29、使用单件(Singleton)模式
从下面这个类派生的所有类,将自动获得单件功能:
public class Singleton&T& : MonoBehaviour where T : MonoBehaviour
protected static T
Returns the instance of this singleton.
public static T Instance
if(instance == null)
instance = (T) FindObjectOfType(typeof(T));
if (instance == null)
Debug.LogError(&An instance of & + typeof(T) +
& is needed in the scene, but there is none.&);
}单件可以作为一些管理器,例如ParticleManager或者AudioManager亦或者GUIManager。
对于那些非唯一的prefab实例使用单件管理器(例如Player)。不要为了坚持这条原则把类的层次关系复杂化,宁愿在你的GameManager(或其他合适的管理器中)中持有一个它们的引用。对于外部经常使用的共有变量和方法定义为static,这样你可以这样简便的书写“GameManager.Player”,而不用写成“GameManager.Instance.player”。
30、在组件中不要使用public成员变量,除非它需要在inspector中调节
除非需要设计师(策划or美术)去调节的变量,特别是它不能明确表明自己是做什么的变量,不要声明为public。如果在这些特殊情况下,无法避免,则可使用两个甚至四个下划线来表明不要从外部调节它,例如:
public float __aV
31、把界面和游戏逻辑分开
这一条本质上就是指的MVC模式。
所有的输入控制器,只负责向相应的组件发送命令,让它们知道控制器被调用了。举一个控制器逻辑的例子,一个控制器根据玩家的状态来决定发送哪个命令。但是这样并不好(例如,如果你添加了多个控制器,那将会导致逻辑重复)。相反的,玩家对象应该根据当前状态(例如减速、惊恐)来设置当前的速度,并根据当前的面朝向来计算如何向前移动。控制器只负责做他们自己状态相关的事情,控制器不改变玩家的状态,因此控制前甚至可以根本不知道玩家的状态。另外一个例子,切换武器。正确的方法是,玩家有一个函数:“SwitchWeapon(Weapon
newWeapon)”供GUI调用。GUI不应该维护所有对象的Transform和他们之间的父子关系。
所有界面相关的组件,只负责维护和处理他们自己状态相关的数据。例如,显示一个地图,GUI可以根据玩家的位移计算地图的显示。但是,这是游戏状态数据,它不属于GUI。GUI只是显示游戏状态数据,这些数据应该在其他地方维护。地图数据也应该在其他地方维护(例如GameManager)。
游戏玩法对象不应该关心GUI。有一个例外是处理游戏暂停(可能是通过控制Time.timeScale,其实这并不是个好主意)。游戏玩法对象应该知道游戏是否暂停。但是,这就是全部了。另外,不要把GUI组件挂到游戏玩法对象上。
这么说吧,如果你把所有的GUI类都删了,游戏应该可以正确编译。
你还应该达到:在不需要重写游戏逻辑的前提下,重写GUI和输入控制。
32、分离状态控制和簿记变量
簿记变量只是为了使用起来方便或者提高查找速度,并且可以根据状态控制来覆盖。将两者分离可以简化:
保存游戏状态调试游戏状态
实现方法之一是为每个游戏逻辑定义一个”SaveData“类,例如:
[Serializable]
PlayerSaveData
//public for serialisation, not exposed in inspector
//... bookkeeping variables
//Don’t expose state in inspector. State is not tweakable.
private PlayerSaveData playerSaveD
33、分离特殊的配置
假设我们有两个敌人,它们使用同一个Mesh,但是有不同的属性设置(例如不同的力量、不同的速度等等)。有很多方法来分离数据。下面是我比较喜欢的一种,特别是对于对象生成或者游戏存档时,会很好用。(属性设置不是状态数据,而是配置数据,所以我们不需要存档他们。当对象加载或者生成是,属性设置会自动加载。)
为每一个游戏逻辑类定义一个模板类。例如,对于敌人,我们来一个“EnemyTemplate”,所有的属性设置变量都保存在这个类中。在游戏逻辑的类中,定义一个上述模板类型的变量。制作一个敌人的Prefab,以及两个模板的Prefab:“WeakEnemyTemplate”和&StrongEnemyTemplate&。在加载或者生成对象是,把模板变量正确的复制。
这种方法可能有点复杂(在一些情况下,可能不需要这样)。
举个例子,最好使用泛型,我们可以这样定义我们的类:
public class BaseTemplate
public class ActorTemplate : BaseTemplate
public class Entity&EntityTemplateType& where EntityTemplateType : BaseTemplate
EntityTemplateT
public class Actor : Entity &ActorTemplate&
34、除了显示用的文本,不要使用字符串
特别是不要用字符串作为对象或者prefab等等的ID标识。一个很遗憾的例外是动画系统,需要使用字符串来访问相应的动画。
35、避免使用public的数组
举例说明,不要定义一个武器的数组,一个子弹的数组,一个粒子的数组,这样你的代码看起来像这样:
public void SelectWeapon(int index)
currentWeaponIndex =
Player.SwitchWeapon(weapons[currentWeapon]);
public void Shoot()
Fire(bullets[currentWeapon]);
FireParticles(particles[currentWeapon]);
这在代码中还不是什么大问题,但是在Inspector中设置他们的值的时候,就很难不犯错了。
我们可以定义一个类,来封装这三个变量,然后使用一个它的实例数组:
[Serializable]
public class Weapon
public GameO
public ParticleS
} 这样代码看起来很整洁,但是更重要的是,在Inspector中设置时就不容易犯错了。
36、在结构中避免使用数组
举个例子,一个玩家可以有三种攻击形式,每种使用当前的武器,并发射不同的子弹、产生不同的行为。
你可以把三个子弹作为一个数组,并像下面这样组织逻辑:
public void FireAttack()
/// behaviour
Fire(bullets[0]);
public void IceAttack()
/// behaviour
Fire(bullets[1]);
public void WindAttack()
/// behaviour
Fire(bullets[2]);
} 使用枚举值可以让代码看起来更好一点:
public void WindAttack()
/// behaviour
Fire(bullets[WeaponType.Wind]);
但是这对Inspector一点也不好。
最好使用单独的变量,并且起一个好的变量名,能够代表他们的内容的含义。使用下面这个类会更整洁。
[Serializable]
public class Bullets
public Bullet FireB
public Bullet IceB
public Bullet WindB
}这里假设没有其他的Fire、Ice、Wind的数据。
37、把数据组织到可序列化的类中,可以让inspector更整洁
有些对象有一大堆可调节的变量,这种情况下在Inspector中找到某个变量简直就成了噩梦。为了简化这种情况,可以使用一下的步骤:
把这些变量分组定义到不同的类中,并让它们声明为public和serializable;在一个主要的类中,把上述类的实例定义为public成员变量;不用在Awake或者Start中初始化这些变量,因为Unity会处理好它们;你可以定义它们的默认值;
这可以把变量分组到Inspector的分组页签中,方便管理。
[Serializable]
public class MovementProperties //Not a MonoBehaviour!
public float movementS
public float turnSpeed = 1; //default provided
public class HealthProperties //Not a MonoBehaviour!
public float maxH
public float regenerationR
public class Player : MonoBehaviour
public MovementProperties movementP
public HealthPorperties healthP
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
38、如果你有很多的剧情文本,那么把他们放到一个文件里面。
不要把他们放到Inspector的字段中去编辑。这些需要做到不打开Unity,也不用保存Scene就可以方便的修改。
39、如果你计划实现本地化,那么把你的字符串分离到一个统一的位置。
有很多种方法来实现这点。例如,定义一个文本Class,为每个字符串定义一个public的字符串字段,并把他们的默认值设为英文。其他的语言定义为子类,然后重新初始化这些字段为相应的语言的值。
另外一种更好的技术(适用于文本很大或者支持的语言数量众多),可以读取几个单独的表单,然后提供一些逻辑,根据所选择的语言来选取正确的字符串。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
测试与调试
40、实现一个图形化的Log用来调试物理、动画和AI。
这可以显著的加速调试工作。详见。
41、实现一个HTML的Log。
在很多情况下,日志是非常有用的。拥有一个便于分析的Log(颜色编码、有多个视图、记录屏幕截图等)可以使基于Log的调试变动愉悦。详见。
42、实现一个你自己的帧速率计算器。
没有人知道Unity的FPS计算器在做什么,但是肯定不是计算帧速率。实现一个你自己的,让数字符合直觉并可视化。
43、实现一个截屏的快捷键。
很多BUG是图形化的,如果你有一个截图,就很容易报告它。一个理想的系统,应该在PlayerPrefes中保存一个计数,并根据这个计数,使得所有成功保存的截屏文件都不被覆盖掉。截屏文件应该保存在工程文件夹之外,这可以防止人们不小心把它提交到版本库中。
44、实现一个打印玩家坐标的快捷键。
这可以在汇报位置相关的BUG时明确它发生在世界中的什么位置,这可以让Debug容易一些。
45、实现一些Debug选项,用来方便测试。
一些例子:
解锁所有道具;关闭所有敌人;关闭GUI;让玩家无敌;关闭所有游戏逻辑;
46、为每一个足够小的团队,创建一个适合他们的Debug选项的Prefab。
设置一个用户标识文件,单不要提交到版本库,在游戏运行时读取它。下面是原因:
团队的成员不会因为意外的提交了自己的Debug设置而影响到其他人。修改Debug设置不需要修改场景。
47、维护一个包含所有游戏元素的场景。
例如,一个场景,包括所有的敌人,所有可以交互的对象等等。这样可以不用玩很久,而进行全面的功能测试。
48、定义一些Debug快捷键常量,并把他们保存在统一的地方。
Debug键通常(方便起见)在一个地方来处理,就像其他的游戏输入一样。为了避免快捷键冲突,在一个中心位置定义所有常量。一种替代方案是,在一个地方处理所有按键输入,不管他是否是Debug键。(负面作用是,这个类可能需要引用更多的其他对象)
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
49、为你的设置建立文档。
代码应该拥有最多的文档,但是一些代码之外的东西也必须建立文档。让设计师们通过代码去看如果进行设置是浪费时间。把设置写入文档,可以提高效率(如果文档的版本能够及时更新的话)。
用文档记录下面这些:
Layer的使用(碰撞、检测、射线检测——本质上说,什么东西应该在哪个Layer里);Tag的使用;GUI的depth层级(说什么应该显示在什么之上);惯用的处理方式;Prefab结构;动画Layer。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
命名规则和目录结构
50、遵从一个命名规范和目录结构,并建立文档
命名和目录结构的一致性,可以方便查找,并明确指出什么东西在哪里。
你很有可能需要创建自己的命名规则和目录结构,下面的例子仅供参考。
普遍的命名规则
名字应该代表它是什么,例如鸟就应该叫做Bird。选择可以发音、方便记忆的名字。如果你在制作一个玛雅文化相关的游戏,不要把关卡命名为QuetzalcoatisReturn。保持唯一性。如果你选择了一个名字,就坚持用它。使用Pascal风格的大小写,例如ComplicatedVerySpecificObject。
不要使用空格,下划线,或者连字符,除了一个例外(详见为同一事物的不同方面命名一节)。不要使用版本数字,或者标示他们进度的名词(WIP、final)。不要使用缩写:DVamp@W应该写成DarkVampire@Walk。使用设计文档中的术语:如果文档中称呼一个动画为Die,那么使用DarkVampire@Die,而不要用DarkVampire@Death。保持细节修饰词在左侧:DarkVampire,而不是VampireDark;PauseButton,而不是ButtonPaused。举例说明,在Inspector中查找PauseButton,比所有按钮都以Button开头方便。(很多人倾向于相反的次序,认为那样名字可以自然的分组。然而,名字不是用来分组的,目录才是。名字是用来在同一类对象中可以快速辨识的。)为一个序列使用同一个名字,并在这些名字中使用数字。例如PathNode0, PathNode1。永远从0开始,而不是1。对于不是序列的情况,不要使用数字。例如&Bird0, Bird1, Bird2,本应该是Flamingo, Eagle, Swallow。为临时对象添加双下划线前缀,例如__Player_Backup。
为同一事物的不同方面命名
在核心名称后面添加下划线,后面的部分代表哪个方面。例如
GUI中的按钮状态:EnterButton_Active、EnterButton_Inactive贴图:&DarkVampire_Diffuse, DarkVampire_Normalmap天空盒:JungleSky_Top, JungleSky_NorthLOD分组:DarkVampire_LOD0, DarkVampire_LOD1
场景组织、工程目录、脚本目录应该使用相似的模式。
DarkVampire
LightVampire
Structures
TestScenes
Dynamic Objects
Management
脚本目录结构
ThirdParty
MyGenericScripts
Extensions
MyGameScripts
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:362061次
积分:3673
积分:3673
排名:第7211名
原创:44篇
评论:215条
难度:初级
类型:实战教学
难度:初级
类型:实战教学
难度:初级
类型:技术教程
文章:13篇
阅读:64046
(1)(2)(1)(2)(2)(3)(2)(1)(1)(2)(1)(3)(1)(1)(1)(2)(1)(3)(7)(4)(1)(2)(1)

我要回帖

更多关于 unity3d手游开发教程 的文章

 

随机推荐