using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
using UnityEngine.Rendering;
using System;
public class ModelInfoChecker
{
#region 通用函数
/// <summary>
/// 判断选的的路径是否合规
/// </summary>
/// <param name="checkPath"></param>
/// <param name="selectPath"></param>
/// <returns></returns>
private static bool CheckSelectPath(string[] checkPathList,out string selectPath)
{
//右键选择文件夹
string guid = Selection.assetGUIDs[0];
selectPath = AssetDatabase.GUIDToAssetPath(guid);
string findPathShow="";
foreach (var path in checkPathList)
{
if (selectPath.Contains(path))
{
return true;
}
if (findPathShow == "")
{
findPathShow += path;
}
else
{
findPathShow += "或"+path;
}
}
string tip = string.Format("当前检测文件夹不是{0}", findPathShow);
EditorUtility.DisplayDialog("提示", tip, "确定");
return false;
}
//参数1 为要查找的总路径, 参数2 保存路径
private static void GetTargetResourceDirs(string dirPath, ref List<string> dirs,string[]ignoreList,string[]extensionList)
{
if (Path.GetExtension(dirPath)!="")
{
//当前选中是资源
if (CheckName(dirPath,ignoreList))
{
dirs.Add(dirPath);
}
return;
}
foreach (string path in Directory.GetFiles(dirPath))
{
//获取所有文件夹中包含后缀为 .prefab 的路径
foreach (string extension in extensionList)
{
if (Path.GetExtension(path) == extension)
{
string final_path = path.Substring(path.IndexOf("Asset")).Replace(@"\", "/");
if (CheckName(final_path,ignoreList))
{
dirs.Add(final_path);
}
break;
}
}
}
if (Directory.GetDirectories(dirPath).Length > 0) //遍历所有文件夹
{
foreach (string path in Directory.GetDirectories(dirPath))
{
GetTargetResourceDirs(path, ref dirs,ignoreList,extensionList);
}
}
}
private static bool CheckName(string prefabName,string[] IgnoreList)
{
foreach (string name in IgnoreList)
{
if (prefabName.Contains(name))
{
return false;
}
}
return true;
}
#endregion
//模型资源修改
public static void ModelInfoModifyFunc(string[] checkPathList,Action<string>checkCallBack,string tip,string[]ignoreList,string[]extensionList)
{
string selectPath;
if (!CheckSelectPath(checkPathList,out selectPath))
return;
List<string> dirs = new List<string>();
GetTargetResourceDirs(selectPath, ref dirs,ignoreList,extensionList);
if (dirs.Count==0)
{
EditorUtility.DisplayDialog("提示", "未找到需要修改的资源", "确定");
return;
}
for (int i=0;i<dirs.Count;++i)
{
int startIndex = dirs[i].LastIndexOf("/") + 1;
int extensionLength = Path.GetExtension(dirs[i]).Length;
int length = dirs[i].Length - startIndex - extensionLength;
string fileName = dirs[i].Substring(startIndex,length);
string msg = string.Format("修改资源 {0} {1}.......{2}/{3}",fileName,tip,i,dirs.Count);
EditorUtility.DisplayProgressBar("提示",msg,(float)i/dirs.Count);
checkCallBack?.Invoke(dirs[i]);
}
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
string messag = string.Format("{0}修改完毕,请记得将修改后的资源上传P4", tip);
EditorUtility.DisplayDialog("提示", messag, "确定"); //修改动画压缩格式的时候,此处会引发GUI报错,可忽视
}
public delegate bool CheckFuncDelegate(string path);
//对指定路径,指定后缀资源进行筛选
public static void ModelInfoCheckFunc(string[] checkPath,CheckFuncDelegate checkFunc,string tip,string[]ignoreList,string[]extensionList,string writePath)
{
string selectPath;
if (!CheckSelectPath(checkPath, out selectPath))
return;
List<string> dirs=new List<string>();
GetTargetResourceDirs(selectPath,ref dirs,ignoreList,extensionList);
List<string> validList=new List<string>();
for (int i=0;i<dirs.Count;++i)
{
int startIndex = dirs[i].LastIndexOf("/") + 1;
int extensionLength = Path.GetExtension(dirs[i]).Length;
int length = dirs[i].Length - startIndex - extensionLength;
string fileName = dirs[i].Substring(startIndex,length);
string msg = string.Format("{0}{1}筛选中...",tip,fileName);
EditorUtility.DisplayProgressBar("提示",msg,(float)i/dirs.Count);
if (checkFunc(dirs[i]))
{
validList.Add(dirs[i]);
}
}
EditorUtility.ClearProgressBar();
if (validList.Count==0)
{
EditorUtility.DisplayDialog("提示", "未找到指定资源", "确定");
return;
}
if (writePath!="")
{
//需要把获得路径保存
WriteText(writePath,validList);
AssetDatabase.Refresh();
}
EditorUtility.DisplayDialog("提示", "资源筛查完成", "确定");
}
private static void WriteText(string path,List<string>dirList)
{
string savePath = Application.dataPath + path;
if (File.Exists(savePath))
{
FileInfo fileInfo=new FileInfo(savePath);
fileInfo.Delete();
}
FileStream fileStream = new FileStream(savePath,FileMode.OpenOrCreate);
StreamWriter sw = new StreamWriter(fileStream,Encoding.UTF8);
foreach (var item in dirList)
{
sw.WriteLine(item);
}
sw.Close();
fileStream.Close();
}
#region 模型Actor,Collider相关初始化
[MenuItem("Assets/角色模型/Actor及Collider初始化",false,2)]
public static void CheckModelActorInfo()
{
string[] findRootPath = {"Assets/ResourcesAB/Prefab/Models"};
string tip = "Actor/Collider属性";
string[] ignoreList = {"hair","head"};//剔除掉 头 头发
string[] extensionList = { ".prefab"};
ModelInfoModifyFunc(findRootPath,ActorAndColliderInfoInit,tip,ignoreList,extensionList);
}
private static void ActorAndColliderInfoInit(string prefabPath)
{
//两种 一种是骨骼mesh分离,一种未分离
//首先判断是否挂载有Actor
GameObject prefabObj=AssetDatabase.LoadAssetAtPath(prefabPath,typeof(GameObject))as GameObject;
Actor actor = prefabObj.GetComponent<Actor>();
//没有挂在Actor,有可能属于分离的mesh
if (actor == null)
{
if (prefabPath.Contains("_ske"))
{
Debug.LogError("prefabPath 没有挂载Actor,请在角色编辑器中重新生成");
}
return;
}
RefreshActorInfo(actor,prefabPath);
//判断是否有Collider
if (actor.capsuleCollider==null)
{
RefreshColliderInfo(actor,prefabPath);
}
EditorUtility.SetDirty(prefabObj);
AssetDatabase.SaveAssets();
}
private static void RefreshActorInfo(Actor actor,string objPath)
{
GameObject avatarObj = actor.avatar;
if (avatarObj==null)
{
avatarObj = actor.transform.FindChildRecursively("avatar").gameObject;
actor.avatar = avatarObj;
}
if (actor.cachedAnimator==null)
{
Animator animator = avatarObj.GetOrAddComponent<Animator>();
actor.cachedAnimator = animator;
}
if (actor.cachedAnimationEventListener==null)
{
AnimationEventListener ael = avatarObj.GetOrAddComponent<AnimationEventListener>();
ael.actor = actor;
actor.cachedAnimationEventListener = ael;
}
if (actor.particleRoot==null)
{
//缺少粒子节点,需要先实例化才能创建子物体
GameObject skeObj = GameObject.Instantiate(actor.gameObject);
GameObject paticleObj = new GameObject("particleRoot");
paticleObj.transform.SetParent(skeObj.transform, false);
Actor actorSke = skeObj.GetComponent<Actor>();
actorSke.particleRoot = paticleObj.transform;
PrefabUtility.SaveAsPrefabAsset(skeObj, objPath);
GameObject.DestroyImmediate(skeObj);
}
}
private static void RefreshColliderInfo(Actor actor,string objPath)
{
//判断是否是_ske
if (objPath.Contains("_ske"))
{
CapsuleCollider capsuleCollider = actor.gameObject.AddComponent<CapsuleCollider>();
//载入对应的mesh文件,计算collider大小,类似采集物的没有骨骼mesh分离的 不需要Collider Collider是用于鼠标点击模型的交互
string meshObjPath = objPath.Replace("_ske","");
GameObject meshObj = AssetDatabase.LoadAssetAtPath(meshObjPath,typeof(GameObject))as GameObject;
Renderer[] renderers = meshObj.transform.GetComponentsInChildren<SkinnedMeshRenderer>();
if (renderers.Length==0)
{
renderers = meshObj.transform.GetComponentsInChildren<MeshRenderer>();
}
if (renderers.Length!=0)
{
Bounds bounds = renderers[0].bounds;
for (int i=1;i<renderers.Length;++i)
{
bounds.Encapsulate(renderers[i].bounds);
}
capsuleCollider.radius = Mathf.Abs(Mathf.Max(bounds.extents.x,bounds.extents.z));
}
else
{
capsuleCollider.radius = 1.5f;
}
GameObject headObj = actor.transform.FindChildRecursively("fx_head").gameObject;
GameObject avatarObj = actor.avatar;
if (avatarObj==null)
return;
if (headObj!=null)
{
Vector3 offset = headObj.transform.position - avatarObj.transform.position;
Vector3 offsetLocal = avatarObj.transform.InverseTransformVector(offset);
capsuleCollider.height=Mathf.Abs(offsetLocal.y);
if (Mathf.Approximately(capsuleCollider.height,0))
{
capsuleCollider.height = 1.5f;
}
capsuleCollider.center=new Vector3(0,capsuleCollider.height/2f,0);
}
else
{
capsuleCollider.height = 1.5f;
capsuleCollider.center=new Vector3(0,capsuleCollider.height/2f,0);
}
actor.capsuleCollider = capsuleCollider;
}
}
#endregion
#region 修改模型Render属性
// 在菜单来创建 选项 , 点击该选项执行搜索代码
[MenuItem("Assets/角色模型/Mesh属性修改",false,2)]
public static void CheckMeshRenderInfo()
{
string[] findRootPath = {"Assets/ResourcesAB/Prefab/Models"};
string tip = "MeshRender属性";
string[] ignoreNameList = {"_ske"};
string[] extensionList = { ".prefab"};
ModelInfoModifyFunc(findRootPath,ChangePrefabPropety,tip,ignoreNameList,extensionList);
}
private static void ChangePrefabPropety(string prefabPath)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(System.Object)) as GameObject;
if (prefab == null)
return;
//一类人型SkinMesh(挂载有MeshNodes),一类没有挂MeshNodes,一类采集物等MeshRender,
MeshNodes meshNodes = prefab.GetComponent<MeshNodes>();
if (meshNodes != null && meshNodes.skinnedMeshInfoList != null)
{
for (int i=0;i<meshNodes.skinnedMeshInfoList.Count;++i)
{
SkinnedMeshInfo meshInfo = meshNodes.skinnedMeshInfoList[i];
SkinnedMeshRenderer smr = meshInfo.smr;
if (smr!=null)
{
RefreshSkinMeshSettings(smr);
}
}
}
else
{
MeshRenderer[] mrs = prefab.GetComponentsInChildren<MeshRenderer>();
foreach (MeshRenderer mr in mrs)
{
RefreshMeshSettings(mr);
}
SkinnedMeshRenderer[] smrs = prefab.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (SkinnedMeshRenderer smr in smrs)
{
RefreshSkinMeshSettings(smr);
}
}
EditorUtility.SetDirty(prefab);
AssetDatabase.SaveAssets();
}
private static void RefreshSkinMeshSettings(SkinnedMeshRenderer sm)
{
if (sm == null)
return;
sm.lightProbeUsage = LightProbeUsage.Off;
sm.reflectionProbeUsage = ReflectionProbeUsage.Off;
sm.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
}
private static void RefreshMeshSettings(MeshRenderer mr)
{
if (mr == null)
return;
mr.lightProbeUsage = LightProbeUsage.Off;
mr.reflectionProbeUsage = ReflectionProbeUsage.Off;
mr.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
}
#endregion
#region 筛选动画文件压缩是否合规
[MenuItem("Assets/动画工具/动画压缩格式筛选",false,2)]
public static void CheckAnimationCompression()
{
string[] findRootPath = {"Assets/ArtAssets/Animation","Assets/ArtAssets/CutScene"};
string tip = "动画文件压缩格式";
string[] ignoreNameList = { };
string[] extensionList = { ".FBX"};
string saveTxtPath = "\\AnimationCompressionCheck.txt";
ModelInfoCheckFunc(findRootPath,AnimationCompressionCheck,tip,ignoreNameList,extensionList,saveTxtPath);
}
private static bool AnimationCompressionCheck(string path)
{
ModelImporter model=AssetImporter.GetAtPath(path)as ModelImporter;
if(model.animationCompression!= ModelImporterAnimationCompression.Optimal)
return true;
return false;
}
#endregion
#region FBX文件 animation压缩格式修改
[MenuItem("Assets/动画工具/修改动画压缩格式",false,2)]
public static void CheckAnimationCompressionInfo()
{
string[] findRootPath = {"Assets/ArtAssets/Animation","Assets/ArtAssets/CutScene"};
string tip = "动画文件压缩格式";
string[] ignoreNameList = { };
string[] extensionList = { ".FBX"};
ModelInfoModifyFunc(findRootPath,ModifyAnimationCompression,tip,ignoreNameList,extensionList);
}
private static void ModifyAnimationCompression(string path)
{
ModelImporter model=AssetImporter.GetAtPath(path)as ModelImporter;
if (model == null)
return;
if (model.animationCompression != ModelImporterAnimationCompression.Optimal)
{
model.animationCompression = ModelImporterAnimationCompression.Optimal;
AssetDatabase.SaveAssets();
AssetDatabase.ImportAsset(path);
}
}
#endregion
}
可用于右键选中某文件夹,筛选符合规范的文件,输出到txt中,或修改资源属性
API总结:
1.右键选中资源路径
string guid = Selection.assetGUIDs[0];
selectPath = AssetDatabase.GUIDToAssetPath(guid);
2.获取后缀
Path.GetExtension
3.获取指定路径内资源以及子文件夹
foreach (string path in Directory.GetFiles(dirPath))
foreach (string path in Directory.GetDirectories(dirPath))
4.Editor提示弹窗
EditorUtility.DisplayDialog("提示", "未找到需要修改的资源", "确定");
5.Editor进度弹窗
EditorUtility.DisplayProgressBar("提示",msg,(float)i/dirs.Count);
EditorUtility.ClearProgressBar();
6.写入txt
private static void WriteText(string path,List<string>dirList)
{
string savePath = Application.dataPath + path;
if (File.Exists(savePath))
{
FileInfo fileInfo=new FileInfo(savePath);
fileInfo.Delete();
}
FileStream fileStream = new FileStream(savePath,FileMode.OpenOrCreate);
StreamWriter sw = new StreamWriter(fileStream,Encoding.UTF8);
foreach (var item in dirList)
{
sw.WriteLine(item);
}
sw.Close();
fileStream.Close();
}
7.Editor载入资源,修改完毕后设置脏标记,保存 将实例化后的午提重新保存为预制体,覆盖原预制体
//第一种 只载入资源
GameObject prefabObj=AssetDatabase.LoadAssetAtPath(prefabPath,typeof(GameObject))as GameObject;
EditorUtility.SetDirty(prefabObj);
AssetDatabase.SaveAssets();
//第二种 将载入的资源实例化,修改后重新保存为预制体, 适用于需要实例化后设置子物体的操作
GameObject skeObj = GameObject.Instantiate(actor.gameObject);
PrefabUtility.SaveAsPrefabAsset(skeObj, objPath);
GameObject.DestroyImmediate(skeObj);
8.载入FBX资源
private static void ModifyAnimationCompression(string path)
{
ModelImporter model=AssetImporter.GetAtPath(path)as ModelImporter;
if (model == null)
return;
if (model.animationCompression != ModelImporterAnimationCompression.Optimal)
{
model.animationCompression = ModelImporterAnimationCompression.Optimal;
AssetDatabase.ImportAsset(path);
}
}