Unity 代码片段2021篇 (记录)

/

///

Unity SpriteAtlas package 预览窗口

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.U2D;
using UnityEditor.U2D;
using System;

public class SpriteAtlasWindowOld : EditorWindow
{
    static SpriteAtlasWindowOld window;

    SpriteAtlas spriteAtlas;

    Sprite[] sprites;
    Texture2D[] texture2Ds;
    Texture2D previewTexture;

    readonly GUIStyle preBackground = "preBackground";

    [MenuItem("Assets/GUI/Sprite preview old")]
    static void Init()
    {
        window = GetWindow<SpriteAtlasWindowOld>("Spriteatlas Preview 01");

        window.spriteAtlas = Selection.activeObject as SpriteAtlas;

        window.sprites = new Sprite[window.spriteAtlas.spriteCount];
        window.spriteAtlas.GetSprites(window.sprites);
        window.texture2Ds = new Texture2D[window.sprites.Length];
        for (int i = 0; i < window.sprites.Length; i++)
        {
            window.texture2Ds[i] = window.sprites[i].texture;

            TextureImporter textureImporter = TextureImporter.GetAtPath(AssetDatabase.GetAssetPath(window.texture2Ds[i])) as TextureImporter;
            textureImporter.isReadable = true;
            textureImporter.SaveAndReimport();
        }
        SpriteAtlasPackingSettings packingsetting = SpriteAtlasExtensions.GetPackingSettings(window.spriteAtlas);
        TextureImporterPlatformSettings platformsetting = SpriteAtlasExtensions.GetPlatformSettings(window.spriteAtlas, "Android");
        window.previewTexture = new Texture2D(platformsetting.maxTextureSize, platformsetting.maxTextureSize);

        window.previewTexture.PackTextures(window.texture2Ds, packingsetting.padding);
    }

    [MenuItem("Assets/GUI/Sprite preview old", true)]
    static bool Valid()
    {
        if (Selection.activeObject.GetType() == typeof(SpriteAtlas))
            return true;
        else
            return false;
    }

    private void OnGUI()
    {
        if (previewTexture != null)
        {
            Rect r = new Rect();
            r.width = previewTexture.width;
            r.height = previewTexture.height;

            if (Event.current.type == EventType.Repaint)
                preBackground.Draw(r, false, false, false, false);

            EditorGUI.DrawTextureTransparent(r, previewTexture);

            GUI.DrawTexture(r, previewTexture);
        }
    }

    private void OnDisable()
    {
        window = null;
    }

}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Reflection;
using UnityEngine.U2D;
using UnityEditor.U2D;
using UnityEditor;

public static class ReflSpriteatlasExtensions
{
    public static Texture2D[] GetPreviewTextures(SpriteAtlas spriteAtlas)
    {
        MethodInfo methodInfo = typeof(SpriteAtlasExtensions).GetMethod("GetPreviewTextures", BindingFlags.NonPublic | BindingFlags.Static);
        if (methodInfo == null)
        {
            Debug.LogError("<color=red> 从 SpriteAtlasExtensions 获取方法为空! </color>");
            return null;
        }
        else
        {
            return methodInfo.Invoke(null, new SpriteAtlas[] { spriteAtlas }) as Texture2D[];
        }
    }
}

public class SpritePreviewWindow : EditorWindow
{
    static SpritePreviewWindow window;

    SpriteAtlas spriteAtlas;

    Texture2D[] previewTextures;

    readonly GUIStyle preBackground = "preBackground";

    [MenuItem("Assets/GUI/Sprite preview 01")]
    static void Init()
    {
        window = GetWindow<SpritePreviewWindow>("Spriteatlas Preview 01");

        window.spriteAtlas = Selection.activeObject as SpriteAtlas;

        SpriteAtlasUtility.PackAtlases(new SpriteAtlas[] { window.spriteAtlas }, BuildTarget.Android);

        //反射拿预览图集图片
        window.previewTextures = ReflSpriteatlasExtensions.GetPreviewTextures(window.spriteAtlas);
    }

    [MenuItem("Assets/GUI/Sprite preview 01", true)]
    static bool Valid()
    {
        if (Selection.activeObject.GetType() == typeof(SpriteAtlas))
            return true;
        else
            return false;
    }

    private void OnGUI()
    {
        if (previewTextures != null && previewTextures.Length > 0 && previewTextures[0] != null)
        {
            Rect r = new Rect();
            r.width = previewTextures[0].width;
            r.height = previewTextures[0].height;

            if (Event.current.type == EventType.Repaint)
                preBackground.Draw(r, false, false, false, false);

            EditorGUI.DrawTextureTransparent(r, previewTextures[0]);

            GUI.DrawTexture(r, previewTextures[0]);
        }
    }

    private void OnDisable()
    {
        window = null;
    }
}

/

通过代码设置project setting属性

using UnityEditor;
using System.Reflection;
using System;
using System.Reflection.Emit;
public class ModifySettings
{
    [MenuItem("Custom/ ModifySettings")]
    private static void ModifySettings()
    {
        //设置属性
        UnityEditor.PlayerSettings.companyName = "XXXX";
        UnityEditor.PlayerSettings.gpuSkinning = !UnityEditor.PlayerSettings.gpuSkinning;
        //下边这个属性比较特别 与平台有关 直接设置在编辑器下看不到变化
        //UnityEditor.PlayerSettings.MTRendering = !UnityEditor.PlayerSettings.MTRendering;
        //通过这个方法设置 平台相关属性
        UnityEngine.Debug.Log(UnityEditor.PlayerSettings.MTRendering);
        UnityEditor.PlayerSettings.SetMobileMTRendering(BuildTargetGroup.Android, UnityEditor.PlayerSettings.MTRendering);
        //可以通过vs中直接查看这个类的属性,如果vs不工作,可以用如下代码反射查看属性
        /**
        Type t = typeof(UnityEditor.PlayerSettings);
        foreach (PropertyInfo pi in t.GetProperties())
        {
            UnityEngine.Debug.Log(pi.Name);
        }
        **/
        //UnityEditor.PlayerSettings.OtherSettings.Mu
    }
}

[MenuItem("Tools/Include Fog in Build")]
        private static void IncludeFogInBuild()
        {
            var path    = "ProjectSettings/GraphicsSettings.asset";
            var manager = AssetDatabase.LoadAllAssetsAtPath(path).FirstOrDefault();
            var obj     = new SerializedObject(manager);
            var prop    = obj.FindProperty("m_FogStripping");
            prop.intValue = 1;  //0: Automatic, 1: Custom
            prop = obj.FindProperty("m_FogKeepLinear");
            prop.boolValue = true;
            prop = obj.FindProperty("m_FogKeepExp");
            prop.boolValue = true;
            prop = obj.FindProperty("m_FogKeepExp2");
            prop.boolValue = true;
            obj.ApplyModifiedProperties();
        }
[MenuItem("Tools/Exclude Fog in Build")]
        private static void ExcludeFogInBuild()
        {
            var path    = "ProjectSettings/GraphicsSettings.asset";
            var manager = AssetDatabase.LoadAllAssetsAtPath(path).FirstOrDefault();
            var obj     = new SerializedObject(manager);
            var prop    = obj.FindProperty("m_FogStripping");
            prop.intValue = 1;  //0: Automatic, 1: Custom
            prop = obj.FindProperty("m_FogKeepLinear");
            prop.boolValue = false;
            prop = obj.FindProperty("m_FogKeepExp");
            prop.boolValue = false;
            prop = obj.FindProperty("m_FogKeepExp2");
            prop.boolValue = false;
            obj.ApplyModifiedProperties();
        }
[SettingsProvider]
public static SettingsProvider CreateCustomSettingsProvider()
{
    var path = "Project/CustomSetting/Sample";
        
    var provider = new SettingsProvider(path, SettingsScope.Project)
    {
        label = "Sample",
            
        guiHandler = searchContext =>
        {
            EditorGUILayout.LabelField("SettingsProvider自定义项目。");
        },
            
        keywords = new HashSet<string>(new[]{ "CustomSetting" }),
    };

    return provider;
}
[SettingsProvider]
public static SettingsProvider CreateCustomSettingsProvider()
{
    var path = "Preferences/CustomSetting/Sample";
        
    var provider = new SettingsProvider(path, SettingsScope.User)
    {
        label = "Sample",
            
        guiHandler = searchContext =>
        {
            EditorGUILayout.LabelField("SettingsProvider自定义项目。");
        },
            
        keywords = new HashSet<string>(new[]{ "CustomSetting" }),
    };

    return provider;
}

