【Unity】工具類系列教程——配置化和規範流程

前言:

程序員是看重效率的群體,如果凡事都事必躬親,一行一行的碼代碼,進行重複的勞動,最後只會淪為碼農(知乎很多大神都用這個自稱,這個詞不是褒義詞啊!)。

我們想像一下這個場景:


策劃:這個場景的出場位置太遠了,調近一點點

程序:(卧槽,還有很多新需求還沒解決,這貨又來!)好,我等下就調。

過了一會……

策劃:角色位置還是不對,你再調遠一點點?

程序:……

反覆幾次後,程序暴走

程序:還做不做新功能了!?

(還是不敢不改啊)

還有這種場景:

程序:美術大爺,你這個角色的動畫丟失了。

美術:好的,我改一下

一會後……

程序:美術大爺,動畫是沒丟失了,但是卻沒設置成循環……,您?您再改一下?

美術:怎麼會?!好的……

過了幾天,這個情況依然還會上演。

很多項目遇到的時間消耗,其實都在「舉手之勞」上,最後程序、美術、策劃三方互相都有怨氣。但是如果一個項目裡面有規範流程化的工具。


比如程序策劃交流場景變成了這樣

程序:這個場景的出生點坐標我已經暴露在這個腳本上了,你直接在場景裡面隨便調,調好了點這個上傳就行,有BUG或者不懂的再問我。

策劃:好的大爺。

程序美術交流的場景是這樣:

程序:你每次上傳美術資源的時候,點一下菜單欄的這個按鈕,它會把你丟失或者不對的都報錯出來。

美術一查,就看到了所有美術資源出錯的地方。

因此引出這篇教程的主題,配置化和規範流程。

ScriptableObject

做遊戲配置有很多方法,有Excel保存,有導出Json、TXT,這裡對Unity自帶ScriptableObject序列化方法配置做介紹。

(如果對配置有興趣的朋友可以去試試LitJson將類導出成Json格式,或者自己試著寫格式)

開始具體使用前,我們普及幾個概念。

ScriptableObject 有什麼好處?

1.Unity用於創建不需要綁定到物體的對象,即非繼承於Mono,該類繼承於UnityEngine.Object

2.存放編輯器或數據配置文件

3.方便操作管理,可以可視化編輯

4.取數據方便,ScriptableObject已經是可序列化的數據,不用像讀取純文本或xml那樣還要繁瑣耗時的數據解析過程

(當然也有壞處,如果不進行Editor編寫變數,可讀性其實很低,而且它和代碼綁定,如果配置類的代碼修改,序列化的數據就會丟失,但是總的來說不使用其他插件的情況下,用ScriptableObject 來學習遊戲內容配置是不錯的)

序列化和反序列化的概念

把對象轉換為位元組序列的過程稱為對象的序列化;把位元組序列恢復為對象的過程稱為對象的反序列化。

/*強調一下,數據解析和序列化目的是一致的,就是將不可用轉換為可用,但是實際的方式方法不同*/

我們先做一個ScriptableObject的 數據類

