Unity防bug指南
从这里开始,我们将会讨论Unity游戏编程中一些常见的非预期情况,以及相应的防范与应对策略。
(本章对于有经验的Unity使用者而言可能太过简单了。如果你认为没有什么值得注意的,可以直接前往下一章。)
1.1 获取游戏组件
在Unity中,相信大家都了解一个极为常用的方法:
GetConponent
此方法用于查找并返回游戏物体上指定类型的组件。
例如:
Animator anim = gameObject.GetConponent
(); 此语句将会查找当前游戏物体上挂载的Animator动画组件,并用Animator anim指代之;
GetConponent
().fontsize = 25; 此语句将会找到当前游戏物体上挂载的Text文字组件,并直接将其字号设置为25。
在脚本中,我们可以用GetConponent
不过,作为一个十分常用的方法,GetConponent
首先讲解一下最基础的常识。(非新手同学先跳过这里往下看)
在Unity中,GetConponent
方法有以下特性: (1)如果成功查找到了类型为T的组件,则此方法的返回值就是该组件;
(2)如果没能成功查找到组件,则此方法的返回值为null;
(3)*此方法得到了对bool运算符的重载,这使得它始终可以被看成一个布尔变量。当此方法成功查找到组件时,该方法返回true;反之则返回false.
*Unity官方文档中似乎并未提到过特性(3),所以不能排除有很多新手都不知道这一点。
1.2 情境:找不到血条的医疗兵
下面我们模拟一个简单的游戏情境,来探究有关组件查询的安全性问题。
假设我们控制一名游戏人物Z进行战斗。Z身上挂载着HitPoint组件,用于记载其生命值信息;拥有100的生命值上限,但当前生命值为30,急需治疗。Z身上同时挂载着Healing组件,此组件定义了一个自我治疗技能,玩家可以按下H键进行自我治疗。
HitPoint组件:
using UnityEngine;
public class HitPoint : MonoBehaviour {
public int HitPointLimit = 100;
public int Hitpoint = 30;
public void ChangeHitPoint(int hp)
{
Hitpoint = Mathf.Clamp(Hitpoint + hp, 0, HitPointLimit);
}
private void OnGUI()
{
GUIStyle style = new GUIStyle();
style.fontSize = 35;
GUI.Label(new Rect(50, 50, 200, 100), "当前生命值:" + Hitpoint.ToString(),style);
}
}
Healing组件:
using UnityEngine;
public class Healing : MonoBehaviour {
void Update ()
{
if(Input.GetKeyDown(KeyCode.H))
{
Heal(gameObject, 10);
}
}
public void Heal(GameObject Unit,int HealingHitPoint)
{
Unit.GetComponent().ChangeHitPoint(HealingHitPoint);
}
}
新建一个场景,创建一个Capsule物体来代表游戏角色,然后为其挂载上HitPoint和Healing组件,运行游戏。
(出于文章美观的需要,这里采用了一个人类角色Cattleya)
运行游戏,按下键盘上的H键,可以从UI文字上看到,Cattleya的生命值恢复了10点。
在上面这个例子中,游戏角色Cattleya上挂载了执行治疗所必须的HitPoint和Healing组件。但在复杂的游戏项目中,我们不一定始终能够对物体上的组件进行正确配置。
假设我们在开发过程中,不慎遗漏了Cattleya身上的生命值(HitPoint)组件。此时我们调用治疗(Heal)指令,但是人物身上没有生命值可供治疗,会发生什么情况呢?
我们将HitPoint组件从Cattleya身上移除,再运行一次游戏并按下H键。
不出所料,Console面板中出现了异常提示。双击跳转到异常位置,可以看到异常出现在GetConponent
此时或许你会说,报错了又怎么样?这个报错看起来无关痛痒,并没有导致游戏崩溃或者产生其它灾难性后果。
Unity确实具有这样的稳定性,使得许多轻微的异常在游戏中出现时,不会导致崩溃、闪退或其它严重后果。然而,就像运行一般的程序一样,在Unity中,一旦某行代码遇到异常,出现在异常后面的语句都不会执行。现在,我们在Heal组件中写入一些Debug检测点。
再次运行并观察Console面板:
我们发现,在异常所在语句后面的所有Debug指令都没有执行。
这一实验结果可以给我们足够的提示:在Unity中,放任可能出现的异常不管,是一种不安全的行为。代码中的一处异常可能会导致更多语句被意外丢弃,从而产生不可预知的后果。
因此,我们必须引入适当的异常处理机制。
1.3 安全地查找并获取组件
要解决组件丢失的问题,最简单的办法就是利用GetConponent
修改Healing组件中的Heal方法如下:
public void Heal(GameObject Unit,int HealingHitPoint)
{
if (!GetComponent())
{ return; }//如果没有找到生命值组件,直接开溜!后面的指令不要了
Unit.GetComponent().ChangeHitPoint(HealingHitPoint);
}
再试一次,仍然在没有HitPoint组件的情况下运行游戏。
这次我们看到,程序不再报出异常或者发生意外中断。
在上面的例子中,我们在试图通过GetConponent
当然,你完全可以针对未查到组件的情况,写入一些Debug提示,或者更多的异常处理指令,使你的程序逻辑更加完善。
*Tips:
如果觉得不习惯,你也可以选择不使用GetConponent
()具备布尔返回值的特性。 因为,if ( GetConponent
() == null ) 与 if (!GetConponent ()) 是等效表述; 同理,if (GetConponent
() != null ) 与 if (GetConponent () 也是等效表述。
1.4 RequireConponent与AddConponent
经过一点小小的努力,我们成功避免了HitPoint组件丢失时可能出现的报错。能否更进一步呢?现在,我们来尝试自动修复“组件丢失”这一小问题。
在一段代码的头部标注RequireConponent属性,即可实现组件依赖。修改组件Healing如下图。
修改后,当你为某一物体挂载Healing组件时,会自动挂载HitPoint组件;且在组件Healing被移除前,HitPoint无法移除。
如果在Healing组件存在的情况下尝试移除组件HitPoint,会收到提示:
这下,你就不必担心开发中会忘记添加或意外移除必要的HitPoint组件了。
此外,你可以在任何时候使用AddConponent
方法,来为物体挂载上任意类型的组件。 下面展示了一种采用AddConponent方法的Healing组件修改方案。
using UnityEngine;
[RequireComponent(typeof(HitPoint))]
public class Healing : MonoBehaviour {
void Start()
{
if (!GetComponent())
{
gameObject.AddComponent();//如果没有发现HitPoint组件,则立即添加之
}
}
void Update ()
{
if(Input.GetKeyDown(KeyCode.H))
{
Heal(gameObject, 10);
}
}
public void Heal(GameObject Unit,int HealingHitPoint)
{
Unit.GetComponent().ChangeHitPoint(HealingHitPoint);
}
}
注意:如果用AddConponent方法在游戏中实时挂载组件,那么必须确保被挂载的组件本身具备完善的自我初始化能力,或者自行写入指令来对新生成的组件进行初始化。
1.5 组件的隐藏与保护
有些时候,开发者不希望某些组件或某些物体在Unity界面中被意外编辑;特别是当该组件或物体是由脚本代码进行全自动生成与管理的时候。在此类情形下,你可以通过编辑物体或组件的Hideflags属性,来实现对物体或组件的隐藏。
下面的图展示了Hideflags属性的部分常见用法。
注意:
(1)如果你希望Hideflags的各种功能在编辑器中(而非游戏运行时)生效,可以考虑为组件标注[ExecuteInEditMode]属性;但是可能需要处理一些比较麻烦的继发问题。
(2)谨慎使用Hideflags功能。特别强调一点,如果你的项目将由另外的人阅览,那么HideFlags的存在会给他人造成极大的困惑。如果你需要将自己的项目交给他人进行学习、研究或编辑,那么强烈建议停用Hideflags相关功能。