使用Unity3D的50个技巧:Unity3D最佳实践(下)
2015-08-15 09:02
405 查看
http://blog.csdn.net/neil3d/article/details/38534809
一些做法并不能很好的一起协作。使用一个,能强制统一设计方向,并明确指出不是其他做法所指的方向;
团队成员使用统一的风格,可能方便大家互相的理解。他使得整体结构和代码都更容易理解。这也可以减少错误;
几组风格的例子:
协程与状态机(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,或者是在新场景加载时不要销毁的对象;
组合动画的方法:混合、叠加、分层;
29、使用单件(Singleton)模式
从下面这个类派生的所有类,将自动获得单件功能:
[csharp] view
plaincopy
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;
}
}
}
单件可以作为一些管理器,例如ParticleManager或者AudioManager亦或者GUIManager。
对于那些非唯一的prefab实例使用单件管理器(例如Player)。不要为了坚持这条原则把类的层次关系复杂化,宁愿在你的GameManager(或其他合适的管理器中)中持有一个它们的引用。
对于外部经常使用的共有变量和方法定义为static,这样你可以这样简便的书写“GameManager.Player”,而不用写成“GameManager.Instance.player”。
30、在组件中不要使用public成员变量,除非它需要在inspector中调节
除非需要设计师(策划or美术)去调节的变量,特别是它不能明确表明自己是做什么的变量,不要声明为public。如果在这些特殊情况下,无法避免,则可使用两个甚至四个下划线来表明不要从外部调节它,例如:
[csharp] view
plaincopy
public float __aVariable;
所有的输入控制器,只负责向相应的组件发送命令,让它们知道控制器被调用了。举一个控制器逻辑的例子,一个控制器根据玩家的状态来决定发送哪个命令。但是这样并不好(例如,如果你添加了多个控制器,那将会导致逻辑重复)。相反的,玩家对象应该根据当前状态(例如减速、惊恐)来设置当前的速度,并根据当前的面朝向来计算如何向前移动。控制器只负责做他们自己状态相关的事情,控制器不改变玩家的状态,因此控制前甚至可以根本不知道玩家的状态。另外一个例子,切换武器。正确的方法是,玩家有一个函数:“SwitchWeapon(Weapon
newWeapon)”供GUI调用。GUI不应该维护所有对象的Transform和他们之间的父子关系。
所有界面相关的组件,只负责维护和处理他们自己状态相关的数据。例如,显示一个地图,GUI可以根据玩家的位移计算地图的显示。但是,这是游戏状态数据,它不属于GUI。GUI只是显示游戏状态数据,这些数据应该在其他地方维护。地图数据也应该在其他地方维护(例如GameManager)。
游戏玩法对象不应该关心GUI。有一个例外是处理游戏暂停(可能是通过控制Time.timeScale,其实这并不是个好主意)。游戏玩法对象应该知道游戏是否暂停。但是,这就是全部了。另外,不要把GUI组件挂到游戏玩法对象上。
这么说吧,如果你把所有的GUI类都删了,游戏应该可以正确编译。
你还应该达到:在不需要重写游戏逻辑的前提下,重写GUI和输入控制。
保存游戏状态
调试游戏状态
实现方法之一是为每个游戏逻辑定义一个”SaveData“类,例如:
[csharp] view
plaincopy
[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;
}
为每一个游戏逻辑类定义一个模板类。例如,对于敌人,我们来一个“EnemyTemplate”,所有的属性设置变量都保存在这个类中。
在游戏逻辑的类中,定义一个上述模板类型的变量。
制作一个敌人的Prefab,以及两个模板的Prefab:“WeakEnemyTemplate”和"StrongEnemyTemplate"。
在加载或者生成对象是,把模板变量正确的复制。
这种方法可能有点复杂(在一些情况下,可能不需要这样)。
举个例子,最好使用泛型,我们可以这样定义我们的类:
[csharp] view
plaincopy
public class BaseTemplate
{
...
}
public class ActorTemplate : BaseTemplate
{
...
}
public class Entity<EntityTemplateType> where EntityTemplateType : BaseTemplate
{
EntityTemplateType template;
...
}
public class Actor : Entity <ActorTemplate>
{
...
}
风格
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类,可以使游戏暂停更容易实现
做一个“Time.DeltaTime”和""Time.TimeSinceLevelLoad"的包装,用来实现暂停和游戏速度缩放。这使用起来略显麻烦,但是当对象运行在不同的时钟速率下的时候就方便多了(例如界面动画和游戏内动画)。生成对象
28、不要让游戏运行时生成的对象搞乱场景层次结构
在游戏运行时,为动态生成的对象设置好它们的父对象,可以让你更方便的查找。你可以使用一个空的对象,或者一个没有行为的单件来简化代码中的访问。可以给这个对象命名为“DynamicObjects”。29、使用单件(Singleton)模式
从下面这个类派生的所有类,将自动获得单件功能:[csharp] view
plaincopy
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;
}
}
}
单件可以作为一些管理器,例如ParticleManager或者AudioManager亦或者GUIManager。
对于那些非唯一的prefab实例使用单件管理器(例如Player)。不要为了坚持这条原则把类的层次关系复杂化,宁愿在你的GameManager(或其他合适的管理器中)中持有一个它们的引用。
对于外部经常使用的共有变量和方法定义为static,这样你可以这样简便的书写“GameManager.Player”,而不用写成“GameManager.Instance.player”。
30、在组件中不要使用public成员变量,除非它需要在inspector中调节
除非需要设计师(策划or美术)去调节的变量,特别是它不能明确表明自己是做什么的变量,不要声明为public。如果在这些特殊情况下,无法避免,则可使用两个甚至四个下划线来表明不要从外部调节它,例如:[csharp] view
plaincopy
public float __aVariable;
31、把界面和游戏逻辑分开
这一条本质上就是指的MVC模式。所有的输入控制器,只负责向相应的组件发送命令,让它们知道控制器被调用了。举一个控制器逻辑的例子,一个控制器根据玩家的状态来决定发送哪个命令。但是这样并不好(例如,如果你添加了多个控制器,那将会导致逻辑重复)。相反的,玩家对象应该根据当前状态(例如减速、惊恐)来设置当前的速度,并根据当前的面朝向来计算如何向前移动。控制器只负责做他们自己状态相关的事情,控制器不改变玩家的状态,因此控制前甚至可以根本不知道玩家的状态。另外一个例子,切换武器。正确的方法是,玩家有一个函数:“SwitchWeapon(Weapon
newWeapon)”供GUI调用。GUI不应该维护所有对象的Transform和他们之间的父子关系。
所有界面相关的组件,只负责维护和处理他们自己状态相关的数据。例如,显示一个地图,GUI可以根据玩家的位移计算地图的显示。但是,这是游戏状态数据,它不属于GUI。GUI只是显示游戏状态数据,这些数据应该在其他地方维护。地图数据也应该在其他地方维护(例如GameManager)。
游戏玩法对象不应该关心GUI。有一个例外是处理游戏暂停(可能是通过控制Time.timeScale,其实这并不是个好主意)。游戏玩法对象应该知道游戏是否暂停。但是,这就是全部了。另外,不要把GUI组件挂到游戏玩法对象上。
这么说吧,如果你把所有的GUI类都删了,游戏应该可以正确编译。
你还应该达到:在不需要重写游戏逻辑的前提下,重写GUI和输入控制。
32、分离状态控制和簿记变量
簿记变量只是为了使用起来方便或者提高查找速度,并且可以根据状态控制来覆盖。将两者分离可以简化:保存游戏状态
调试游戏状态
实现方法之一是为每个游戏逻辑定义一个”SaveData“类,例如:
[csharp] view
plaincopy
[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、分离特殊的配置
假设我们有两个敌人,它们使用同一个Mesh,但是有不同的属性设置(例如不同的力量、不同的速度等等)。有很多方法来分离数据。下面是我比较喜欢的一种,特别是对于对象生成或者游戏存档时,会很好用。(属性设置不是状态数据,而是配置数据,所以我们不需要存档他们。当对象加载或者生成是,属性设置会自动加载。)为每一个游戏逻辑类定义一个模板类。例如,对于敌人,我们来一个“EnemyTemplate”,所有的属性设置变量都保存在这个类中。
在游戏逻辑的类中,定义一个上述模板类型的变量。
制作一个敌人的Prefab,以及两个模板的Prefab:“WeakEnemyTemplate”和"StrongEnemyTemplate"。
在加载或者生成对象是,把模板变量正确的复制。
这种方法可能有点复杂(在一些情况下,可能不需要这样)。
举个例子,最好使用泛型,我们可以这样定义我们的类:
[csharp] view
plaincopy
public class BaseTemplate
{
...
}
public class ActorTemplate : BaseTemplate
{
...
}
public class Entity<EntityTemplateType> where EntityTemplateType : BaseTemplate
{
EntityTemplateType template;
...
}
public class Actor : Entity <ActorTemplate>
{
...
}
相关文章推荐
- Unity Asset(1)Pocket RPG Weapon Trails 武器拖尾效果
- unity3D实际的原始视频游戏开发系列讲座10它《战斗:外来入侵》在第一季度游戏开发
- Unity Critter地图导出到服务器配置
- Unity用shader显示物体线框
- unity, 显示像素图,以及iOS下像素图变模糊解决办法
- Unity学习
- unity, iOS集成微信
- Unity Shader 学习笔记 (二) 简单颜色Shader
- Unity3D视图与iOS视图结合
- Unity人工智能学习—确定性AI算法之追踪算法二
- (大数据工程师学习路径)第三步 Git Community Book----高级技能
- 关于在3dmax 导入再导出FBX到unity中的尺寸选项问题
- Unity AssetBundle爬坑手记
- Unity Shader 学习笔记 (一) 创建Shader自带代码解读
- (大数据工程师学习路径)第三步 Git Community Book----中级技能(下)
- Unity3d Shader 代码高亮插件
- 关于Unity3D中的空引用 Null Reference Exception
- (大数据工程师学习路径)第三步 Git Community Book----中级技能(上)
- Hololens开发,Unity不是必选的,但当下是唯一的
- Unity 打印信息