missing prefab check

Problem: After unity manually deletes the prefab, it packs Assetbundle for the prefab with the deleted prefab, and unity does not report an error Crash.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
 
 
public class MissingPrefabDetector
{
    [MenuItem("Util/Find Missing Prefab")]
    static void Init()
    {
        string[] allPrefabs = GetAllPrefabs();
 
        int count = 0;
        EditorUtility.DisplayProgressBar("Processing...", "Begin Job", 0);
 
        foreach (string prefab in allPrefabs)
        {
            AssetDatabase.ImportAsset(prefab);
            UnityEngine.Object o = AssetDatabase.LoadMainAssetAtPath(prefab);
 
            if (o == null)
            {
                Debug.Log("prefab " + prefab + " null?");
                continue;
            }
 
            GameObject go;
            try
            {
                go = o as GameObject;
                EditorUtility.DisplayProgressBar("Processing...", go.name, ++count / (float)allPrefabs.Length);
                FindMissingPrefabInGO(go, prefab, true);
            }
            catch
            {
                Debug.Log("For some reason, prefab " + prefab + " won't cast to GameObject");
            }
        }
        EditorUtility.ClearProgressBar();
    }
 
 
    static void FindMissingPrefabInGO(GameObject g, string prefabName, bool isRoot)
    {
        if (g.name.Contains("Missing Prefab"))
        {
            Debug.LogError($"{prefabName} has missing prefab {g.name}");
            return;
        }
 
        if (PrefabUtility.IsPrefabAssetMissing(g))
        {
            Debug.LogError($"{prefabName} has missing prefab {g.name}");
            return;
        }
 
        if (PrefabUtility.IsDisconnectedFromPrefabAsset(g))
        {
            Debug.LogError($"{prefabName} has missing prefab {g.name}");
            return;
        }
 
        if (!isRoot)
        {
            if (PrefabUtility.IsAnyPrefabInstanceRoot(g))
            {
                return;
            }
            GameObject root = PrefabUtility.GetNearestPrefabInstanceRoot(g);
            if (root == g)
            {
                return;
            }
        }
 
 
        // Now recurse through each child GO (if there are any):
        foreach (Transform childT in g.transform)
        {
            //Debug.Log("Searching " + childT.name  + " " );
            FindMissingPrefabInGO(childT.gameObject, prefabName, false);
        }
    }
 
    public static string[] GetAllPrefabs()
    {
        string[] temp = AssetDatabase.GetAllAssetPaths();
        List<string> result = new List<string>();
        foreach (string s in temp)
        {
            if (s.Contains(".prefab")) result.Add(s);
        }
        return result.ToArray();
    }
}

/

protected override void OnPopulateMesh(VertexHelper vh)

Unity UGUI 圆形图片(头像)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Sprites;

//圆形图片
public class CircleImageutils : Image
{
    /// <summary>
    /// 圆形由多少个三角形组成
    /// </summary>
    private int segement = 100;

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear();

        float width = rectTransform.rect.width;
        float height = rectTransform.rect.height;

        Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero;
        float uv_width = uv.z - uv.x;
        float uv_height = uv.w - uv.y;
        Vector2 uvCenter = new Vector2(uv_width * 0.5f, uv_height * 0.5f);
        Vector2 convertRatio = new Vector2(uv_width / width, uv_height / height);

        float radian = 2 * Mathf.PI / segement;
        float radius = width * 0.5f;

        UIVertex origin = new UIVertex();
        origin.color = color;
        origin.position = Vector3.zero;
        origin.uv0 = new Vector2(origin.position.x * convertRatio.x + uvCenter.x, origin.position.y * convertRatio.y + uvCenter.y);
        vh.AddVert(origin);

        int vertexCount = segement + 1;
        float curRadian = 0;
        for (int i = 0; i < vertexCount; i++)
        {
            float x = Mathf.Cos(curRadian) * radius;
            float y = Mathf.Sin(curRadian) * radius;
            curRadian += radian;

            UIVertex vertexTemp = new UIVertex();
            vertexTemp.color = color;
            vertexTemp.position = new Vector2(x, y);
            vertexTemp.uv0 = new Vector2(vertexTemp.position.x * convertRatio.x + uvCenter.x, vertexTemp.position.y * convertRatio.y + uvCenter.y);
            vh.AddVert(vertexTemp);
        }

        int id = 1;
        for (int i = 0; i < segement; i++)
        {
            vh.AddTriangle(id, 0, id + 1);
            id++;
        }
    }
}

/

OnOpenAssetAttribute 具有提供回调中的顺序索引的选项,从 0 开始。如果您有多个 OnOpenAssetAttribute 回调并想以特定顺序调用它们,此选项将非常有用。系统将从零开始按顺序调用回调。

using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;

public class MyAssetHandler
{
    [OnOpenAssetAttribute(1)]
    public static bool step1(int instanceID, int line)
    {
        string name = EditorUtility.InstanceIDToObject(instanceID).name;
        Debug.Log("Open Asset step: 1 (" + name + ")");
        return false; // we did not handle the open
    }

    // step2 has an attribute with index 2, so will be called after step1
    [OnOpenAssetAttribute(2)]
    public static bool step2(int instanceID, int line)
    {
        Debug.Log("Open Asset step: 2 (" + instanceID + ")");
        return false; // we did not handle the open
    }
}

/

Unity 编辑器中获取GameView的分辨率

using UnityEngine;
using System.Collections;
 
public class GameCamera : MonoBehaviour
{
    private Camera _unityCamera;
    private Camera UnityCamera
    {
        get
        {
            if (_unityCamera == null)
            {
                _unityCamera = GetComponent<Camera>();
                if (_unityCamera == null)
                {
                    Debug.LogError("A unity camera must be attached to the GameCamera script");
                }
            }
            return _unityCamera;
        }
    }
 
    public Camera ScreenCamera 
    {
        get
        {
            return UnityCamera;
        }
    }
 
    void Awake()
    {
 
    }
 
#if UNITY_EDITOR
    void Update()
    {    // 打印看看,是不是得到了编辑器的分辨率
        Debug.Log("[INFO] Resolusion: " + GetScreenPixelDimensions(this));
    }
#endif
 
    // 获得分辨率,当选择 Free Aspect 直接返回相机的像素宽和高
    Vector2 GetScreenPixelDimensions(GameCamera settings)
    {
        Vector2 dimensions = new Vector2(ScreenCamera.pixelWidth, ScreenCamera.pixelHeight);
 
#if UNITY_EDITOR
        // 获取编辑器 GameView 的分辨率
        float gameViewPixelWidth = 0, gameViewPixelHeight = 0;
        float gameViewAspect = 0;
 
        if (Editor__GetGameViewSize(out gameViewPixelWidth, out gameViewPixelHeight, out gameViewAspect))
        {
            if (gameViewPixelWidth != 0 && gameViewPixelHeight != 0)
            {
                dimensions.x = gameViewPixelWidth;
                dimensions.y = gameViewPixelHeight;
            }
        }
#endif
 
        return dimensions;
    }
 
#if UNITY_EDITOR
    static bool Editor__getGameViewSizeError = false;
    public static bool Editor__gameViewReflectionError = false;
 
