/
///
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视图的分辨率
-
public void GetGameViewSize(out int width, out int height)
-
{
-
System.Type T = System.Type.GetType("UnityEditor.GameView,UnityEditor");
-
System.Reflection.MethodInfo GetMainGameView = T.GetMethod("GetMainGameView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
-
System.Object Res = GetMainGameView.Invoke(null, null);
-
var gameView = (UnityEditor.EditorWindow)Res;
-
var prop = gameView.GetType().GetProperty("currentGameViewSize", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
-
var gvsize = prop.GetValue(gameView, new object[0] { });
-
var gvSizeType = gvsize.GetType();
-
height = (int)gvSizeType.GetProperty("height", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).GetValue(gvsize, new object[0] { });
-
width = (int)gvSizeType.GetProperty("width", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).GetValue(gvsize, new object[0] { });
-
Debug.Log("当前高:"+ height);
-
Debug.Log("当前宽:" + width);
-
}
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并没有保存下来
//