當我們要製作比較龐大的遊戲專案時,開發團隊通常會在數十人以上的規模在合作。而負責實作遊戲邏輯的軟體工程師,通常會在遊戲規格開得差不多後,開始參予專案製作。由於遊戲軟體工程師很接近遊戲的核心,所以在製作的中後期會變得非常忙碌。大至模組的製作、小至細微參數的調整都要一手包辦,真的會忙得焦頭爛額啊。如果想要減輕一些負擔給別人,總不能叫遊戲企畫或美術來寫程式吧。如果軟體工程師在專案開啟的前期,能設計好一些工具供後期使用,不但可以省下很多時間作重複的事情,甚至可以請不懂程式設計的人也來幫忙哩。舉個例子,如果今天專案要製作的是音樂遊戲,譜面的編輯工具就變得很重要,而且編輯音樂譜面也是要交給專業人士編輯,總不能讓寫程式的工程師,在最後死線前還要負責編譜吧。
所以,好的工具可以減輕工程師的負擔,將工作交出去給美術或企畫做編輯和調整。但相對的,製作遊戲工具也是要花不少時間的,這也要作好拿捏的,並不是所有的遊戲開發都需要另外開發編輯器或工具。
Unity 其實可以讓你擴充編輯器功能,包括設計自己的 Inspectors 或建立新的客制化視窗。裡面的參數元件該如何呈現、位置該如何排列,都可以由開發者來定義。而本篇就要來先介紹客制化 Inspectors。
撰寫 Inspectors 客制化工具有三個步驟
- 建立一個 Script。它是我們的目標類別,我們要將這隻 Script 的 Inspectors 呈現做修改
- 建立另一個 Script 繼承自 Editor,這個 Editor Script 會負責如何呈現 Inspectors 內的元件
- 在 Editor Script 中加入 CustomEditor 的 Attribute,便代入要參照的類別,也就是我們的目標 Script
- 「Standard Assets」和「Pro Standard Assets」和「Plugins」這三個資料夾內的程式碼
- 「Standard Assets」和「Pro Standard Assets」和「Plugins」這三個資料夾下的「Editor」內的程式碼
- 資料夾「Editor」以外的程式碼
- 資料夾「Editor」內的程式碼
using System; using UnityEngine; public class CameraFollow : MonoBehaviour { // X 軸方向的追蹤 public bool isTrackingXAxis; // 是否開啟X 軸方向的追蹤 public float xMargin = 1f; // 角色離攝影機中心多遠以上會開始追蹤 public float xSmooth = 8f; // 攝影機追蹤的速度 public Vector2 minMaxX; // 攝影機追蹤的最小最大值 (最小值, 最大值) public float xPos; // 攝影機實際的 X 位置 // Y 軸方向的追蹤 public bool isTrackingYAxis; public float yMargin = 1f; public float ySmooth = 8f; public Vector2 minMaxY; public float yPos; private Transform m_Player; // 玩家角色的 Transform private void Awake() { m_Player = GameObject.FindGameObjectWithTag("Player").transform; } // 檢查位置 X 是否超出範圍 private bool CheckXMargin() { return Mathf.Abs(transform.position.x - m_Player.position.x) > xMargin; } // 檢查位置 Y 是否超出範圍 private bool CheckYMargin() { return Mathf.Abs(transform.position.y - m_Player.position.y) > yMargin; } private void Update() { TrackPlayer(); } private void TrackPlayer() { float targetX = transform.position.x; float targetY = transform.position.y; if (CheckXMargin()) targetX = Mathf.Lerp(transform.position.x, m_Player.position.x, xSmooth*Time.deltaTime); if (CheckYMargin()) targetY = Mathf.Lerp(transform.position.y, m_Player.position.y, ySmooth*Time.deltaTime); // 攝影機的位置必須要在最小和最大值之間 targetX = Mathf.Clamp(targetX, minMaxX.x, minMaxX.y); targetY = Mathf.Clamp(targetY, minMaxY.x, minMaxY.y); // 設定攝影機位置 transform.position = new Vector3(targetX, targetY, transform.position.z); } }將以上的程式碼附加到場景中的 Camera 上,我們在 Camera 的 Inspector 中會看到多一個 Camera Follow 的 Component 。而所有被設定成 public 的變數欄位(Field),都會變成一個可控制的欄位或是可以勾選的 CheckBox (Boolean的變數)。這是 Unity 內建的 Inspector 功能,方便開發者在遊戲場景中,可以隨時調整參數。
但內建的東西總是事與願違對吧!像是,我希望開發者如果沒有勾選「Is Tracking X Axis」,那下面四個和追蹤X軸相關的參數也不必開啟給開發者做設定吧。另外,參數「X Pos」代表攝影機的初始(或目前)位置,如果開發者輸入一個超出「Min Max X」設定值,那也不太合理啊!這時,我們就需要自己撰寫程式,來修改 Inspector 的畫面呈現囉!
我們需要建立另一個 Script 繼承自 Editor,這個 Editor Script 會負責如何呈現 Inspectors 內的元件
using UnityEngine; using UnityEditor; using System.Collections; [CustomEditor(typeof(CameraFollow))] public class CameraFollowerEditor : Editor { CameraFollow m_Target; private bool _isTrackingXAxis; public override void OnInspectorGUI() { DrawDefaultInspector(); m_Target = (CameraFollow)target; // Toggle(標題, 預設值),勾選框元件 _isTrackingXAxis = EditorGUILayout.Toggle("Tracking X Axis", m_Target.isTrackingXAxis); m_Target.isTrackingXAxis = _isTrackingXAxis; // 從 BeginDisabledGroup(Boolean) 到 EndDisabledGroup() 中間的範圍是否可以被選取 // 取決於 BeginDisabledGroup 傳入的布林參數 EditorGUI.BeginDisabledGroup(_isTrackingXAxis == false); // FloatField(標題, 預設值),浮點數輸入元件 // 原本的目標物件(Camera)裡的變數都要設定為 Inspector 欄位中修改的數值 m_Target.xMargin = EditorGUILayout.FloatField("Margin", m_Target.xMargin); m_Target.xSmooth = EditorGUILayout.FloatField("Smooth", m_Target.xSmooth); m_Target.minMaxX.x = EditorGUILayout.FloatField("Min position", m_Target.minMaxX.x); m_Target.minMaxX.y = EditorGUILayout.FloatField("Max positio", m_Target.minMaxX.y); // 我們要用 Slider 來控制攝影機的位置,在此要先取得目標物件(Camera)的 Position Vector3 cameraPosition = m_Target.transform.position; // Slider(標題, 預設值, 最小值, 最大值),滑桿元件 cameraPosition.x = EditorGUILayout.Slider("Camera X Position", cameraPosition.x, m_Target.minMaxX.x, m_Target.minMaxX.y); EditorGUI.EndDisabledGroup(); if (cameraPosition.x != m_Target.transform.position.x) { // 在修改目標物件(Camera)的位移前,先記錄到 Undo List 中。開發者可以藉由 Undo 的功能回到 Transform 尚未位移前的狀態 Undo.RecordObject(m_Target.transform, "Change Camera X Position"); // 原本的目標物件(Camera)裡的 position 都要設定為 Inspector 滑桿中修改的數值 m_Target.transform.position = cameraPosition; } // 每一次都重畫場景中的物件(為了處理 Gizmos) SceneView.RepaintAll(); } }
上面撰寫 Editor Script 有幾個重點要注意
- Line 2 : Editor Script 需要用到「UnityEditor」的 Namespace,記得加入「using UnityEditor;」
- Line 6 : Editor Script 需要繼承自 Editor
- Line 5 : 必須宣告這個 Editor Script 是為了編輯哪個目標類別,因此在 class 的上方加入「[CustomEditor (typeof(CameraFollow))]」
- Line 12 : OnInspectorGUI()這個 Function 會在 Inspector 畫面有重畫或有任何滑鼠點擊事件時執行,相當於 Update()
- Line 14 : target這個變數是目標類別的物件。將它轉型後(m_Target),便可以從裡面拿到目標類別裡的變數和方法
- 以上程式只示範攝影機X軸方向的追蹤Y軸方向的追蹤可以以此類推,程式邏輯一模一樣
結果呈現如下圖,所有的設定都會在勾選了「Tracking X Axis」後才可以編輯。且「Camera X Position」也會根據「Min/Max Position」作範圍限制,並跟著移動鏡頭
void OnDrawGizmos() { Camera cam = Camera.main; float cHeight = 2f * cam.orthographicSize; Vector3 pos = this.transform.position - new Vector3(0.0f, 0.0f, 1.0f); float region_width = xMargin * 2.0f; if (xMargin < 0.0f) Gizmos.color = new Color(1.0f, 0.0f, 0.0f, 0.5f); else Gizmos.color = new Color(0.0f, 1.0f, 0.0f, 0.5f); Gizmos.DrawCube(pos, new Vector3(region_width, cHeight, 1.0f)); }
結果呈現如下圖。還記得我們在 CameraFollowerEditor 最後加的 SceneView.RepaintAll() 嗎?它會在我們編輯場景的過程中去重畫場景的物件。也就是說,即使不執行程式,Gizmos 也會不停的被重畫,我們便可以在編輯時看到即時的效果。
沒有留言:
張貼留言