    // 尝试获取 GameView 的分辨率
    // 当正确获取到 GameView 的分辨率时,返回 true
    public static bool Editor__GetGameViewSize(out float width, out float height, out float aspect)
    {
        try
        {
            Editor__gameViewReflectionError = false;
 
            System.Type gameViewType = System.Type.GetType("UnityEditor.GameView,UnityEditor");
            System.Reflection.MethodInfo GetMainGameView = gameViewType.GetMethod("GetMainGameView", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
            object mainGameViewInst = GetMainGameView.Invoke(null, null);
            if (mainGameViewInst == null)
            {
                width = height = aspect = 0;
                return false;
            }
            System.Reflection.FieldInfo s_viewModeResolutions = gameViewType.GetField("s_viewModeResolutions", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
            if (s_viewModeResolutions == null)
            {
                System.Reflection.PropertyInfo currentGameViewSize = gameViewType.GetProperty("currentGameViewSize", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
                object gameViewSize = currentGameViewSize.GetValue(mainGameViewInst, null);
                System.Type gameViewSizeType = gameViewSize.GetType();
                int gvWidth = (int)gameViewSizeType.GetProperty("width").GetValue(gameViewSize, null);
                int gvHeight = (int)gameViewSizeType.GetProperty("height").GetValue(gameViewSize, null);
                int gvSizeType = (int)gameViewSizeType.GetProperty("sizeType").GetValue(gameViewSize, null);
                if (gvWidth == 0 || gvHeight == 0)
                {
                    width = height = aspect = 0;
                    return false;
                }
                else if (gvSizeType == 0)
                {
                    width = height = 0;
                    aspect = (float)gvWidth / (float)gvHeight;
                    return true;
                }
                else
                {
                    width = gvWidth; height = gvHeight;
                    aspect = (float)gvWidth / (float)gvHeight;
                    return true;
                }
            }
            else
            {
                Vector2[] viewModeResolutions = (Vector2[])s_viewModeResolutions.GetValue(null);
                float[] viewModeAspects = (float[])gameViewType.GetField("s_viewModeAspects", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).GetValue(null);
                string[] viewModeStrings = (string[])gameViewType.GetField("s_viewModeAspectStrings", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).GetValue(null);
                if (mainGameViewInst != null
                    && viewModeStrings != null
                    && viewModeResolutions != null && viewModeAspects != null)
                {
                    int aspectRatio = (int)gameViewType.GetField("m_AspectRatio", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).GetValue(mainGameViewInst);
                    string thisViewModeString = viewModeStrings[aspectRatio];
                    if (thisViewModeString.Contains("Standalone"))
                    {
                        width = UnityEditor.PlayerSettings.defaultScreenWidth; height = UnityEditor.PlayerSettings.defaultScreenHeight;
                        aspect = width / height;
                    }
                    else if (thisViewModeString.Contains("Web"))
                    {
                        width = UnityEditor.PlayerSettings.defaultWebScreenWidth; height = UnityEditor.PlayerSettings.defaultWebScreenHeight;
                        aspect = width / height;
                    }
                    else
                    {
                        width = viewModeResolutions[aspectRatio].x; height = viewModeResolutions[aspectRatio].y;
                        aspect = viewModeAspects[aspectRatio];
                        // this is an error state
                        if (width == 0 && height == 0 && aspect == 0)
                        {
                            return false;
                        }
                    }
                    return true;
                }
            }
        }
        catch (System.Exception e)
        {
            if (Editor__getGameViewSizeError == false)
            {
                Debug.LogError("GameCamera.GetGameViewSize - has a Unity update broken this?\nThis is not a fatal error !\n" + e.ToString());
                Editor__getGameViewSizeError = true;
            }
            Editor__gameViewReflectionError = true;
        }
        width = height = aspect = 0;
        return false;
    }
#endif
}

unity editor模式下获取当前gameview视图的分辨率

  1. public void GetGameViewSize(out int width, out int height)

  2. {

  3. System.Type T = System.Type.GetType("UnityEditor.GameView,UnityEditor");

  4. System.Reflection.MethodInfo GetMainGameView = T.GetMethod("GetMainGameView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

  5. System.Object Res = GetMainGameView.Invoke(null, null);

  6. var gameView = (UnityEditor.EditorWindow)Res;

  7. var prop = gameView.GetType().GetProperty("currentGameViewSize", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

  8. var gvsize = prop.GetValue(gameView, new object[0] { });

  9. var gvSizeType = gvsize.GetType();

  10. height = (int)gvSizeType.GetProperty("height", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).GetValue(gvsize, new object[0] { });

  11. width = (int)gvSizeType.GetProperty("width", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).GetValue(gvsize, new object[0] { });

  12. Debug.Log("当前高:"+ height);

  13. Debug.Log("当前宽:" + width);

  14. }

void ChangeScene(int nextSceneIndex) //index of scene to move to

{

SceneManager.LoadScene(nextSceneIndex, LoadSceneMode.Additive);

Scene nextScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);

SceneManager.MoveGameObjectToScene(gameObject, nextScene);

}

/Event Manager//

using GF.Debug;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace GF.EventSystem
{
    /// <summary>
    /// 消息分发器
    /// </summary>
    public class EventManager
    {
        private static EventManager instance;
        public static EventManager Instance
        {
            get
            {
                if (null == instance)
                    instance = new EventManager();
                return instance;
            }
            set
            {
                instance = value;
            }
        }
        /// <summary>
        /// 委托事件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sender"></param>
        /// <param name="evt"></param>
        public delegate void EventHandler<T>(object sender, T evt);

        /// <summary>
        /// 注册事件字典
        /// </summary>
        public Dictionary<Type, List<object>> handlers = new Dictionary<Type, List<object>>();

        /// <summary>
        /// 注册事件
        /// </summary>
        /// <typeparam name="T">Class类型</typeparam>
        /// <param name="handler">函数</param>
        public void Register<T>(EventHandler<T> handler)
        {
            Debugger.Log("注册事件:" + handler.Method.Name);
            Register(typeof(T), handler);
        }

        private void Register<T>(Type EventType, EventHandler<T> handler)
        {
            if (!handlers.ContainsKey(EventType))
            {
                handlers.Add(EventType, new List<object>());
            }
            if (!handlers[EventType].Contains(handler))
            {
                handlers[EventType].Add(handler);
            }
        }

        /// <summary>
        /// 分发事件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sender"></param>
        /// <param name="evt"></param>
        public void Publish<T>(object sender, T evt)
        {
            subscribe(null, typeof(T), evt);
        }

        /// <summary>
        /// 发送通知事件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sender"></param>
        /// <param name="EventType"></param>
        /// <param name="evt"></param>
        private void publish<T>(object sender, Type EventType, T evt)
        {
            if (handlers.ContainsKey(EventType))
            {
                Debugger.Log("public event key:" + typeof(T));
                //移除所有的无效事件
                handlers[EventType].RemoveAll(delegate (object handler) { return handler == null; });

                for (int i = 0; i < handlers[EventType].Count; i++)
                {
                    object handler = handlers[EventType][i];
                    MethodInfo method = handler.GetType().GetMethod("Invoke");
                    method.Invoke(handler, new object[] { sender, evt });
                }
            }
            else
            {
                Debugger.Log(string.Format("<color=red>未注册的Key:{0}</color>", EventType));
            }
        }


        /// <summary>
        /// 注销事件
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="handler">注册函数名</param>
        public void UnRegister<T>(EventHandler<T> handler)
        {
            UnRegister(typeof(T), handler);
        }

        private void UnRegister<T>(Type EventType, EventHandler<T> handler)
        {
            if (handlers.ContainsKey(EventType))
            {
                handlers[EventType].Remove(handler);
                Debugger.Log("Remove Event:" + handler);

                if (0 == handlers[EventType].Count)//注册事件为空时,移除字典Key
                {
                    handlers.Remove(EventType);
                }
            }
        }

        /// <summary>
        /// 清空注册事件
        /// </summary>
        public void Clear()
        {
            handlers.Clear();
        }
    }
}

EventManager.Instance.Register<FishRoundData>(onFrameData);

EventManager.Instance.UnRegister<FishRoundData>(onFrameData);

private void onFrameData(object sender, FishRoundData data)
{

}

EventManager.Instance.Publish<FishRoundData>(this, roundData.Value);

/// <summary>
  /// 游戏框架中包含事件数据的类的基类。
  /// </summary>
  public abstract class GFEventArgs : EventArgs
  {
      /// <summary>
      /// 初始化游戏框架中包含事件数据的类的新实例。
      /// </summary>
      public GFEventArgs()
      {

      }
  }
/// <summary>
    /// 游戏逻辑事件基类。
    /// </summary>
    public abstract class GameEventArgs : GFEventArgs
    {

    }

internal sealed class EventManager1
{
    private readonly List<GameEventArgs> m_EventPool;

    public EventManager1()
    {
        m_EventPool = new List<GameEventArgs>();
    }

    public bool Check(int id, EventHandler<GameEventArgs> handler)
    {
        //检查事件
    }

    public void Subscribe(int id, EventHandler<GameEventArgs> handler)
    {
        //订阅消息
    }

    public void Unsubscribe(int id, EventHandler<GameEventArgs> handler)
    {
        //取消订阅
    }

    public void Fire(object sender, GameEventArgs e)
    {
        //放入事件响应队列
    }

    public void FireNow(object sender, GameEventArgs e)
    {
        //立刻响应
    }
}

Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);

private void OnOpenUIFormSuccess(object sender, GameEventArgs e)
{
    OpenUIFormSuccessEventArgs ne = (OpenUIFormSuccessEventArgs)e;
    // 判断userData是否为自己
    if (ne.UserData != this)
    {
        return;
    }
    Log.Debug("UI_Menu:恭喜你,成功地召唤了我。");
}

///UI显示结构///

UI界面在Hierarchy中的显示


分成三层显示,每一层的就Order in Layer不同,数值越大的越显示在前面,动态加载的UI根据需要显示在具体的哪一层。

窗口基类 WINDOW_BASE

主要就是窗口的管理(初始化、打开、关闭、消息管理),加载对应的界面预设,子窗口管理(添加、打开、关闭)

UIMANAGER设计


主要是UI的加载(同步、异步)和管理(、显示、卸载、获取、关闭、获取UI状态),发送消息

如何打开一个UI界面呢?这里采用类似iOS开发中界面跳转,导航跳转。
我们每创建一个需要跳转显示的界面,我们要创建两个脚本,一个UI的View界面脚本,一个导航脚本:

跳转界面

ScreenViewManager.Inst.MainLayer.BeginNavTo("main");

参数是导航脚本的特性的值

导航跳转原理

上面的截图会发现导航脚本类都继承自IScreenView接口,这个用于导航管理器搜集所有导航组件用的。

接口里面重要的是BeginInit和BeginExit方法,是导航跳转到这个界面和离开这个界面触发的事件方法。所有这些导航跳转脚本都归ScreenViewLayer管理,
这个其实就是导航组件Manager,既然是Manager,那肯定是有所有导航组件的管理器的,

当调用了跳转方法的时候会将这个界面的导航组件添加进这个navViews

/// <summary>
/// 灵魂功能,导航到一个指定名称的ScreenView,可能是向前,也可能是向后
/// </summary>
/// <param name="_name"></param>
/// <param name="onLoad"></param>
public void BeginNavTo(string name)
{
    //
    if (currentView != null && currentView.Name == name)
    {
        Debugger.LogError("别闹,当前就是" + name);
        return;
    }
    //
    IScreenView view = null;
    if (this.allViews.TryGetValue(name, out view))
    {
        view.BeginInit();
        if (currentView != null)
        {
           currentView.BeginExit();
        }
        //
        currentView = view;
        currentViewIndex = -1;
        //
        this.navViews.Add(view);
        if (navViews.Count > 10)
        {
            navViews.RemoveAt(0);
        }
    }
}

这样以便向前,向后跳转。也可以导航到具体的界面,这个也有点跟Cocos的UI管理类似。

根据导航组件加载对应的UI


根据特性标记搜集导航组件,当调用ScreenViewManager.Inst.MainLayer.BeginNavTo(“main”)的时候,导航器会找到对应UI的导航组件然后调用他的BeinInit()方法,在这个方法里面我们去加载想要加载的UI。

以上就是UI导航的原理。

由于C#代码热更,所以不能将C#代码直接挂在Prefab上,我们加载出来的Prefab然后动态给他挂在代码组件,UI组件脚本原来的定义一个组件变量,我们就要在Start或者Awake里面取Find然后GetComponent,如果变量很多就会显得这样的代码很多,感觉很冗余,这里采用自定义特性去找对应的UI上的组件节点。

public class TransformPath : Attribute
{
    public string Path;

    public TransformPath(string path)
    {
        this.Path = path;
    }
}

在界面加载的时候,UITools会调用AutoSetTransformPath方法找到这个界面上特性绑定的节点

static public void AutoSetTransformPath(WindowBase win)
{
    var vt = win.GetType();
    var fields = vt.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
    var vTransform = win.Transform;
    foreach (var f in fields)
    {
        if (f.FieldType.IsSubclassOf(checkType) == false)
        {
            continue;
        }

        //1.自动获取节点
        var _attrs = f.GetCustomAttributes(typeof(TransformPath), false); //as Attribute[];
        if (_attrs != null && _attrs.Length > 0)
        {
            var attr = _attrs.ToList().Find((a) => a is TransformPath) as TransformPath;
            if (attr == null) continue;
            //获取节点,并且获取组件
            var trans = vTransform.Find(attr.Path);
            if (trans == null)
            {
                Debugger.LogError(string.Format("自动设置节点失败:{0} - {1}", vt.FullName, attr.Path));
            }
            var com = trans.GetComponent(f.FieldType);
            if (com == null)
            {
                Debugger.LogError(string.Format("节点没有对应组件:type【{0}】 - {1}", f.FieldType, attr.Path));
            }

            //设置属性
            f.SetValue(win, com);
            //Debug.LogFormat("字段{0}获取到setTransform ,path:{1}" , f.Name , attr.Path);
        }
    }
    #endregion
}

UI窗口之间交互通讯都通过发送消息来实现,这样可以减少UI模块之间的耦合,消息是这样的一个结构:


发送消息

var data = WindowData.Create("AddDial"); //创建一个消息结构,参数是消息名,用于监听,内部会创建一个消息字典
data.AddData("key", tempDialData); //添加消息键值对
UIManager.Inst.SendMessage((int)WinEnum.Win_Menu, data); //通过UI接口发送消息,第一个参数是接受界面ID
消息接受


在接受消息界面添加消息监听,第一个参数是监听的消息名


获取消息内容

每一个界面都继承自Window_Base,WindowsBase里面有一个消息回调表

protected Dictionary<string, Action<WindowData>> callbackMap;

还有消息注册监听方法,也就是我们消息接受调用的监听方法

protected void RegisterAction(string name, Action<WindowData> callback)
{
    callbackMap[name] = callback;
}

发送消息

public void SendMessage(WindowData data)
{
    Action<WindowData> action = null;
    callbackMap.TryGetValue(data.Name, out action);
    if (action != null)
    {
        action(data);
    }
}

UIManager的发送消息,Manager里面缓存了所有的界面,然后根据想要发送消息的界面ID找到对应的界面调用上面的界面的SendMessage方法,这样就明白消息发送的原理

public void SendMessage(int Index, WindowData data)
{
    var uiIndex = Index.GetHashCode();
    if (windowMap.ContainsKey(uiIndex))
    {
        var ui = windowMap[uiIndex];

        if (ui.IsLoad)
        {
            ui.SendMessage(data);
            return;
        }
    }
}

//代码创建UI界面及代码///

选择菜单Tools->AutoGenUI

选择要生成的Prefab界面

点击生成,生成想要的一级、二级UI View代码

自动生成,那UI肯定要符合一定的规范,这里我们将要生成属性字段的节点名后面加上@组件名

生成后的效果

生成的是二级界面,所以继承的是SubWindow,如果是一级界面则继承WindowBase,SubWindow也是继承的WindowBase

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class AutoGenUI : EditorWindow
{
    [MenuItem("Tools/AutoGenUI")]
    static void Audo_Gen_UI()
    {
        EditorWindow.GetWindow<AutoGenUI>();
    }

    void OnGUI()
    {
        GUILayout.Label("选择UI节点,自动生成UI脚本,需要生成的组件节点名后面加@组件名");
        if (GUILayout.Button("开始生成父窗口"))
        {
            if (Selection.activeGameObject)
            {
                Debug.Log("开始生成");
                GenUIUtil.GenWindow(Selection.activeGameObject);
                AssetDatabase.Refresh();
                Debug.Log("生成结束");
            }
        }

        if (GUILayout.Button("开始生成子窗口"))
        {
            if(Selection.activeGameObject)
            {
                Debug.Log("开始生成");
                GenUIUtil.GenSubWindow(Selection.activeGameObject);
                AssetDatabase.Refresh();
                Debug.Log("生成结束");
            }
        }

        if (Selection.activeGameObject != null)
        {
            GUILayout.Label(Selection.activeGameObject.name);
        }
    }

    void OnSelectionChange()
    {
        // 当我们选着有改变的时候
        this.Repaint();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

public class GenUIUtil
{
    const string filePath = "/Code/Game@hotfix/DialGame/";

    static Dictionary<string, string> componentAbbreviation = new Dictionary<string, string>()
    {
        {"Text","txt_" },
        {"Button","btn_" },
        {"Transform","tsf_" },
        {"Image","img_" },
        {"Label","lbl_" },
        {"Input","ipt_" },
        {"InputField","ifd_"},
    };

    public static void GenWindow(GameObject selectGameObject)
    {
        GenUI(true, selectGameObject);
    }

    public static void GenSubWindow(GameObject selectGameObject)
    {
        GenUI(false, selectGameObject);
    }

    static void GenUI(bool windowOrSubWindow, GameObject selectGameObject)
    {
        string gameObjectName = selectGameObject.name;
        string fileName = string.Format("CUI{0}{1}", gameObjectName, windowOrSubWindow ? "Window" : "Widget");
        string fatherClass = windowOrSubWindow ? "WindowBase" : "SubWindow";
        StreamWriter sw = new StreamWriter(Application.dataPath + filePath + fileName + ".cs");

        sw.WriteLine("using GFFramework.UI;");
        sw.WriteLine("using UnityEngine;");
        sw.WriteLine("using UnityEngine.UI;");

        sw.WriteLine("//Create at:" + System.DateTime.Now.ToString());

        sw.WriteLine(string.Format("public class {0} : {1}", fileName, fatherClass));
        sw.WriteLine("{");

        var childs = deepFindChild(selectGameObject.transform);

        foreach (var c in childs)
        {
            var path = getRelativePath(c);
            var nameSplite = c.gameObject.name.Split('@');
            string componentName = nameSplite[1];
            string nodeName = nameSplite[0];
            sw.WriteLine(string.Format("\t[TransformPath(\"{0}\")]", path));
            sw.WriteLine(string.Format("\tprivate {0} {1};", c.gameObject.name.Split('@')[1], (componentAbbreviation[componentName] + nodeName).Replace(" ", "")));
            sw.WriteLine("\n");
        }

        //构造函数
        sw.WriteLine(string.Format("\tpublic {0}(Transform transform) : base(transform)", fileName));
        sw.WriteLine("\t{");
        sw.WriteLine("\t}");
        sw.WriteLine("\n");

        //Init
        sw.WriteLine("\tpublic override void Init()");
        sw.WriteLine("\t{");
        sw.WriteLine("\t\tbase.Init();");
        sw.WriteLine("\t}");
        sw.WriteLine("\n");

        //Close
        sw.WriteLine("\tpublic override void Close()");
        sw.WriteLine("\t{");
        sw.WriteLine("\t\tbase.Close();");
        sw.WriteLine("\t}");
        sw.WriteLine("\n");

        //Open
        sw.WriteLine("\tpublic override void Open(WindowData data = null)");
        sw.WriteLine("\t{");
        sw.WriteLine("\t\tbase.Open();");
        sw.WriteLine("\t}");
        sw.WriteLine("\n");

        //Destroy
        sw.WriteLine("\tpublic override void Destroy()");
        sw.WriteLine("\t{");
        sw.WriteLine("\t\tbase.Destroy();");
        sw.WriteLine("\t}");

        sw.WriteLine("}");
        sw.Flush();
        sw.Close();
    }

    static List<Transform> deepFindChild(Transform root)
    {
        List<Transform> collects = new List<Transform>();
        var childs = root.GetComponentsInChildren<Transform>();
        foreach (var child in childs)
        {
            if (child.name.Contains("@"))
                collects.Add(child);
        }
        return collects;
    }

    static string getRelativePath(Transform obj)
    {
        if (obj.parent.parent)
        {
            return getRelativePath(obj.parent) + "/" + obj.name;
        }
        return obj.name;
    }
}

///代码创建AnimatorController

基于Odin插件的模式

using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using System;
using System.IO;
using System.Windows.Forms;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

public class AnimatorGeneratorWindow : OdinEditorWindow
{
    string fileDirectory;
    bool recursion = false;//是否是递归模式

    [UnityEditor.MenuItem("Tools/AnimatorGenerator")]
    private static void Open()
    {
        var window = GetWindow<AnimatorGeneratorWindow>();
        window.position = GUIHelper.GetEditorWindowRect().AlignCenter(450, 500);
    }
    [InlineEditor(InlineEditorModes.LargePreview)] //对材质和mesh有效,可以预览
    [LabelText("动画模板")]
    public AnimatorController AnimatorControllerTemplate;

    [Button("选择要生成的目录(递归遍历)", ButtonSizes.Medium), GUIColor(1, 1, 0)]
    [HorizontalGroup("ChooseMenu")]
    [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
    private void ChooseMenus()
    {
        Debug.Log("选择目录");
        FolderBrowserDialog fbd = new FolderBrowserDialog();
        fbd.RootFolder = Environment.SpecialFolder.MyComputer;
        if (fbd.ShowDialog() == DialogResult.OK)
        {
            fileDirectory = fbd.SelectedPath;
            recursion = true;
        }
        Debug.Log("选择目录:" + fileDirectory);
    }

    [Button("选择要生成的目录(单文件目录)", ButtonSizes.Medium), GUIColor(1, 1, 0)]
    [HorizontalGroup("ChooseMenu")]
    [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
    private void ChooseMenu()
    {
        Debug.Log("选择目录");
        FolderBrowserDialog fbd = new FolderBrowserDialog();
        fbd.RootFolder = Environment.SpecialFolder.MyComputer;
        if (fbd.ShowDialog() == DialogResult.OK)
        {
            fileDirectory = fbd.SelectedPath;
            recursion = false;
        }
        Debug.Log("选择目录:" + fileDirectory);
    }

    [Button("生成动画控制器", ButtonSizes.Medium), GUIColor(0, 1, 1)]
    [HorizontalGroup("Buttons")]
    [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
    private void GeneratorAnimatorButton()
    {
        Debug.Log("生成动画控制器");
        CreateAnimatorAssets();
    }


    private void CreateAnimatorAssets()
    {
        if (!Directory.Exists(fileDirectory))
        {
            throw new Exception("目录不存在或者路径不存在");
        }
        if (AnimatorControllerTemplate == null)
        {
            Debug.LogError("没有选择动画模板");
            return;
        }

        var animatorFilePath = AssetDatabase.GetAssetPath(AnimatorControllerTemplate);
        var dirArray = fileDirectory.Split('\\');
        var pathLastDirectoryName = dirArray[dirArray.Length - 1];
        var animatorExtension = Path.GetExtension(animatorFilePath);

        if (recursion)
        {
            var folders = Directory.GetDirectories(fileDirectory);
            foreach (var folder in folders)
            {
                SingleFolderDispose(folder, animatorFilePath);
            }
        }
        else
        {
            SingleFolderDispose(fileDirectory, animatorFilePath);
        }
    }


    private void SingleFolderDispose(string folder, string animatorFilePath)
    {
        DirectoryInfo info = new DirectoryInfo(folder);
        string folderName = info.Name;
        var newAnimatorFilePath = Path.Combine(folder, folderName + Path.GetExtension(animatorFilePath));
        File.Copy(animatorFilePath, newAnimatorFilePath, true);
        AssetDatabase.Refresh();
        AnalyzeAnimController(folder, newAnimatorFilePath);
        AssetDatabase.Refresh();
        var obj = LoadFbx(folder, newAnimatorFilePath);
        PrefabUtility.SaveAsPrefabAsset(obj, string.Format("{0}/{1}.prefab", folder, folderName));
        DestroyImmediate(obj);
    }

    private GameObject LoadFbx(string folder, string animatorFilePath)
    {
        //找到fbx,找到当前目录下名字不带@符号的fbx 进行实例化
        FileInfo tempFile = null;
        DirectoryInfo folderDirectoryInfo = new DirectoryInfo(folder);
        var files = folderDirectoryInfo.GetFiles();
        foreach (var fileInfo in files)
        {
            if (!fileInfo.Name.Contains("@") && fileInfo.Name.Contains(".fbx") && !fileInfo.Name.Contains(".meta"))   //TODO:也可以根据floder名去找对应的fbx
            {
                tempFile = fileInfo;
                break;
            }
        }
        if (tempFile == null)
        {
            throw new Exception(string.Format("目录:{0} 没有找到不带@的fbx", folder));
        }

        var obj = Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>(GetAssetPath(tempFile.FullName))) as GameObject;
        //找到controller

        var controller = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(GetAssetPath(animatorFilePath));
        obj.GetComponent<Animator>().runtimeAnimatorController = controller;
        return obj;
    }

    private void AnalyzeAnimController(string floder, string controllerPath)
    {
        var assetPath = GetAssetPath(controllerPath);
        var animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(assetPath);
        string animationFolder = Path.GetDirectoryName(assetPath) + "\\animations";
        animationFolder.Replace("\\", "/");
        //animatorController的Parameters不需要修改
        //遍历所有的layer
        for (int i = 0; i < animatorController.layers.Length; i++)
        {
            var layer = animatorController.layers[i];
            AnimatorStateMachine sm = layer.stateMachine;
            RecursionAnalyzeAnimatorStateMachine(sm, animationFolder);
        }
    }

    private string GetAssetPath(string fullPath)
    {
        var strs = fullPath.Split(new string[] { "Assets" }, StringSplitOptions.None);
        var assetPath = "Assets" + strs[strs.Length - 1];
        assetPath.Replace("\\", "/");
        return assetPath;
    }

    private void RecursionAnalyzeAnimatorStateMachine(AnimatorStateMachine stateMachine, string animationFlolder)
    {
        //遍历states
        for (int i = 0; i < stateMachine.states.Length; i++)
        {
            var animatorState = stateMachine.states[i];
            var motion = animatorState.state.motion;
            if (motion != null)
            {
                if (motion is BlendTree)
                {
                    BlendTree bt = motion as BlendTree;

                    ChildMotion[] childMotions = new ChildMotion[bt.children.Length];


                    for (int j = 0; j < bt.children.Length; j++)
                    {
                        var childMotion = bt.children[j];
                        var motionClip = GetAnimationClip(childMotion.motion.name, animationFlolder);

                        if (motionClip == null)
                        {
                            Debug.LogError("没有找到" + motion.name + "的动画控制器");
                        }
                        else
                        {
                            Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, childMotion.motion));   //根据名字找到对应的prefab 然后找出里面的动画文件加载
                            //childMotion.motion = (Motion)motionClip;

                            //var newChildMotion = new ChildMotion() { motion = motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                            //childMotion = newChildMotion;

                            childMotions[j] = new ChildMotion() { motion = (Motion)motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                        }
                    }
                    //bt.children = childMotions;
                    BlendTree newBt = new BlendTree()
                    {
                        blendParameter = bt.blendParameter,
                        blendParameterY = bt.blendParameterY,
                        blendType = bt.blendType,
                        hideFlags = bt.hideFlags,
                        maxThreshold = bt.maxThreshold,
                        minThreshold = bt.minThreshold,
                        name = bt.name,
                        useAutomaticThresholds = bt.useAutomaticThresholds,
                        children = childMotions,
                    };
                    animatorState.state.motion = newBt;
                }
                else
                {
                    animatorState.state.motion = null;
                    var motionClip = GetAnimationClip(motion.name, animationFlolder);
                    if (motionClip == null)
                    {
                        Debug.LogError("没有找到" + motion.name + "的动画控制器");
                    }
                    else
                    {
                        animatorState.state.motion = (Motion)motionClip;
                        Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, motion));
                    }
                }
            }
        }
        //遍历substatemachine
        for (int j = 0; j < stateMachine.stateMachines.Length; j++)
        {
            var stateMachines = stateMachine.stateMachines[j];
            RecursionAnalyzeAnimatorStateMachine(stateMachines.stateMachine, animationFlolder);
        }
    }

    private AnimationClip GetAnimationClip(string motionName, string animationFolder)
    {
        var motionNameExt = motionName.Substring(motionName.IndexOf("_"));
        DirectoryInfo directoryInfo = new DirectoryInfo(animationFolder);
        FileInfo tempFileInfo = null;
        var files = directoryInfo.GetFiles("*.FBX", SearchOption.AllDirectories);
        for (int i = 0; i < files.Length; i++)
        {
            if (files[i].Name.EndsWith(motionNameExt + ".FBX"))    //有可能是Robert01_gun_jump_start  对应的Robert01@Robert01_gun_jump
            {
                tempFileInfo = files[i];
                break;
            }
        }
        if (tempFileInfo != null)
        {
            var datas = AssetDatabase.LoadAllAssetsAtPath(GetAssetPath(tempFileInfo.FullName));
            if (datas.Length == 0)
            {
                Debug.Log(string.Format("Can't find clip in {0}", tempFileInfo.FullName));
                return null;
            }
            foreach (var data in datas)
            {
                if (!(data is AnimationClip))//如果不是动画文件则跳过
                    continue;
                var newClip = data as AnimationClip;
                return newClip;
            }
        }
        else
        {
            Debug.LogError("没有找到对应的动画FBX:" + motionName);
        }
        return null;
    }
}

基于OnGUI的原生模式

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Windows.Forms;
using System;
using System.IO;
using UnityEditor.Animations;

public class AnimatorEditor : EditorWindow
{
    string fileDirectory = string.Empty;
    string assetDirectory = string.Empty;
    bool recursion = false; //是否是递归模式
    AnimatorController animatorControllerTemplate = null;

    [UnityEditor.MenuItem("Tools/AnimatorEditor")]
    private static void Open()
    {
        var window = EditorWindow.GetWindow(typeof(AnimatorEditor), true, "动画生成器", true);
        window.Show();
    }

    void OnSelectionChange()
    {
        if (Selection.activeObject != null)
        {
            Debug.Log("选择物体:" + Selection.activeObject);
            if (Selection.activeObject is AnimatorController)
            {
                animatorControllerTemplate = Selection.activeObject as AnimatorController;
                Debug.Log("选择的物体是:" + animatorControllerTemplate.name);
            }
            if (Selection.activeObject is DefaultAsset) //选择目录
            {
                var asset = Selection.activeObject as DefaultAsset;
                string[] strs = Selection.assetGUIDs;
                string path = AssetDatabase.GUIDToAssetPath(strs[0]);
                assetDirectory = path;
                fileDirectory = Path.Combine(Environment.CurrentDirectory, path);
                Debug.Log("选择的路径:" + path);
            }
        }
    }

    void OnGUI()
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("选择Controller模板:");
        GUILayout.Label(animatorControllerTemplate == null ? "" : animatorControllerTemplate.name);
        GUILayout.EndHorizontal();
        GUILayout.Space(5);
        GUILayout.BeginHorizontal();
        GUILayout.Label("选择生成的目录:");
        GUILayout.Label(assetDirectory);
        GUILayout.EndHorizontal();
        GUILayout.Space(5);
        GUILayout.BeginHorizontal();
        GUILayout.Label("是否批量生成:");
        recursion = EditorGUILayout.Toggle(recursion);
        GUILayout.EndHorizontal();
        GUILayout.Space(30);
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("选择要生成的目录(递归遍历)"))
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.RootFolder = Environment.SpecialFolder.MyComputer;
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                fileDirectory = fbd.SelectedPath;
                recursion = true;
            }
            Debug.Log("选择目录:" + fileDirectory);
        }
        if (GUILayout.Button("选择要生成的目录(单文件目录)"))
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.RootFolder = Environment.SpecialFolder.MyComputer;
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                fileDirectory = fbd.SelectedPath;
                recursion = false;
            }
            Debug.Log("选择目录:" + fileDirectory);
        }
        GUILayout.EndHorizontal();
        GUILayout.Space(30);
        if (GUILayout.Button("生成AnimatorController"))
        {
            Debug.Log("生成动画控制器");
            CreateAnimatorAssets();
        }
    }


    private void CreateAnimatorAssets()
    {
        if (!Directory.Exists(fileDirectory))
        {
            throw new Exception("目录不存在或者路径不存在");
        }
        if (animatorControllerTemplate == null)
        {
            Debug.LogError("没有选择动画模板");
            return;
        }

        var animatorFilePath = AssetDatabase.GetAssetPath(animatorControllerTemplate);
        var dirArray = fileDirectory.Split('\\');
        var pathLastDirectoryName = dirArray[dirArray.Length - 1];
        var animatorExtension = Path.GetExtension(animatorFilePath);

        if (recursion)
        {
            var folders = Directory.GetDirectories(fileDirectory);
            foreach (var folder in folders)
            {
                SingleFolderDispose(folder, animatorFilePath);
            }
        }
        else
        {
            SingleFolderDispose(fileDirectory, animatorFilePath);
        }
    }


    private void SingleFolderDispose(string folder, string animatorFilePath)
    {
        DirectoryInfo info = new DirectoryInfo(folder);
        string folderName = info.Name;
        var newAnimatorFilePath = Path.Combine(folder, folderName + Path.GetExtension(animatorFilePath));
        File.Copy(animatorFilePath, newAnimatorFilePath, true);
        AssetDatabase.Refresh();
        AnalyzeAnimController(folder, newAnimatorFilePath);
        AssetDatabase.Refresh();
        var obj = LoadFbx(folder, newAnimatorFilePath);
        PrefabUtility.SaveAsPrefabAsset(obj, string.Format("{0}/{1}.prefab", folder, folderName));
        DestroyImmediate(obj);
    }

    private GameObject LoadFbx(string folder, string animatorFilePath)
    {
        //找到fbx,找到当前目录下名字不带@符号的fbx 进行实例化
        FileInfo tempFile = null;
        DirectoryInfo folderDirectoryInfo = new DirectoryInfo(folder);
        var files = folderDirectoryInfo.GetFiles();
        foreach (var fileInfo in files)
        {
            if (!fileInfo.Name.Contains("@") && fileInfo.Name.Contains(".fbx") && !fileInfo.Name.Contains(".meta"))   //TODO:也可以根据floder名去找对应的fbx
            {
                tempFile = fileInfo;
                break;
            }
        }
        if (tempFile == null)
        {
            throw new Exception(string.Format("目录:{0} 没有找到不带@的fbx", folder));
        }

        var obj = Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>(GetAssetPath(tempFile.FullName))) as GameObject;
        //找到controller

        var controller = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(GetAssetPath(animatorFilePath));
        obj.GetComponent<Animator>().runtimeAnimatorController = controller;
        return obj;
    }

    private void AnalyzeAnimController(string floder, string controllerPath)
    {
        var assetPath = GetAssetPath(controllerPath);
        var animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(assetPath);
        string animationFolder = Path.GetDirectoryName(assetPath) + "\\animations";
        animationFolder.Replace("\\", "/");
        //animatorController的Parameters不需要修改
        //遍历所有的layer
        for (int i = 0; i < animatorController.layers.Length; i++)
        {
            var layer = animatorController.layers[i];
            AnimatorStateMachine sm = layer.stateMachine;
            RecursionAnalyzeAnimatorStateMachine(sm, animationFolder);
        }
    }

    private string GetAssetPath(string fullPath)
    {
        var strs = fullPath.Split(new string[] { "Assets" }, StringSplitOptions.None);
        var assetPath = "Assets" + strs[strs.Length - 1];
        assetPath.Replace("\\", "/");
        return assetPath;
    }

    private void RecursionAnalyzeAnimatorStateMachine(AnimatorStateMachine stateMachine, string animationFlolder)
    {
        //遍历states
        for (int i = 0; i < stateMachine.states.Length; i++)
        {
            var animatorState = stateMachine.states[i];
            var motion = animatorState.state.motion;
            if (motion != null)
            {
                if (motion is BlendTree)
                {
                    BlendTree bt = motion as BlendTree;

                    ChildMotion[] childMotions = new ChildMotion[bt.children.Length];


                    for (int j = 0; j < bt.children.Length; j++)
                    {
                        var childMotion = bt.children[j];
                        var motionClip = GetAnimationClip(childMotion.motion.name, animationFlolder);

                        if (motionClip == null)
                        {
                            Debug.LogError("没有找到" + motion.name + "的动画控制器");
                        }
                        else
                        {
                            Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, childMotion.motion));   //根据名字找到对应的prefab 然后找出里面的动画文件加载
                            //childMotion.motion = (Motion)motionClip;

                            //var newChildMotion = new ChildMotion() { motion = motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                            //childMotion = newChildMotion;

                            childMotions[j] = new ChildMotion() { motion = (Motion)motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                        }
                    }
                    //bt.children = childMotions;
                    BlendTree newBt = new BlendTree()
                    {
                        blendParameter = bt.blendParameter,
                        blendParameterY = bt.blendParameterY,
                        blendType = bt.blendType,
                        hideFlags = bt.hideFlags,
                        maxThreshold = bt.maxThreshold,
                        minThreshold = bt.minThreshold,
                        name = bt.name,
                        useAutomaticThresholds = bt.useAutomaticThresholds,
                        children = childMotions,
                    };
                    animatorState.state.motion = newBt;
                }
                else
                {
                    animatorState.state.motion = null;
                    var motionClip = GetAnimationClip(motion.name, animationFlolder);
                    if (motionClip == null)
                    {
                        Debug.LogError("没有找到" + motion.name + "的动画控制器");
                    }
                    else
                    {
                        animatorState.state.motion = (Motion)motionClip;
                        Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, motion));
                    }
                }
            }
        }
        //遍历substatemachine
        for (int j = 0; j < stateMachine.stateMachines.Length; j++)
        {
            var stateMachines = stateMachine.stateMachines[j];
            RecursionAnalyzeAnimatorStateMachine(stateMachines.stateMachine, animationFlolder);
        }
    }

    private AnimationClip GetAnimationClip(string motionName, string animationFolder)
    {
        var motionNameExt = motionName.Substring(motionName.IndexOf("_"));
        DirectoryInfo directoryInfo = new DirectoryInfo(animationFolder);
        FileInfo tempFileInfo = null;
        var files = directoryInfo.GetFiles("*.FBX", SearchOption.AllDirectories);
        for (int i = 0; i < files.Length; i++)
        {
            if (files[i].Name.EndsWith(motionNameExt + ".FBX"))    //有可能是Robert01_gun_jump_start  对应的Robert01@Robert01_gun_jump
            {
                tempFileInfo = files[i];
                break;
            }
        }
        if (tempFileInfo != null)
        {
            var datas = AssetDatabase.LoadAllAssetsAtPath(GetAssetPath(tempFileInfo.FullName));
            if (datas.Length == 0)
            {
                Debug.Log(string.Format("Can't find clip in {0}", tempFileInfo.FullName));
                return null;
            }
            foreach (var data in datas)
            {
                if (!(data is AnimationClip))//如果不是动画文件则跳过
                    continue;
                var newClip = data as AnimationClip;
                return newClip;
            }
        }
        else
        {
            Debug.LogError("没有找到对应的动画FBX:" + motionName);
        }
        return null;
    }
}

