【學習筆記】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相關功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章