使用Unity3D的50个技巧:Unity3D最佳实践
转自:http://www.tuicool.com/articles/buMz63I
关于这些技巧
- 这些是基于我的一些项目经验,项目团队的规模从3人到20人不等;
- 框架结构的可重用性、清晰程度是有代价的——团队的规模和项目的规模决定你要在这个上面付出多少;
- 很多技巧是品味的问题(这里所列的所有技巧,可能有同样好的技术替代方案);
- 一些技巧可能是对传统的Unity开发的一个冲击。例如,使用prefab替代对象实例并不是一个传统的Unity风格,并且这样做的代价还挺高的(需要很多的preffab)。也许这些看起来有些疯狂,但是在我看来是值得的。
流程
1、避免Assets分支
3、考虑使用外部的关卡编辑工具
4、考虑把关卡保存为XML,而非scene
- 它可以让你不必每个场景都设置一遍;
- 他可以加载的更快(如果大多数对象都是在场景之间共享的)。
- 它让场景的版本合并变的简单(就算是Unity的新的文本格式的Scene,也由于数据太多,而让版本合并变的不切实际)。
- 它可以使得在关卡之间保持数据更简便。
5、考虑编写通用的自定义Inspector代码
- 它不支持从继承中获益;
- 它不允许定义字段级别的Inspector组件,而只能是class类型级别。举个例子,如果没有游戏对象都有一个ScomeCoolType字段,而你想在Inspector中使用不同的渲染,那么你必须为你的所有class写Inspector代码。
场景组织6、使用命名的空Game Object来做场景目录
7、把控制对象和场景目录(空Game Objec)放在原点(0,0,0)
8、尽量减少使用GUI组件的offset
9、把世界的地面放在Y=0
10、使游戏可以从每个Scene启动
1
2
3
4
5
6
|
myObject = FindMyObjectInScene ( ) ; if ( myObjet = = null ) { myObject = SpawnMyObject ( ) ; } |
美术11、把角色和地面物体的中心点(Pivot)放在底部,不要放在中间
12、统一所有的模型的面朝向(Z轴正向或者反向)
13、在开始就把Scale搞正确
14、为GUI组件或者手动创建的粒子制作一个两个面的平面模型
15、制作并使用测试资源
- 为SkyBox创建带文字的方形贴图;
- 一个网格(Grid);
- 为Shader测试使用各种颜色的平面:白色,黑色,50%灰度,红,绿,蓝,紫,黄,青;
- 为Shader测试使用渐进色:黑到白,红到绿,红到蓝,绿到蓝;
- 黑白格子;
- 平滑的或者粗糙的法线贴图;
- 一套用来快速搭建场景的灯光(使用Prefa);
Prefabs16、所有东西都使用Prefab
17、对于特例使用单独的Prefab,而不要使用特殊的实例对象
- 在同一个地方修改所有类型
- 在不动用场景的情况下进行修改
如果你有很多敌人的类型,那么也不要在编辑器中使用特殊的实例。一种可选的方案是程序化处理它们,或者为所有敌人使用一个核心的文件/Prefab。使用一个下拉列表来创建不同的敌人,或者根据敌人的位置、玩家的进度来计算。
18、在Prefab之间链接,而不要链接实例对象
19、如果可能,自动在实例对象之间产生链接关系
20、使用安全的流程来处理Prefab分支
- 复制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_WithNewFeature;
扩展和MonoBehaviourBase21、扩展一个自己的Mono Behaviour基类,然后自己的所有组件都从它派生
22、为Invoke, StartCoroutine and Instantiate 定义安全调用方法
1
2
3
4
|
public void Invoke ( Task task , float time ) { Invoke ( task.Method.Name , time ) ; } |
23、为共享接口的组件扩展
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/ / 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 ) ; } } return list ; } |
24、使用扩展来让代码书写更便捷
01
02
03
04
05
06
07
08
09
10
11
|
public static class CSTransform { public static void SetX ( this Transform transform , float x ) { Vector 3 newPosition = new Vector 3 ( x , transform. position .y , transform. position .z ) ; transform. position = newPosition; } ... } |
25、使用防御性的GetComponent()
01
02
03
04
05
06
07
08
09
10
11
12
|
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 ) ; } return component; } |
风格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或者是对象自主管理;
- 在场景变换时保存数据:通过 PlayerPrefs ,或者是在新场景加载时不要销毁的对象;
- 组合动画的方法:混合、叠加、分层;
时间27、维护一个自己的Time类,可以使游戏暂停更容易实现
生成对象28、不要让游戏运行时生成的对象搞乱场景层次结构
类设计29、使用单件(Singleton)模式
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class Singleton < T > : MonoBehaviour where T : MonoBehaviour { protected static T instance; / * * Returns the instance of this singleton. * / public static T Instance { get { 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." ) ; } } return instance; } } } |
- 对于那些非唯一的prefab实例使用单件管理器(例如Player)。不要为了坚持这条原则把类的层次关系复杂化,宁愿在你的GameManager(或其他合适的管理器中)中持有一个它们的引用。
- 对于外部经常使用的共有变量和方法定义为static,这样你可以这样简便的书写“GameManager.Player”,而不用写成“GameManager.Instance.player”。
30、在组件中不要使用public成员变量,除非它需要在inspector中调节
1
|
public float __aVariable; |
31、把界面和游戏逻辑分开
32、分离状态控制和簿记变量
- 保存游戏状态
- 调试游戏状态
实现方法之一是为每个游戏逻辑定义一个”SaveData“类,例如:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
[Serializable] PlayerSaveData { public float health; / / public for serialisation , not exposed in inspector } Player { / / ... bookkeeping variables / / Don’t expose state in inspector. State is not tweakable. private PlayerSaveData playerSaveData; } |
33、分离特殊的配置
- 为每一个游戏逻辑类定义一个模板 类 。例如,对于敌人,我们来一个“EnemyTemplate”,所有的属性设置变量都保存在这个类中。
- 在游戏逻辑的类中,定义一个上述模板类型的变量。
- 制作一个敌人的Prefab,以及两个模板的Prefab:“WeakEnemyTemplate”和"StrongEnemyTemplate"。
- 在加载或者生成对象是,把模板变量正确的复制。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public class BaseTemplate { ... } public class ActorTemplate : BaseTemplate { ... } public class Entity < EntityTemplateType > where EntityTemplateType : BaseTemplate { EntityTemplateType template; ... } public class Actor : Entity < ActorTemplate > { ... } |
34、除了显示用的文本,不要使用字符串
35、避免使用public的数组
01
02
03
04
05
06
07
08
09
10
11
|
public void SelectWeapon ( int index ) { currentWeaponIndex = index ; Player.SwitchWeapon ( weapons[currentWeapon] ) ; } public void Shoot ( ) { Fire ( bullets[currentWeapon] ) ; FireParticles ( particles[currentWeapon] ) ; } |
1
2
3
4
5
6
|
public class Weapon { public GameObject prefab; public ParticleSystem particles; public Bullet bullet; } |
这样代码看起来很整洁,但是更重要的是,在Inspector中设置时就不容易犯错了。
36、在结构中避免使用数组
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public void FireAttack ( ) { / / / behaviour Fire ( bullets[ 0 ] ) ; } public void IceAttack ( ) { / / / behaviour Fire ( bullets[ 1 ] ) ; } public void WindAttack ( ) { / / / behaviour Fire ( bullets[ 2 ] ) ; } |
使用枚举值可以让代码看起来更好一点:
1
2
3
4
5
|
public void WindAttack ( ) { / / / behaviour Fire ( bullets[WeaponType.Wind] ) ; } |
1
2
3
4
5
6
|
public class Bullets { public Bullet FireBullet; public Bullet IceBullet; public Bullet WindBullet; } |
这里假设没有其他的Fire、Ice、Wind的数据。
37、把数据组织到可序列化的类中,可以让inspector更整洁
- 把这些变量分组定义到不同的类中,并让它们声明为public和serializable;
- 在一个主要的类中,把上述类的实例定义为public成员变量;
- 不用在Awake或者Start中初始化这些变量,因为Unity会处理好它们;
- 你可以定义它们的默认值;
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public class MovementProperties / / Not a MonoBehaviour! { public float movementSpeed; public float turnSpeed = 1 ; / / default provided } public class HealthProperties / / Not a MonoBehaviour! { public float maxHealth; public float regenerationRate; } public class Player : MonoBehaviour { public MovementProperties movementProeprties; public HealthPorperties healthProeprties; } |
文本38、如果你有很多的剧情文本,那么把他们放到一个文件里面。
39、如果你计划实现本地化,那么把你的字符串分离到一个统一的位置。
测试与调试40、实现一个图形化的Log用来调试物理、动画和AI。
41、实现一个HTML的Log。
42、实现一个你自己的帧速率计算器。
43、实现一个截屏的快捷键。
44、实现一个打印玩家坐标的快捷键。
45、实现一些Debug选项,用来方便测试。
- 解锁所有道具;
- 关闭所有敌人;
- 关闭GUI;
- 让玩家无敌;
- 关闭所有游戏逻辑;
46、为每一个足够小的团队,创建一个适合他们的Debug选项的Prefab。
- 团队的成员不会因为意外的提交了自己的Debug设置而影响到其他人。
- 修改Debug设置不需要修改场景。
47、维护一个包含所有游戏元素的场景。
48、定义一些Debug快捷键常量,并把他们保存在统一的地方。
文档
- 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_North
- LOD分组:DarkVampire_LOD0, DarkVampire_LOD1
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
Materials GUI Effects Meshes Actors DarkVampire LightVampire ... Structures Buildings ... Props Plants ... ... Plugins Prefabs Actors Items ... Resources Actors Items ... Scenes GUI Levels TestScenes Scripts Textures GUI Effects ... |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
Cameras Dynamic Objects Gameplay Actors Items ... GUI HUD PauseMenu ... Management Lights World Ground Props Structure ... |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
ThirdParty ... MyGenericScripts Debug Extensions Framework Graphics IO Math ... MyGameScripts Debug Gameplay Actors Items ... Framework Graphics GUI ... |
使用Unity3D的50个技巧:Unity3D最佳实践的更多相关文章
- 使用Unity3D的50个技巧
使用Unity3D的50个技巧 刚开始学习Unity3D时间不长,在看各种资料.除了官方的手册以外,其他人的经验也是非常有益的.偶尔看到老外这篇文章,觉得还不错,于是翻译过来和大家共享.原文地址:ht ...
- 【转】超实用的JavaScript技巧及最佳实践
众所周知,JavaScript是一门非常流行的编程语言,开发者用它不仅可以开发出炫丽的Web程序,还可以用它来开发一些移动应用程序(如PhoneGap或Appcelerator),它还有一些服务端实现 ...
- 超实用的JavaScript技巧及最佳实践
众所周知,JavaScript是一门非常流行的编程语言,开发者用它不仅可以开发出炫丽的Web程序,还可以用它来开发一些移动应用程序(如PhoneGap或Appcelerator),它还有一些服务端实现 ...
- 超实用的JavaScript技巧及最佳实践(上)
在这篇文章中,作者将会向大家分享JavaScript开发的小技巧.最佳实践等非常实用的内容,不管你是前端开发者还是服务端开发者,都应该来看看这些小技巧,它们绝对会让你受益的. 文中所提供的代码片段都已 ...
- 避免Java应用中NullPointerException的技巧和最佳实践
Java应用中抛出的空指针异常是解决空指针的最好方式,也是写出能顺利工作的健壮程序的关键.俗话说"预防胜于治疗",对于这么令人讨厌的空指针异常,这句话也是成立的.值得庆幸的是运用一 ...
- nodejs进阶(1)——npm使用技巧和最佳实践
nodejs进阶教程,小白绕道!!! npm使用技巧和最佳实践 前提:请确保安装了node.js npm的最佳实践 npm install是最常见的npm cli命令,但是它还有更多能力!接下来你会了 ...
- 避免Java中NullPointerException的Java技巧和最佳实践
Java中的NullPointerException是我们最经常遇到的异常了,那我们到底应该如何在编写代码是防患于未然呢.下面我们就从几个方面来入手,解决这个棘手的问题吧. 值得庆幸的是,通过应用 ...
- 使用Unity3D的50个技巧:Unity3D最佳实践
翻译故事 原文:http://devmag.org.za/2012/07/12/50-tips-for-working-with-unity-best-practices/ 这篇技巧,我自己也在翻译, ...
- 超实用的JavaScript技巧及最佳实践(下)
1.使用逻辑符号&&或者||进行条件判断 1 2 3 var foo = 10; foo == 10 && doSomething(); // is the same ...
随机推荐
- Selenium2+python自动化45-18种定位方法(find_elements)【转载】
前言 江湖传言,武林中流传八种定位,其中xpath是宝刀屠龙,css是倚天剑. 除了这八种,其实还有十种定位方法,眼看就快失传了,今天小编让失传已久的定位方法重出江湖! 一.十八种定位方法 前八种是大 ...
- Appium+python自动化28-name定位【转载】
本篇转自博客:上海-悠悠 前言 appium1.5以下老的版本是可以通过name定位的,新版本从1.5以后都不支持name定位了 一. name定位报错 1.最新版appium V1.7用name定位 ...
- 在表达式和脚本中将bean实例暴露出来
默认情况下,在activiti.cfg.xml中的所有bean和Spring配置文件中的所有bean都可以用于表达式和脚本.如果要限制配置文件中的bean的可见性,可以在流程引擎配置文件中配置一个名为 ...
- Redis的cluster集群
目前Redis实现集群的方法主要是采用一致性哈稀分片(Shard),将不同的key分配到不同的redis server上,达到横向扩展的目的. 对于一致性哈稀分片的算法,Jedis-2.0.0已经提供 ...
- MySQL数据库中的Date,DateTime,int,TimeStamp和Time类型的对比
DATETIME 用在你需要同时包含日期和时间信息的值时.MySQL检索并且以'YYYY-MM-DD HH:MM:SS'格式显示DATETIME值,支持的范围是'1000-01-01 00:00:00 ...
- mkdir 创建目录
短选项 长选项 含义 -m <目录属性> --mode <目录属性> 建立目录时同时设置目录的权限. -p --parents 此选项后,可以是一个路径名称.若路径中的某些目录 ...
- 发布Office 365插件
在上一篇博客<VisualStudio 2013开发Office插件>开发完成了插件后,需要将插件发布 发布前需要: Azure 应用服务,作为Office插件的发布空间,地址是:http ...
- 树状数组【CF703D】Mishka and Interesting sum
Description 给你n( 1<=n<=1000000)个数,以及m(1<=m<=1000000)个询问,每个询问包括l和r,问你在这n个数中,区间l~r,出现偶数个数的 ...
- Manacher【SP7586】NUMOFPAL - Number of Palindromes
Description 求一个串中包含几个回文串. Input 输入一个字符串\(S\) Output 包含的回文串的个数. 看到题解里面有人人写回文自动机. 有必要那么麻烦嘛 emmm 我们直接跑\ ...
- 8、Flask实战第8天:add_url_rule和app.route原理
之前我们使用@app.route这个装饰器来把视图函数和url绑定 @app.route('/') def hell_world(): return 'hello world' 而且我们可以通过url ...