打开弹框可以用Unity内置的System.Windows.Forms.dll的api来打开,将其放在Plugins下,打开方法:

public void OpenFile()
{
    OpenFileDialog dialog = new OpenFileDialog();
    dialog.Filter = "exe files (*.exe)|*.exe";  //过滤文件类型
    dialog.InitialDirectory = "D:\\";  //定义打开的默认文件夹位置,可以在显示对话框之前设置好各种属性
    if (dialog.ShowDialog() == DialogResult.OK)
    {
        Debug.Log(dialog.FileName);
    }
}

创建控制器

using System;
using UnityEngine;
using System.Collections;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using System.Collections.Generic;

public class AnimatorTool : MonoBehaviour
{
    private static List<AnimatorState> stateList = new List<AnimatorState>();
    /// <summary>
    /// 菜单方法,遍历文件夹创建Animation Controller
    /// </summary>
    [MenuItem("Tools/CreateAnimator")]
    static void CreateAnimationAssets()
    {
        string rootFolder = "Assets/Resources/Fbx/";
        if (!Directory.Exists(rootFolder))
        {
            Directory.CreateDirectory(rootFolder);
            return;
        }
        // 遍历目录,查找生成controller文件
        var folders = Directory.GetDirectories(rootFolder);
        foreach (var folder in folders)
        {
            DirectoryInfo info = new DirectoryInfo(folder);
            string folderName = info.Name;
            // 创建animationController文件
            AnimatorController aController =
                AnimatorController.CreateAnimatorControllerAtPath(string.Format("{0}/animation.controller", folder));  //在对应目录生成AnimatorController文件

            //添加参数
            aController.AddParameter("run", AnimatorControllerParameterType.Bool);
            aController.AddParameter("attack01", AnimatorControllerParameterType.Bool);

            // 得到其layer
            var layer = aController.layers[0];//Base Layer
            // 绑定动画文件
            AddStateTranstion(string.Format("{0}/{1}_model.fbx", folder, folderName), layer);
            Debug.Log(string.Format("<color=yellow>{0}</color>", layer));
            // 创建预设
            GameObject go = LoadFbx(folderName);
            PrefabUtility.CreatePrefab(string.Format("{0}/{1}.prefab", folder, folderName), go);
            DestroyImmediate(go);
        }

    }

