【学习笔记】Unity & C#异常处理(中)————Unity防bug指南

Unity防bug指南

从这里开始,我们将会讨论Unity游戏编程中一些常见的非预期情况,以及相应的防范与应对策略。

一. 【我血条呢??】——组件查找与组件依赖

(本章对于有经验的Unity使用者而言可能太过简单了。如果你认为没有什么值得注意的,可以直接前往下一章。)

1.1 获取游戏组件

在Unity中,相信大家都了解一个极为常用的方法:

GetConponent<T>

此方法用于查找并返回游戏物体上指定类型的组件。

例如:

Animator anim = gameObject.GetConponent<Animator>();

此语句将会查找当前游戏物体上挂载的Animator动画组件,并用Animator anim指代之;

GetConponent<Text>().fontsize = 25;

此语句将会找到当前游戏物体上挂载的Text文字组件,并直接将其字号设置为25。

在脚本中,我们可以用GetConponent<T>对任意的Unity组件进行操作,或者用于找到挂载在物体上的另一个MonoBehaviour组件,实现代码间的通讯。

不过,作为一个十分常用的方法,GetConponent<T>本身是很不安全,很容易引发异常的。如果你有一定的Unity编程经验,相信一定都经历过组件查找失败的困扰。

首先讲解一下最基础的常识。(非新手同学先跳过这里往下看)

在Unity中,GetConponent<T>方法有以下特性:

(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<HitPoint>().ChangeHitPoint(HealingHitPoint);
    }    
}

新建一个场景,创建一个Capsule物体来代表游戏角色,然后为其挂载上HitPoint和Healing组件,运行游戏。

(出于文章美观的需要,这里采用了一个人类角色Cattleya)

运行游戏,按下键盘上的H键,可以从UI文字上看到,Cattleya的生命值恢复了10点。

在上面这个例子中,游戏角色Cattleya上挂载了执行治疗所必须的HitPointHealing组件。但在复杂的游戏项目中,我们不一定始终能够对物体上的组件进行正确配置。

假设我们在开发过程中,不慎遗漏了Cattleya身上的生命值(HitPoint)组件。此时我们调用治疗(Heal)指令,但是人物身上没有生命值可供治疗,会发生什么情况呢?

我们将HitPoint组件从Cattleya身上移除,再运行一次游戏并按下H键。

不出所料,Console面板中出现了异常提示。双击跳转到异常位置,可以看到异常出现在GetConponent<HitPoint>()的所在行。

此时或许你会说,报错了又怎么样?这个报错看起来无关痛痒,并没有导致游戏崩溃或者产生其它灾难性后果。

Unity确实具有这样的稳定性,使得许多轻微的异常在游戏中出现时,不会导致崩溃、闪退或其它严重后果。然而,就像运行一般的程序一样,在Unity中,一旦某行代码遇到异常,出现在异常后面的语句都不会执行。现在,我们在Heal组件中写入一些Debug检测点。

再次运行并观察Console面板:

我们发现,在异常所在语句后面的所有Debug指令都没有执行。

这一实验结果可以给我们足够的提示:在Unity中,放任可能出现的异常不管,是一种不安全的行为。代码中的一处异常可能会导致更多语句被意外丢弃,从而产生不可预知的后果。

因此,我们必须引入适当的异常处理机制。

1.3 安全地查找并获取组件

要解决组件丢失的问题,最简单的办法就是利用GetConponent<T>作为bool型返回值的特性。一旦GetConponent<T>的返回值为false,说明未能成功查找到所需的组件;此时就不应该针对这个“不存在的组件”执行进一步的操作。

修改Healing组件中的Heal方法如下:

    public void Heal(GameObject Unit,int HealingHitPoint)
    {
        if (!GetComponent<HitPoint>())
        { return; }//如果没有找到生命值组件,直接开溜!后面的指令不要了

        Unit.GetComponent<HitPoint>().ChangeHitPoint(HealingHitPoint);
    }

再试一次,仍然在没有HitPoint组件的情况下运行游戏。

这次我们看到,程序不再报出异常或者发生意外中断。

 

在上面的例子中,我们在试图通过GetConponent<T>调取HitPoint组件之前,首先*检查了此方法的bool型返回值;如果返回值为true,说明成功查到组件,之后我们就可以放心地对该组件进行操作。

当然,你完全可以针对未查到组件的情况,写入一些Debug提示,或者更多的异常处理指令,使你的程序逻辑更加完善。

*Tips:

如果觉得不习惯,你也可以选择不使用GetConponent<T>()具备布尔返回值的特性。

因为,if ( GetConponent<T>() == null ) 与 if (!GetConponent<T>()) 是等效表述;

同理,if (GetConponent<T>() != null ) 与 if (GetConponent<T>() 也是等效表述。

1.4 RequireConponent与AddConponent

经过一点小小的努力,我们成功避免了HitPoint组件丢失时可能出现的报错。能否更进一步呢?现在,我们来尝试自动修复“组件丢失”这一小问题。

在一段代码的头部标注RequireConponent属性,即可实现组件依赖。修改组件Healing如下图。

修改后,当你为某一物体挂载Healing组件时,会自动挂载HitPoint组件;且在组件Healing被移除前,HitPoint无法移除。

如果在Healing组件存在的情况下尝试移除组件HitPoint,会收到提示:

这下,你就不必担心开发中会忘记添加或意外移除必要的HitPoint组件了。

此外,你可以在任何时候使用AddConponent<T>方法,来为物体挂载上任意类型的组件。

下面展示了一种采用AddConponent方法的Healing组件修改方案。

using UnityEngine;

[RequireComponent(typeof(HitPoint))]
public class Healing : MonoBehaviour {

    void Start()
    {
        if (!GetComponent<HitPoint>())
        {
            gameObject.AddComponent<HitPoint>();//如果没有发现HitPoint组件,则立即添加之
        }
    }

    void Update ()
    {
		if(Input.GetKeyDown(KeyCode.H))
        {
            Heal(gameObject, 10);
        }
	}

    public void Heal(GameObject Unit,int HealingHitPoint)
    {
        Unit.GetComponent<HitPoint>().ChangeHitPoint(HealingHitPoint);
    }
}

注意:如果用AddConponent方法在游戏中实时挂载组件,那么必须确保被挂载的组件本身具备完善的自我初始化能力,或者自行写入指令来对新生成的组件进行初始化。

1.5 组件的隐藏与保护

有些时候,开发者不希望某些组件或某些物体在Unity界面中被意外编辑;特别是当该组件或物体是由脚本代码进行全自动生成与管理的时候。在此类情形下,你可以通过编辑物体组件的Hideflags属性,来实现对物体或组件的隐藏。

下面的图展示了Hideflags属性的部分常见用法。

注意:

(1)如果你希望Hideflags的各种功能在编辑器中(而非游戏运行时)生效,可以考虑为组件标注[ExecuteInEditMode]属性;但是可能需要处理一些比较麻烦的继发问题。

(2)谨慎使用Hideflags功能。特别强调一点,如果你的项目将由另外的人阅览,那么HideFlags的存在会给他人造成极大的困惑。如果你需要将自己的项目交给他人进行学习、研究或编辑,那么强烈建议停用Hideflags相关功能。

猜你喜欢

转载自blog.csdn.net/qq_35587645/article/details/106731015