using System.Collections;nusing System.Collections.Generic;nusing UnityEngine;nnpublic class sceneConfigObject : ScriptableObjectn{n /// <summary>n /// 配置名字n /// </summary>n public string mIndex;n /// <summary>n /// 出生點位置n /// </summary>n public Vector3 spawnPos; n}n

這個腳本是不能直接掛載到物體上的,只有繼承了mono類的腳本才能夠。

然後為了我們直觀的修改數據,我們用一個mono腳本做中間層。

using System.Collections;nusing System.Collections.Generic;nusing UnityEngine;nnpublic class SceneConfig : MonoBehaviourn{n public sceneConfigObject mInfo;n}n

現在我們可以將SceneConfig 掛載物體上,但是顯示並不是我們想要看到的數據。

我們如果要讓Unity顯示出來我們要編輯的數據,就比如去修改它顯示的內容。而我們如何去自定義化腳本的顯示內容呢?

這裡就需要UnityEditor擴展編輯器功能。

編寫Editor代碼:

using System.Collections;nusing System.Collections.Generic;nusing UnityEngine;nusing UnityEditor;nusing System.IO;nn[CustomEditor(typeof(SceneConfig))]npublic class SceneConfigEditor : Editorn{nn SceneConfig mScript;nn /// <summary>n /// 腳本激活的時候進入,target就是對應[CustomEditor(typeof(SceneConfig))]的SceneConfig類n /// </summary>n public void OnEnable()n {n mScript = target as SceneConfig;n if (mScript.mInfo == null)n {n mScript.mInfo = new sceneConfigObject();n }n }nn /// <summary>n /// 重載腳本的界面n /// </summary>n public override void OnInspectorGUI()n {n mScript.mInfo.mIndex = EditorGUILayout.TextField("場景配置名", mScript.mInfo.mIndex);nn mScript.mInfo.spawnPos = EditorGUILayout.Vector3Field("出生點位置", mScript.mInfo.spawnPos);n }nn}n

此時我們的數據就顯示出來了:

但是我們不可能就這樣存儲數據,所以最後我們加上載入配置和導出配置的功能

/// <summary>n /// 重載腳本的界面n /// </summary>n public override void OnInspectorGUI()n {n ……nn if (GUILayout.Button("導入"))n {n if (string.IsNullOrEmpty(mScript.mInfo.mIndex))n {n Debug.LogError("未輸入配置名");n return;n }nn string path = "config/" + mScript.mInfo.mIndex;nn var configObj = Resources.Load(path) as sceneConfigObject;n if (configObj != null)n {n configObj = Instantiate(configObj);n configObj.name = mScript.mInfo.mIndex;n }n mScript.mInfo = configObj;n }nn if (GUILayout.Button("導出"))n {n if (string.IsNullOrEmpty(mScript.mInfo.mIndex))n {n Debug.LogError("未輸入配置名");n return;n }nn string path = "Assets/Resources/config/" + mScript.mInfo.mIndex + ".asset";nn if (File.Exists(path))n {n AssetDatabase.DeleteAsset(path);n AssetDatabase.SaveAssets();n }nnn AssetDatabase.CreateAsset(Instantiate(mScript.mInfo), "Assets/Resources/config/" + mScript.mInfo.mIndex + ".asset");n AssetDatabase.SaveAssets();n AssetDatabase.Refresh();n }n }n

這樣我們導出的ScriptableObject類就存放到硬碟上。

/*AssetDatabase類是Unity專門針對編輯模式下的數據存儲基類*/

如果要使用,我們就將它當成資源載入,轉換成對應的腳本類型,就可以實現調用

public void LoadScriptableObject()n {n var configObj = Instantiate(Resources.Load("config/test01") as sceneConfigObject);n Debug.Log(configObj.mIndex);n Debug.Log(configObj.spawnPos);n }n

[查錯工具]

遊戲中的查錯工具很多,因為團隊項目在工作中合併資源出錯會導致資源丟失,如果一個一個去尋找是非常花時間的。這裡以檢測動畫文件狀態為例。

依然是Editor擴展編輯器功能,它有一個屬性可以修改菜單欄。

[MenuItem("Tools/動畫查錯", priority = 0)]

MenuItem後跟上的路徑,為選項的父子目錄關係。

priority為優先順序,可以調整選項顯示的順序。

效果圖:

EditorUtility.DisplayDialog

提供彈窗功能

以下為源碼:

using System.Collections;nusing System.Collections.Generic;nusing UnityEngine;nusing UnityEditor;nusing System.IO;nusing UnityEditor.Animations;nnpublic class AnimtorTool : Editor {nnn public static string modePrefabsPath = "Assets/Resources/animator/";nn [MenuItem("Tools/動畫查錯", priority = 0)]n public static void FreshAnimtor()n {n FileInfo[] modeDirect = new DirectoryInfo(modePrefabsPath).GetFiles();nn for (int i = 0; i < modeDirect.Length; i++)n {n if (modeDirect[i].Name.Contains("meta"))n {n continue;n }n string modelName = modeDirect[i].Name;nn string animtorPath = modePrefabsPath + modelName;nn //設置動畫控制器內參數n AnimatorController AnimatorTemplate = AssetDatabase.LoadAssetAtPath(animtorPath, typeof(AnimatorController)) as AnimatorController;nn if (AnimatorTemplate == null)n {n if (EditorUtility.DisplayDialog("錯誤的路徑", "尋找動畫路徑失敗:" + animtorPath + ",檢查動畫控制器名字是否和模型名字匹配", "繼續"))n {n return;n }n }nn foreach (var obj in AnimatorTemplate.layers[0].stateMachine.states)n {n if (obj.state.motion == null)n {n if (EditorUtility.DisplayDialog("存在空的動畫Clip", "動畫" + animtorPath + "的狀態" + obj.state.name + "為空", "繼續"))n {n continue;n }n }n }nn }n }n}n

最後注意,Editor代碼一定要放在Editor目錄中,目錄中的代碼不參與打包。

總結

我個人理解的程序員職責應當是除了解決項目實際問題外,還要致力於優化項目流程。畢竟修改自己的代碼容易,修改項目的BUG難。如果不以團隊合作為目的,僅僅想著自身單方面能力的提高,是不能將個人價值發揮到最大,IT行業不比傳統行業,更注重的是一個人的整體能力,有經驗的程序員一個能打十個就是因為能改善項目工作流程。

回到正題,Editor代碼因為不參與打包,完全是遊戲項目的擴展工具,因此普適性很強,能混用很多個項目中去,之後幾篇文章會針對工具類腳本進行教學,盡請期待。

對遊戲開發感興趣的同學,歡迎圍觀我們:【皮皮關遊戲開發教育】 ,會定期更新各種教程乾貨,更有別具一格的線下小班教育。在你學習進步的路上,有皮皮關陪你!~

我們的官網地址:levelpp.com/

我們的遊戲開發技術交流群:610475807

我們的微信公眾號:皮皮關

推薦閱讀:

「反響這麼好,現在都很震驚」:《絕地求生》製作人談吃雞的成就與未來
【練習】我試著給俄羅斯方塊多加了一條規則進去
又要修裝備了!為什麼遊戲中會有耐久度?
一個簡單的交代...
《InsideUE4》GamePlay架構(三)WorldContext,GameInstance,Engine

TAG:游戏 | 游戏开发 | Unity游戏引擎 |