    /// <summary>
    /// 添加动画状态机状态
    /// </summary>
    /// <param name="path"></param>
    /// <param name="layer"></param>
    private static void AddStateTranstion(string path, AnimatorControllerLayer layer)
    {
        AnimatorStateMachine sm = layer.stateMachine;  //状态机
        // 根据动画文件读取它的AnimationClip对象
        var datas = AssetDatabase.LoadAllAssetsAtPath(path);
        if (datas.Length == 0)
        {
            Debug.Log(string.Format("Can't find clip in {0}", path));
            return;
        }
        /*
        //创建默认state  
        AnimatorState defaultState = sm.AddState("default", new Vector3(300, 0, 0));
        //defaultState.motion=  
        sm.defaultState = defaultState;
        AnimatorStateTransition defaultTransition = sm.AddAnyStateTransition(defaultState);
        defaultTransition.AddCondition(AnimatorConditionMode.If, 0, "default");
        */
        // 先添加一个默认的空状态
        var emptyState = sm.AddState("empty", new Vector3(500, 0, 0));
        sm.AddAnyStateTransition(emptyState);

        //遍历模型中包含的动画片段,将其加入状态机中
        foreach (var data in datas)
        {
            int index = 0;
            if (!(data is AnimationClip)) //如果不是动画文件则跳过
                continue;
            var newClip = data as AnimationClip; //如果是的话则转化

            if (newClip.name.StartsWith("__"))
                continue;
            // 取出动画名字,添加到state里面
            AnimatorState state = sm.AddState(newClip.name, new Vector3(500, sm.states.Length * 60, 0)); //将动画添加到动画控制器
            stateList.Add(state);
            if (state.name == "walk")
            {
                sm.defaultState = state;   //将walk设置为默认动画
            }
            Debug.Log(string.Format("<color=red>{0}</color>", state));
            index++;
            state.motion = newClip; //设置动画状态指定到自己的动画文件
            // 把State添加在Layer里面
            sm.AddAnyStateTransition(state); //将动画状态连线到AnyState
        }

        AddTransition(sm, "walk", "run", 1);
        AddTransition(sm, "run", "walk", 0);

        AddTransition(sm, "walk", "attack01", 1);
        AddTransition(sm, "attack01", "walk", 0);

        AddSuMechie(sm, 2, path, layer, "sub2Machine");
    }

