NLDClient-yudde/ProjectNLD/Assets/Art/temp/Editor/AnimationTools/AnimationToolWindow.cs

470 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System.Collections.Generic;
using System.IO;
using System.Linq;
using Framework;
using Gameplay.Character;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Art.temp.Editor.AnimationTools
{
public class AnimationTool : EditorWindow
{
[MenuItem("Test/Animation 工具/Animation")]
public static void ShowWindow()
{
GetWindow<AnimationTool>("Animation Tools");
}
public void CreateGUI()
{
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Art/temp/Editor/CommonUSSFile.uss");
rootVisualElement.styleSheets.Add(styleSheet);
Box box1 = new();
box1.Add(new Label("Anim file tool"));
box1.Add(new Button() { name = "meta_btn", text = "重新生成 meta 文件并设置 avatar" });
box1.Add(new Button() { name = "renameClip_btn", text = "rename animationClip" });
box1.Add(new Button() { name = "copyAnim_btn", text = "提取 anim to compressedShow" });
box1.Add(new Button() { name = "copyTempAnim_btn", text = "提取 anim to compressed" });
box1.Add(new Button() { name = "find something", text = "Find Fire" });
rootVisualElement.Add(box1);
box1.Q<Button>("meta_btn").RegisterCallback<ClickEvent>(DeleteMeta);
box1.Q<Button>("renameClip_btn").RegisterCallback<ClickEvent>(RenameAnim);
box1.Q<Button>("copyAnim_btn").RegisterCallback<ClickEvent>(CopySelectedToCompressedShow);
box1.Q<Button>("copyTempAnim_btn").RegisterCallback<ClickEvent>(CopySelectedToTemp);
box1.Q<Button>("find something").RegisterCallback<ClickEvent>(FindSomething);
Box box2 = new();
rootVisualElement.Add(box2);
box2.Add(new Label("other files tool"));
box2.Add(new Button() { name = "renameAssets_btn", text = "rename assets" });
box2.Q<Button>("renameAssets_btn").RegisterCallback<ClickEvent>(RenameAssets);
box2.Add(new Button() { name = "test", text = "test" });
box2.Q<Button>("test").RegisterCallback<ClickEvent>(Test);
box2.Add(new Button() { name = "MeshSimplify_btn", text = "character mesh simplify" });
box2.Q<Button>("MeshSimplify_btn").RegisterCallback<ClickEvent>(SimplifyCharacterMesh);
box2.Add(new Button() { name = "vehicleShader_btn", text = "vehicleShader" });
box2.Q<Button>("vehicleShader_btn").RegisterCallback<ClickEvent>(SetVehicleShader);
box2.Add(new Button() { name = "AddPoint_btn", text = "添加 hit Points" });
box2.Q<Button>("AddPoint_btn").RegisterCallback<ClickEvent>(AddHitPoints);
}
private static bool HasCombined(GameObject go)
{
Transform[] children = go.GetComponentsInChildren<Transform>();
bool hasCombined = children.Any(c => c.gameObject.name.StartsWith("Default_NLD"));
if (hasCombined) Debug.LogWarning("有合并mesh需要重命名");
return hasCombined;
}
private static bool HasHitPoints(GameObject go)
{
Transform[] children = go.GetComponentsInChildren<Transform>();
bool hasHitPoints = children.Any(c => c.gameObject.name.StartsWith("hit"));
if (hasHitPoints) Debug.LogWarning("已有hit points");
return hasHitPoints;
}
private static void RenameDuplicateBones(GameObject go)
{
var groups = go.GetComponentsInChildren<Transform>()
.GroupBy(p => p.name)
.Where(g => g.Count() > 1)
.ToArray();
if (!groups.Any()) return;
foreach (var group in groups)
{
for (int i = 0; i < group.Count(); i++)
{
group.ElementAt(i).name = group.ElementAt(i).name + "_" + (i + 1);
}
Debug.Log("重命名重复骨骼:" + group.Key);
}
}
private static void AddHitPoints(ClickEvent evt)
{
GameObject[] gameObjects = Selection.gameObjects;
foreach (GameObject go in gameObjects)
{
GameObject instance = PrefabUtility.InstantiatePrefab(go) as GameObject;
if (instance == null) return;
bool hasCombined = HasCombined(instance);
bool hasHitPoints = HasHitPoints(instance);
RenameDuplicateBones(instance);
if (hasCombined || hasHitPoints) continue;
if (instance.name.EndsWith("_b")) continue;
// 添加 hit points
var pointEffect = instance.AddComponent<CmpCharacterEffectPoints>();
List<GameObject> points = new List<GameObject>();
for (int i = 0; i < 4; i++)
{
var hit = new GameObject($"hit00{i + 1}");
hit.transform.SetParent(instance.transform);
points.Add(hit);
}
pointEffect.m_CriticalPoints = new[] { points[0].transform };
pointEffect.m_OtherPoints = new[] { points[1].transform, points[2].transform, points[3].transform };
}
}
private static void SetVehicleShader(ClickEvent evt)
{
string[] paths = { "Assets/Art/Character/Models/Battery", "Assets/Art/Character/Models/Vehicle" };
List<Object> objects = AnimHelper.FindAssetsByFolders<Object>(filter: "t:material", paths).ToList();
foreach (Object obj in objects)
{
if (obj is Material mat)
{
if (mat.shader == Shader.Find("NLD_URP/NLD_Vehicle")) continue;
mat.shader = Shader.Find("NLD_URP/NLD_Vehicle");
// mat.SetFloat("_Factor", 0.2f);
Debug.Log(mat.name);
}
}
AssetDatabase.SaveAssets();
}
private static void SimplifyMesh(GameObject instance, float vertexAmount)
{
Debug.Log(instance.transform.childCount);
for (int i = 0; i < instance.transform.childCount; i++)
{
GameObject meshGameObject = instance.transform.GetChild(i).gameObject;
SkinnedMeshRenderer skin = meshGameObject.GetComponent<SkinnedMeshRenderer>();
if (skin == null) continue;
if (skin.sharedMesh == null) break;
string dir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(skin.sharedMesh));
if (!Directory.Exists(dir)) break;
var simplify = meshGameObject.GetComponent<MeshSimplify>();
if (simplify == null) simplify = meshGameObject.AddComponent<MeshSimplify>();
simplify.m_fVertexAmount = vertexAmount;
simplify.m_bEnablePrefabUsage = true;
simplify.m_strAssetPath = Path.Combine(dir, skin.sharedMesh.name + "_simplyMesh.asset");
MeshSimplyTool.Create(simplify);
PrefabUtility.ApplyPrefabInstance(instance, InteractionMode.UserAction);
Debug.Log(Path.Combine(dir, skin.sharedMesh.name));
}
}
private static void SimplifyCharacterMesh(ClickEvent evt)
{
Object[] folders = Selection.objects;
string[] folderPaths = folders.Select(AssetDatabase.GetAssetPath).ToArray();
IEnumerable<Object> characters = AnimHelper.FindAssetsByFolders<GameObject>("character_", folderPaths);
int i = -1;
foreach (Object obj in characters)
{
if (obj is not GameObject character) continue;
GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(character);
instance.transform.localPosition = new Vector3(i, 0, 0);
i--;
if (instance.name.StartsWith("character_A")) SimplifyMesh(instance, 0.6f);
if (instance.name.StartsWith("character_E")) SimplifyMesh(instance, 0.4f);
}
}
private static void RenameAnim(ClickEvent evt)
{
var gos = Selection.gameObjects;
if (!gos.Any()) return;
foreach (var go in gos)
{
string goPath = AssetDatabase.GetAssetPath(go);
ModelImporter importer = AssetImporter.GetAtPath(goPath) as ModelImporter;
if (!importer) continue;
importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
ModelImporterClipAnimation clip = importer.defaultClipAnimations[0];
clip.name = go.name;
importer.clipAnimations = new[] { clip };
importer.SaveAndReimport();
}
AssetDatabase.Refresh();
}
// 删除 meta 文件, 设置 avatar
private static void DeleteMeta(ClickEvent evt)
{
var objs = Selection.gameObjects;
if (objs.Length == 0)
{
Debug.LogWarning("请先选择至少一个Fbx文件");
}
foreach (GameObject obj in objs)
{
string objPath = AssetDatabase.GetAssetPath(obj);
string metaPath = objPath + ".meta";
File.Delete(metaPath);
AssetDatabase.Refresh();
var modelImporter = AssetImporter.GetAtPath(objPath) as ModelImporter;
if (!modelImporter) continue;
modelImporter.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
modelImporter.SaveAndReimport();
}
AssetDatabase.Refresh();
}
private static void FindSomething(ClickEvent evt)
{
Object[] folders = Selection.objects;
string[] folderPaths = folders.Select(AssetDatabase.GetAssetPath).ToArray();
var objects = AnimHelper.FindAssetsByFolders<GameObject>("ani_", folderPaths);
foreach (var obj in objects)
{
string fbxPath = AssetDatabase.GetAssetPath(obj);
var assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(fbxPath);
var clips = assets.OfType<AnimationClip>();
var curves = clips.SelectMany(AnimationUtility.GetCurveBindings);
if (curves.Any(c => Path.GetFileName(c.path).StartsWith("Fire")))
Debug.LogWarning("f:" + obj.name);
}
}
private static void AnimOptimize(AnimationClip clip)
{
var curveBindings = AnimationUtility.GetCurveBindings(clip);
for (int ii = 0; ii < curveBindings.Length; ++ii)
{
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, curveBindings[ii]);
// 删除移动帧(除了Bip001)和缩放帧
string propName = curveBindings[ii].propertyName.ToLower();
string nodeName = Path.GetFileName(curveBindings[ii].path);
/*if (propName.Contains("position") && nodeName.StartsWith("Bip001 "))
{
curve = null; //有空格排除了“Bip001”
}*/
if (propName.Contains("scale") && !nodeName.Contains("Grip_point01"))
{
curve = null;
}
if (nodeName.StartsWith("Fire"))
{
curve = null;
}
// 压缩浮点数
if (curve is { keys: not null })
{
var keyFrames = curve.keys;
for (int i = 0; i < keyFrames.Length; i++)
{
Keyframe key = keyFrames[i];
key.value = float.Parse(key.value.ToString("f4"));
key.inTangent = float.Parse(key.inTangent.ToString("f4"));
key.outTangent = float.Parse(key.outTangent.ToString("f4"));
keyFrames[i] = key;
}
curve.keys = keyFrames;
}
// curve 为 null, 则删除曲线
AnimationUtility.SetEditorCurve(clip, curveBindings[ii], curve);
}
}
// copy fbx 中一个或多个 anim 到 new path
private static string[] CopyAnim(GameObject fbx, string destFolder, bool toCompressed = false)
{
string fbxPath = AssetDatabase.GetAssetPath(fbx);
var assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(fbxPath);
var animPaths = new List<string>();
foreach (Object asset in assets)
{
if (asset is not AnimationClip clip) continue;
AnimOptimize(clip);
AnimationClip newClip = new();
EditorUtility.CopySerialized(clip, newClip);
string str = clip.name + ".anim";
string compressed = toCompressed ? "compressed_" + str : str;
string newPath = Path.Combine(destFolder, compressed);
AssetDatabase.CreateAsset(newClip, newPath);
Debug.Log(newPath);
animPaths.Add(newPath);
}
return animPaths.ToArray();
}
public void CopyToVictory(ClickEvent evt)
{
var objs = Selection.gameObjects;
const string folder = "Assets/Art/Animations/animVictory";
if (objs.Length == 0)
{
Debug.LogWarning("请先选择至少一个Fbx文件");
}
foreach (GameObject obj in objs)
{
CopyAnim(obj, folder);
}
AssetDatabase.Refresh();
}
public void CopyToCompressedInShow(ClickEvent evt)
{
string[] guids = AssetDatabase.FindAssets("t:GameObject", new[]
{
"Assets/Art/Animations/Show/Battle",
"Assets/Art/Animations/Show/Ready",
"Assets/Art/Animations/Show/Story",
});
var paths = guids.Select(AssetDatabase.GUIDToAssetPath);
var objs = paths.Select(AssetDatabase.LoadAssetAtPath<GameObject>).ToArray();
const string folder = "Assets/Art/Animations/compressedShow";
foreach (GameObject obj in objs)
{
CopyAnim(obj, folder, true);
}
AssetDatabase.Refresh();
}
private static void CopySelectedToCompressedShow(ClickEvent evt)
{
// const string folder = "Assets/Art/Animations/compressedShow";
var objects = Selection.gameObjects;
const string tempFolder = "Assets/Art/temp/Animation";
const string destFilePath = "Assets/Art/Animations/compressedShow/{0}";
foreach (GameObject obj in objects)
{
AnimHelper.PreSetModel(obj);
string[] fileNames = CopyAnim(obj, tempFolder, true);
foreach (string f in fileNames)
{
string destFile = string.Format(destFilePath, Path.GetFileName(f));
File.Copy(f, destFile, true);
Debug.Log(destFile);
}
}
AssetDatabase.Refresh();
}
private static void CopySelectedToTemp(ClickEvent evt)
{
var objects = Selection.gameObjects;
const string tempFolder = "Assets/Art/temp/Animation";
const string destFilePath = "Assets/Art/Animations/compressed/{0}";
foreach (GameObject obj in objects)
{
AnimHelper.PreSetModel(obj);
string[] fileNames = CopyAnim(obj, tempFolder, true);
foreach (string f in fileNames)
{
string destFile = string.Format(destFilePath, Path.GetFileName(f));
File.Copy(f, destFile, true);
Debug.Log(destFile);
}
}
AssetDatabase.Refresh();
}
// rename assets
private static void RenameAssets(ClickEvent evt)
{
// 选择的文件中的 GameObject
// Object[] folders = Selection.objects;
// string[] folderPath = folders.Select(AssetDatabase.GetAssetPath).ToArray();
// var objects = AnimHelper.FindAssetsByFolders<GameObject>("t:GameObject", folderPath);
// 选择的 gameObject
var objects = Selection.gameObjects.Where(g => g.name.EndsWith("_01"));
// 选择的 文件夹
// var objects = Selection.GetFiltered<Object>(SelectionMode.Assets);
foreach (GameObject obj in objects)
{
string name = obj.name.Split("_")[2];
string newName = "3" + name[1..];
string path = AssetDatabase.GetAssetPath(obj);
AssetDatabase.RenameAsset(path, newName);
}
AssetDatabase.Refresh();
}
public void MoveFiles(ClickEvent evt)
{
var objs = Selection.objects;
string folder = $"Assets/Art_Out/Manual/VictoryAnim";
foreach (Object obj in objs)
{
string oldPath = AssetDatabase.GetAssetPath(obj);
string fileName = Path.GetFileName(oldPath);
string newPath = Path.Combine(folder, fileName);
AssetDatabase.MoveAsset(oldPath, newPath);
Debug.Log(newPath);
}
AssetDatabase.Refresh();
}
private static void Test(ClickEvent evt)
{
var personDataList = ArtTableManager.instance.tables.PersonConfig.DataList;
foreach (var i in personDataList)
{
Debug.Log(i.Name);
}
Debug.Log(personDataList.Count);
}
private static void Combine()
{
GameObject obj = Selection.activeGameObject;
AnimHelper.PreSetAll(obj);
var go = Object.Instantiate(obj);
var nameId = obj.name.Split('_')[2];
var texNames = new[] { "_MainTex", "_MaskTex" };
var parentPath = $"Assets/Art/temp/Prefab/character_{nameId}";
if (!Directory.Exists(parentPath))
{
Directory.CreateDirectory(parentPath);
}
AnimHelper.CombineSkinSaveAssets(go, texNames, parentPath);
// save prefab
string path = $"{parentPath}/character_{nameId}.prefab";
PrefabUtility.SaveAsPrefabAssetAndConnect(go, path, InteractionMode.AutomatedAction);
}
}
}