[Unity] 設計可以在建置前自我檢查的場景 – Make GameObject Check Itself

我在製作遊戲 UI,或者場景中有些會不斷在顯示、隱藏之間切換的物件時,會利用物件的 SetActive() 來進行操作;或者有其他原因,會在場景中佈置一些關閉的物件,之後再依據遊戲流程重新啟動。

在這樣的情境下,我常常發生一個失誤:在編輯場景時為了確認某個效果,而將應該關閉的物件啟動,或者將應該啟動的物件關閉,最後忘記復原就進行了儲存與遊戲建置,然後理所當然地出錯了。

但畢竟場景配置不像程式碼撰寫,會在失誤時提示錯誤。於是這次就嘗試建立一套機制,可以對場景配置進行自動檢查,來避免人為的失誤造成遊戲運行的錯誤。

步驟一:在儲存場景的時候自動執行一段檢查腳本

這邊利用的是 AssetModificationProcessor.OnWillCreateAsset 這個 Unity 所提供的 API 接口,這段腳本必須放在 Editor 資料夾中:

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneSaveProcessor : UnityEditor.AssetModificationProcessor {
    static string[] OnWillSaveAssets (string[] paths) {
        foreach (GameObject obj in SceneManager.GetActiveScene ().GetRootGameObjects ()) {
            DoTaskInTransform (obj.transform);
        }
        return paths;
    }

    private static void DoTaskInTransform (Transform parent) {
        Task (parent);
        foreach (Transform trans in parent.GetComponentsInChildren<Transform> (true)) {
            if (trans != parent.transform) {
                DoTaskInTransform (trans);
            }
        }
    }

    private static void Task(Transform trans) {
        Component[] components = trans.GetComponents<Component> ();
        foreach (Component component in components) {
            if (component is IBeforeSaveScene) {
                (component as IBeforeSaveScene).BeforeSaveScene ();
            }
        }
    }
}

這段程式碼會在有 Asset 被儲存時由 Unity 自動執行,其中也包括場景儲存時。

當這段程式碼被執行時,首先會利用 SceneManager.GetActiveScene ().GetRootGameObjects () 將當前場景的所有 根物件(Root GameObject) 找出來,並進一步利用 遞迴(Recursion) 找出所有場景中不論啟動或關閉的物件,針對物件執行 Task ()。

** 為了找出關閉的物件,GetComponentsInChildren (true) 必須加上 true。

每當 Task () 被執行了,便會找出當下物件中的所有 Component,並對有 IBeforeSaveScene 介面的 Component 執行 BeforeSaveScene () 方法。

IBeforeSaveScene 介面的設計如下,這段腳本不必放在 Editor 資料夾中:

public interface IBeforeSaveScene {
    void BeforeSaveScene ();
}

這個介面在任何 Component 都可以添加繼承,用於給 Task () 進行辨認以及轉型後的呼叫。如果有需要,也可以依照專案需求更動方法,或者再添加其他介面,只要 Task () 有對應進行呼叫的修改即可。

步驟二:在需要檢查的物件上加上對應的介面與方法

自動檢查的動作有了,再來就是要依照需求在特定物件上加上檢查條件與對應動作,這邊我舉兩個可行例子進行說明,但一定還有更多的應用可以被發揮。

  • 固定於場景中關閉物件:
using UnityEngine;
public class AlwaysInactiveOnSave : MonoBehaviour, IBeforeSaveScene {
    public bool AlwaysInactive = true;

    public void BeforeSaveScene () {
        if (AlwaysInactive && this.gameObject.activeSelf) {
            Debug.Log (this.gameObject.name + " is automatically turn off");
            this.gameObject.SetActive (false);
        }
    }
}

以上腳本被掛載於物件上時,物件將在儲存時被檢查是否關閉,若未關閉則顯示一條 Log 並自動關閉。

這個檢查的目的是針對因為需求,而預設關閉的物件,確保沒有因為測試而被人為失誤開啟,反之也可以撰寫腳本檢查物件是否有啟動。

  • 自動更新 List :
using UnityEngine;
public class BeforeSceneSaveTest : MonoBehaviour, IBeforeSaveScene {
    public void BeforeSaveScene () {
        HaveGameObjectList targetComponent = GameObject.FindObjectOfType<HaveGameObjectList> ();
        if (targetComponent.gameObjectList.Find(p => p.transform == this.transform) == null) {
            targetComponent.gameObjectList.Add (this.gameObject);
        }
    }
}

這段腳本的使用情境是,物件會自行檢查自身是否有被參照於另一個特定的腳本之中,若沒有在對方的 List 則自動補上。

通常為了管理大量物件的共同動作,可能會在某個腳本中儲存了一系列的物件參照,可是當場景編輯過程中物件有被刪減,卻未必會記得對相關的物件參照進行更新,這個檢查便可以將相關動作自動化,避免人為失誤。

更多的說明

這段機制設計之目的,是希望將一些場景的檢查工作從人工處理換成自動處理,是在自動化建置與專案管理中的一環。同樣的工作其實也可以將所有檢查設定撰寫於 OnWillSaveAssets() 方法之中,而非逐一分散於每個物件之上。

分散式設計的好處是,如果檢查的對象有所變動,並不一定要對整套檢查的腳本進行修改,只要替換對應的腳本即可。甚至在不同專案之中,同樣的檢查腳本可以不斷被重複應用,有著更高的彈性與可利用性。

但相對有個缺點是,檢查的動作之間是平行的工作而非有次序的工作,對於一些有先後次序要求的檢查工作可能較難以勝任。另外就是專案中的腳本數量可能會應為檢查工作大量增加。

** 最近發現 Unity 提供的一些 Asset Processor 相關接口充滿著各種可能性,目前這套機制對我來說尚已足夠,或許未來有新的需要可能會再進一步調整。

希望這篇心得可以讓期待將許多工作自動化的你得到一些幫助。

參考

Advertisements

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s