    static void AddSuMechie(AnimatorStateMachine machine, int index1, string path, AnimatorControllerLayer layer, string sunStateMachine)
    {
        创建子状态机  
        //for (int k = 1; k < index1; k++)
        //{
        //    AnimatorStateMachine sub2Machine = machine.AddStateMachine("sub2Machine", new Vector3(100, 300, 0));
        //}
        AnimatorStateMachine sub2Machine = machine.AddStateMachine(sunStateMachine, new Vector3(100, 300, 0));

        // 根据动画文件读取它的AnimationClip对象
        var datas = AssetDatabase.LoadAllAssetsAtPath(path);
        if (datas.Length == 0)
        {
            Debug.Log(string.Format("Can't find clip in {0}", path));
            return;
        }
        foreach (var data in datas)
        {
            int index = 0;
            if (!(data is AnimationClip))
                continue;
            var newClip = data as AnimationClip;

            if (newClip.name.StartsWith("__"))
                continue;
            // 取出动画名字,添加到state里面
            AnimatorState state = sub2Machine.AddState(newClip.name, new Vector3(500, sub2Machine.states.Length * 60, 0));
            stateList.Add(state);
            if (state.name == "walk")
            {
                sub2Machine.defaultState = state;
            }
            Debug.Log(string.Format("<color=red>{0}</color>", state));
            index++;
            state.motion = newClip;
            // 把State添加在Layer里面
            sub2Machine.AddAnyStateTransition(state);
        }
    }

