Unity模型資源使用流程,unity模型使用流程
昨天研究了下Unity中AnimatorController自動產生。今天稍微完善了操作流程,並且補充說明了在MMO中如何使用模型資源(AssetBundle),這個使用方法是MMO這樣模型資源非常大的情況下才需要的。如果是比較小的情況下,直接塞到Resources目錄下,載入Prefab就OK了,根本不用操心這些問題。
先附上最終修改後的指令碼。它建立了一個菜單選項,選擇一個檔案夾(或者檔案),右鍵選擇CreateAnimation就可以自動遍曆所有的模型資源,然後建立AnimatorController並建立Prefab檔案。在Prefab都產生完畢後(或者在產生的同時),可以使用建立AssetBundle的功能在磁碟任意目錄建立AssetBundle。
using UnityEngine;using UnityEditor;using UnityEditorInternal;using System.IO;using System.Collections;using System.Collections.Generic; public class CreateAnimation : Editor { //產生出的Prefab的路徑private static readonly string PREFAB_PATH = "Assets/Prefab/"; private static readonly string PATH_TAG = "Assets/"; private static bool _ingnoreExist = true; // 這些動畫是迴圈播放的(除此之外的迴圈動畫需要手動建立) private static readonly string[] LOOP_TAG = new string[] { "idle", "walk", "run" }; [MenuItem("Assets/CreateAnimation")] static void DoCreateAnimation() { UnityEngine.Object obj = Selection.activeObject; string path = AssetDatabase.GetAssetPath(Selection.activeObject); if (Directory.Exists(path)) { // 如果是路徑的話 Walk(path, (string fbxPath)=>{ fbxPath = fbxPath.Replace("\\", "/").Replace(".FBX", ".fbx"); string relativePath = fbxPath.Substring(fbxPath.IndexOf(PATH_TAG)); if (!relativePath.EndsWith(".fbx")) { return; } DoCreateController(relativePath); DoCreatePrefab(relativePath); }); } else if (File.Exists(path)) { // 如果是檔案的話 string fbxPath = path.Replace("\\", "/").Replace(".FBX", ".fbx"); string relativePath = fbxPath.Substring(fbxPath.IndexOf(PATH_TAG)); if (!relativePath.EndsWith(".fbx")) { return; } DoCreateController(relativePath); DoCreatePrefab(relativePath); } else { // 路徑不存在 Debug.LogError(string.Format("路徑不存在: {0}", path)); } AssetDatabase.Refresh(); Debug.Log(string.Format("轉換完畢: {0}", path)); } // 遍曆檔案夾 delegate void PathCallBack(string path); static void Walk(string dir, PathCallBack callback) { DirectoryInfo d = new DirectoryInfo(dir); if (d.GetDirectories() != null) { FileInfo[] allf = d.GetFiles(); for (int i = 0; i < allf.Length; i++) { if (callback != null) { callback(allf[i].FullName); } } DirectoryInfo[] alldr = d.GetDirectories(); if (alldr != null) { for (int i = 0; i < alldr.Length; i++) { Walk(alldr[i].FullName, callback); } } } } static void DoCreateController(string assetPath) { // 擷取animationclip List<AnimationClip> clips = new List<AnimationClip>(); UnityEngine.Object[] objects = AssetDatabase.LoadAllAssetsAtPath(assetPath); foreach (var obj in objects) { AnimationClip clip = obj as AnimationClip; if (clip != null && clip.name.IndexOf("__preview__") == -1) { clips.Add(clip); } } if (clips.Count <= 0) { return; } // controller檔案可以和fbx放在一起 string acPath = assetPath.Replace(".fbx", ".controller"); // 建立動畫控制器 AnimatorController animatorController = null; // 更新controller無法及時更新,所以建立controller,但是不能單獨prefab,否則會丟失關聯 animatorController = AnimatorController.CreateAnimatorControllerAtPath(acPath); AnimatorControllerLayer layer = animatorController.GetLayer(0); UnityEditorInternal.StateMachine sm = layer.stateMachine; Vector3 anyStatePosition = sm.anyStatePosition; float OFFSET_X = 220; float OFFSET_Y = 60; float ITEM_PER_LINE = 4; float originX = anyStatePosition.x - OFFSET_X * (ITEM_PER_LINE / 2.5f); float originY = anyStatePosition.y + OFFSET_Y; float x = originX; float y = originY; State defaultState = null; foreach (AnimationClip newClip in clips) { string clipName = newClip.name.ToLower(); State state = sm.AddState(clipName); // 設定defaultState,優先選取idle動畫 if (defaultState == null) { if (clipName.IndexOf("idle") != -1) { sm.defaultState = state; defaultState = state; } } else { if (clipName == "idle") { sm.defaultState = state; defaultState = state; } } foreach (var tag in LOOP_TAG) { //有些動畫我希望天生它就動畫迴圈 if (clipName.IndexOf(tag) != -1) { SerializedObject serializedClip = new SerializedObject(newClip); AnimationClipSettings clipSettings = new AnimationClipSettings(serializedClip.FindProperty("m_AnimationClipSettings")); clipSettings.loopTime = true; clipSettings.loopBlend = true; serializedClip.ApplyModifiedProperties(); break; } } state.SetAnimationClip(newClip, layer); state.position = new Vector3(x, y, 0); x += OFFSET_X; if (x >= originX + OFFSET_X * ITEM_PER_LINE) { x = originX; y += OFFSET_Y; } } AssetDatabase.SaveAssets(); } static void DoCreatePrefab(string assetPath) { // 建立對應檔案夾 string destPath = assetPath; if (!destPath.StartsWith(PREFAB_PATH)) { destPath = assetPath.Replace("Assets/", PREFAB_PATH); } string dirPath = System.IO.Path.GetDirectoryName(destPath); if (!System.IO.Directory.Exists(dirPath)) { System.IO.Directory.CreateDirectory(dirPath); } string acPath = assetPath.Replace(".fbx", ".controller"); string prefabPath = destPath.Replace(".fbx", ".prefab"); string name = assetPath.Substring(assetPath.LastIndexOf("/") + 1).Replace(".fbx", ""); // 建立prefab GameObject fbx = AssetDatabase.LoadAssetAtPath(assetPath, typeof(GameObject)) as GameObject; GameObject go = Instantiate(fbx) as GameObject; go.name = name; // 如果controller檔案存在,則建立對應的animator if (System.IO.File.Exists(acPath)) { AnimatorController animatorController = AssetDatabase.LoadAssetAtPath(acPath, typeof(AnimatorController)) as AnimatorController; Animator animator = go.GetComponent<Animator>(); if (animator == null) { animator = go.AddComponent<Animator>(); } animator.applyRootMotion = false; animator.runtimeAnimatorController = animatorController; } // 建立prefab if (System.IO.File.Exists(prefabPath)) { // 替換原來的prefab (每個prefab都有對應的udid不能直接使用新的,否則所有相關引用都將失效) GameObject prefab = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject)) as GameObject; PrefabUtility.ReplacePrefab(go, prefab); } else { // 建立新的prefab PrefabUtility.CreatePrefab(prefabPath, go, ReplacePrefabOptions.ConnectToPrefab); } GameObject.DestroyImmediate(go, true); AssetDatabase.SaveAssets(); } class AnimationClipSettings{SerializedProperty m_Property;private SerializedProperty Get (string property) { return m_Property.FindPropertyRelative(property); }public AnimationClipSettings(SerializedProperty prop) { m_Property = prop; }public float startTime { get { return Get("m_StartTime").floatValue; } set { Get("m_StartTime").floatValue = value; } }public float stopTime{ get { return Get("m_StopTime").floatValue; } set { Get("m_StopTime").floatValue = value; } }public float orientationOffsetY { get { return Get("m_OrientationOffsetY").floatValue; } set { Get("m_OrientationOffsetY").floatValue = value; } }public float level { get { return Get("m_Level").floatValue; } set { Get("m_Level").floatValue = value; } }public float cycleOffset { get { return Get("m_CycleOffset").floatValue; } set { Get("m_CycleOffset").floatValue = value; } } public bool loopTime { get { return Get("m_LoopTime").boolValue; } set { Get("m_LoopTime").boolValue = value; } } public bool loopBlend { get { return Get("m_LoopBlend").boolValue; } set { Get("m_LoopBlend").boolValue = value; } }public bool loopBlendOrientation { get { return Get("m_LoopBlendOrientation").boolValue; } set { Get("m_LoopBlendOrientation").boolValue = value; } }public bool loopBlendPositionY { get { return Get("m_LoopBlendPositionY").boolValue; } set { Get("m_LoopBlendPositionY").boolValue = value; } }public bool loopBlendPositionXZ { get { return Get("m_LoopBlendPositionXZ").boolValue; } set { Get("m_LoopBlendPositionXZ").boolValue = value; } }public bool keepOriginalOrientation { get { return Get("m_KeepOriginalOrientation").boolValue; } set { Get("m_KeepOriginalOrientation").boolValue = value; } }public bool keepOriginalPositionY { get { return Get("m_KeepOriginalPositionY").boolValue; } set { Get("m_KeepOriginalPositionY").boolValue = value; } }public bool keepOriginalPositionXZ { get { return Get("m_KeepOriginalPositionXZ").boolValue; } set { Get("m_KeepOriginalPositionXZ").boolValue = value; } }public bool heightFromFeet { get { return Get("m_HeightFromFeet").boolValue; } set { Get("m_HeightFromFeet").boolValue = value; } }public bool mirror { get { return Get("m_Mirror").boolValue; } set { Get("m_Mirror").boolValue = value; } }}}
這裡有幾個需要說明的地方:
1、產生AnimatorController和Prefab的時間不快,所以一次轉換的模型不要太多。
2、原本可以更新AnimatorController內容會更好些,但是沒有嘗試成功,所以建立完controller就要更新Prefab,這樣關聯資訊不會丟失(miss animator)
3、建立Prefab的時候,如果已存在,則更新Prefab。這樣可以保證其他使用Prefab的地方不會丟失其資訊。因為建立一個新的Prefab,則會產生一個新的uuid,這樣原來的引用就不存在了。
4、在產生controller的時候,有判斷animationclip,如果它是休閑或者跑步動作,則自動化佈建其為loop模式。這個不在指令碼中處理,就需要自己一個模型一個模型去修改。如果不屬於LOOP_TAG中的動畫,然後又需要設定loop可以自己在Unity中操作修改。這裡原本我希望可以通過ModelImporter來修改,但是沒有找到正確的方法。所以參考雨松的方式,直接修改AnimationClip的屬性。
這種資源(AssetBundle)使用流程有這麼幾個好處:
1、開發項目工程不會很大。以程式的思路載入資源。否則一個大的MMO,資源量可能幾G。運行Unity都需要等幾分鐘。
2、AssetBundle可以完全由美術搞定,程式只關心資源載入使用。
3、建立AssetBundle的項目可以分類。比如情境美術一個項目,角色美術一個項目等等。這樣可以進一步減小項目,並且減少衝突的機會。