【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代碼因為不參與打包,完全是遊戲項目的擴展工具,因此普適性很強,能混用很多個項目中去,之後幾篇文章會針對工具類腳本進行教學,盡請期待。
對遊戲開發感興趣的同學,歡迎圍觀我們:【皮皮關遊戲開發教育】 ,會定期更新各種教程乾貨,更有別具一格的線下小班教育。在你學習進步的路上,有皮皮關陪你!~
我們的官網地址:http://levelpp.com/
我們的遊戲開發技術交流群:610475807
我們的微信公眾號:皮皮關
推薦閱讀:
※「反響這麼好,現在都很震驚」:《絕地求生》製作人談吃雞的成就與未來
※【練習】我試著給俄羅斯方塊多加了一條規則進去
※又要修裝備了!為什麼遊戲中會有耐久度?
※一個簡單的交代...
※《InsideUE4》GamePlay架構(三)WorldContext,GameInstance,Engine