    /// <summary>
    /// 添加状态之间的连线
    /// </summary>
    /// <param name="stateM">状态</param>
    /// <param name="ani_name"></param>
    /// <param name="ani_des"></param>
    /// <param name="flag"></param>
    static void AddTransition(AnimatorStateMachine stateM, string ani_name, string ani_des, int flag)
    {
        foreach (var item in stateM.states)
        {
            if (item.state.name == ani_name)
            {
                foreach (var des in stateM.states)
                {
                    if (des.state.name == ani_des)
                    {
                        AnimatorStateTransition transition = item.state.AddTransition(des.state); //添加连线
                        transition.hasExitTime = true;
                        transition.exitTime = 0.8f;
                        if (flag == 1)
                            transition.AddCondition(AnimatorConditionMode.If, flag, ani_des); //添加连线状态
                        else
                        {
                            transition.AddCondition(AnimatorConditionMode.IfNot, flag, ani_name);
                        }
                    }
                }
            }
        }
        Resources.UnloadUnusedAssets(); //卸载资源
    }


    /// <summary>
    /// 生成带动画控制器的对象
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public static GameObject LoadFbx(string name)
    {
        var obj = Instantiate(Resources.Load(string.Format("Fbx/{0}/{0}_model", name))) as GameObject;
        obj.GetComponent<Animator>().runtimeAnimatorController =
            Resources.Load<RuntimeAnimatorController>(string.Format("fbx/{0}/animation", name));
        return obj;
    }
}

新生成的动画控制器在关闭Unity之后,重新打开会发现新生成的Animaor出问题了,motion丢失,但测试下来直接将Clip赋给Motin没有问题,子状态机这种情况也没有问题,唯独BlendTree有问题,我对比新生成的Animator文件跟模板Animator文件对比发现FileID=0,也就是说BlendTree文件并没有保存下来,但BlendTree又不像动画Clip那样我们能直接看到,经过查看Animator文件的数据会发现BlendTree信息写在Animator中,也就是BT并没有保存下来

//

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中可以使用JsonUtility类来将对象序列化为JSON格式,并将其保存到本地文件中。您可以创建一个包含聊天记录的对象,并使用JsonUtility将其转换为JSON字符串,然后将其保存到本地文件中。以下是一个示例代码片段: ```c# //创建一个包含聊天记录的对象 [Serializable] public class ChatLog { public List<string> messages; } //将聊天记录保存到本地文件 ChatLog chatLog = new ChatLog(); chatLog.messages = new List<string>(); chatLog.messages.Add("Hello"); chatLog.messages.Add("World"); string json = JsonUtility.ToJson(chatLog); File.WriteAllText(Application.dataPath + "/chatLog.json", json); ``` 在这个示例中,我们创建了一个ChatLog对象,其中包含了一个字符串列表messages,用于保存聊天记录。我们将一些示例消息添加到列表中,然后使用JsonUtility将ChatLog对象转换为JSON字符串。最后,我们使用File.WriteAllText将JSON字符串写入到本地文件chatLog.json中。 要检索聊天记录,您可以使用类似的方法从本地文件中读取JSON字符串,并使用JsonUtility将其反序列化为ChatLog对象。以下是一个示例代码片段: ```c# //从本地文件中读取聊天记录 string json = File.ReadAllText(Application.dataPath + "/chatLog.json"); ChatLog chatLog = JsonUtility.FromJson<ChatLog>(json); //显示聊天记录 foreach (string message in chatLog.messages) { Debug.Log(message); } ``` 在这个示例中,我们使用File.ReadAllText从本地文件chatLog.json中读取JSON字符串。然后,我们使用JsonUtility.FromJson将JSON字符串反序列化为ChatLog对象。最后,我们遍历ChatLog对象的messages列表,并将每条消息输出到控制台中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值