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

345 lines
14 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 Art.temp.Editor.PrefabTool;
using artcfg.ArtDesign;
using Framework;
using Sirenix.Utilities;
using UnityEditor;
using UnityEngine;
namespace Art.temp.Editor.AnimationTools
{
public static class AnimHelper
{
public static GameObject FindDisplayPrefab(string nameId)
{
switch (nameId[0])
{
case 'A':
{
var player = CharacterFactory.CreatePlayer(nameId);
return player.DisplayPrefab;
}
case 'E':
{
var enemy = CharacterFactory.CreateEmeny(nameId);
return enemy.DisplayPrefab;
}
case 'N':
{
var npc = CharacterFactory.CreateNpc(nameId);
return npc.DisplayPrefab;
}
default:
return null;
}
}
// filter = "name t:xx l:xx" Name Labels Types
public static IEnumerable<T> FindAssetsByFolders<T>(string filter, string[] folders) where T : Object
{
// FindAssets 能找子文件夹
string[] guids = AssetDatabase.FindAssets(filter, folders);
IEnumerable<string> paths = guids.Select(AssetDatabase.GUIDToAssetPath);
return paths.Select(AssetDatabase.LoadAssetAtPath<T>);
}
/// <summary>
/// 合并 prefab 中的 mesh, 要求只有一个材质,并且合并后只有一个 material
/// </summary>
/// <param name="go">cloned prefab</param>
/// <param name="texNames">shader 贴图字段 [_MainTex, _MaskTex]</param>
private static void CombineSkinnedMesh(GameObject go, string[] texNames)
{
var parts = go.GetComponentsInChildren<SkinnedMeshRenderer>(true);
if (parts.Length < 2) return;
// 获取蒙皮骨骼
var bones = parts.SelectMany(p => p.bones).ToArray();
// 合并贴图
var texGroups = texNames.Select(t => parts.Select(p => p.sharedMaterial.GetTexture(t) as Texture2D))
.ToArray();
var combineTextures = texGroups.Select(g =>
{
var tex = new Texture2D(4, 4, TextureFormat.ARGB4444, true);
tex.PackTextures(g.ToArray(), 0, 2048);
return tex;
});
// 合并 mesh uv
var uvGroups = parts.Select(p => p.sharedMesh.uv).ToArray();
var rects =
new Texture2D(4, 4, TextureFormat.ARGB4444, true).PackTextures(texGroups.ElementAt(0).ToArray(), 0,
2048);
var newUvs = uvGroups.Zip(rects, (uv, rect) =>
{
for (int i = 0; i < uv.Length; i++)
{
uv[i].x = Mathf.Lerp(rect.xMin, rect.xMax, uv[i].x);
uv[i].y = Mathf.Lerp(rect.yMin, rect.yMax, uv[i].y);
}
return uv;
}).SelectMany(uv => uv).ToArray();
// copy and combine mesh
var combineInstances = parts.Select(p => new CombineInstance() { mesh = DeepCopyMesh(p.sharedMesh) });
Mesh newMesh = new();
newMesh.CombineMeshes(combineInstances.ToArray(), true, false);
// set texture
var mat = Object.Instantiate(parts[0].sharedMaterial);
var list = texNames.Zip(combineTextures, (name, tex) =>
{
mat.SetTexture(name, tex);
return 0;
}).ToArray();
// create combine
GameObject combinedMesh = new GameObject("combined_mesh");
combinedMesh.transform.SetParent(go.transform.GetChild(0));
var smr = combinedMesh.AddComponent<SkinnedMeshRenderer>();
smr.sharedMesh = newMesh;
smr.sharedMesh.uv = newUvs.ToArray();
smr.bones = bones;
smr.rootBone = go.transform.GetChild(0).Find("Root");
smr.sharedMaterial = mat;
parts.ForEach(p => Object.DestroyImmediate(p.gameObject));
}
public static void CombineSkinSaveAssets(GameObject go, string[] texNames, string parentPath)
{
string nameId = go.transform.GetChild(0).name;
AnimHelper.CombineSkinnedMesh(go, texNames);
var smr = go.transform.GetChild(0).GetComponentInChildren<SkinnedMeshRenderer>();
var mat = smr.sharedMaterial;
// texture
foreach (var name in texNames)
{
var tex = mat.GetTexture(name);
var texPath = $"{parentPath}/{name}.png";
var newTex = SaveAndReloadTexture2D(tex as Texture2D, texPath);
mat.SetTexture(name, newTex);
}
// material
var matSavePath = $"{parentPath}/mat_{nameId}.mat";
AssetDatabase.CreateAsset(mat, matSavePath);
// mesh
var mesh = smr.sharedMesh;
var meshSavePath = $"{parentPath}/mesh_{nameId}.asset";
AssetDatabase.CreateAsset(mesh, meshSavePath);
}
private static Texture2D SaveAndReloadTexture2D(Texture2D t2d, string savePath)
{
var bytes = t2d.EncodeToPNG();
File.WriteAllBytes(savePath, bytes);
AssetDatabase.Refresh();
var newT2d = AssetDatabase.LoadAssetAtPath<Texture2D>(savePath);
return newT2d;
}
public static string[] PreSetAll(GameObject prefab)
{
var allSkin = prefab.GetComponentsInChildren<SkinnedMeshRenderer>();
string[] allTexNames = new string[] { "_MainTex", "_MaskTex" };
foreach (SkinnedMeshRenderer skin in allSkin)
{
var material = skin.sharedMaterial;
if (material.shader.name != "NLD_URP/NLD_Charactor")
{
// 修改为NLD_URP/NLD_Charactor
material.shader = Shader.Find("NLD_URP/NLD_Charactor");
}
// 判断所有的贴图是否已开启read/write
foreach (var texName in allTexNames)
{
var tex = material.GetTexture(texName);
if (!tex) continue;
var texPath = AssetDatabase.GetAssetPath(tex);
var importer = AssetImporter.GetAtPath(texPath) as TextureImporter;
if (!importer) continue;
importer.isReadable = true;
importer.SaveAndReimport();
}
}
return allTexNames;
}
private static Mesh DeepCopyMesh(Mesh originalMesh)
{
Mesh copiedMesh = new Mesh
{
vertices = (Vector3[])originalMesh.vertices.Clone(),
triangles = (int[])originalMesh.triangles.Clone(),
uv = (Vector2[])originalMesh.uv.Clone(),
normals = (Vector3[])originalMesh.normals.Clone(),
tangents = (Vector4[])originalMesh.tangents.Clone(),
colors = (Color[])originalMesh.colors.Clone(),
boneWeights = (BoneWeight[])originalMesh.boneWeights.Clone(),
bindposes = (Matrix4x4[])originalMesh.bindposes.Clone()
};
return copiedMesh;
}
public static void PreSetModel(GameObject model)
{
var objPath = AssetDatabase.GetAssetPath(model);
var modelImporter = AssetImporter.GetAtPath(objPath) as ModelImporter;
if (!modelImporter) return;
modelImporter.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
AssetDatabase.Refresh();
modelImporter.SaveAndReimport();
}
public static void SetFaceMaterial(GameObject model)
{
const string browTexPath = "Assets/Art_Out/Manual/Emoji/Tex/T_emoji_eyebows_{0}_01.png";
const string browMaskPath = "Assets/Art_Out/Manual/Emoji/Tex/T_emoji_eyebows_{0}_01_mask.png";
const string eyeTexPath = "Assets/Art_Out/Manual/Emoji/Tex/T_emoji_eyes_0{0}.png";
const string eyeMaskPath = "Assets/Art_Out/Manual/Emoji/Tex/T_emoji_eyes_0{0}_mask.png";
const string mouthPath = "Assets/Art_Out/Manual/Emoji/Tex/T_emoji_mouth_0{0}.png";
string nameId = model.name;
DataPerson dataPerson = ArtTableManager.instance.tables.PersonConfig.DataList.FirstOrDefault(p => p.NameId == nameId);
if (dataPerson == null)
{
Debug.LogWarning($"{nameId} not found face config");
return;
}
GameObject emoji = model.transform
.Find("Root/Bip001/Bip001 Spine/Bip001 Spine1/Bip001 Neck/Bip001 Head/G_emoji").gameObject;
Material[] mats = emoji.GetComponent<MeshRenderer>().sharedMaterials;
if (!string.IsNullOrEmpty(dataPerson.BowsColor))
{
ColorUtility.TryParseHtmlString(dataPerson.BowsColor, out Color browColor);
mats[0].SetColor("_MainColor", browColor);
}
if (!string.IsNullOrEmpty(dataPerson.EyesColor))
{
ColorUtility.TryParseHtmlString(dataPerson.EyesColor, out Color eyeColor);
mats[1].SetColor("_MainColor", eyeColor);
}
Texture2D browTex = AssetDatabase.LoadAssetAtPath<Texture2D>(string.Format(browTexPath, dataPerson.BowsType));
Texture2D browMask = AssetDatabase.LoadAssetAtPath<Texture2D>(string.Format(browMaskPath, dataPerson.BowsType));
mats[0].SetTexture("_MainTex", browTex);
mats[0].SetTexture("_MaskTex", browMask);
Texture2D eyeTex = AssetDatabase.LoadAssetAtPath<Texture2D>(string.Format(eyeTexPath, dataPerson.EyesType));
Texture2D eyeMask = AssetDatabase.LoadAssetAtPath<Texture2D>(string.Format(eyeMaskPath, dataPerson.EyesType));
mats[1].SetTexture("_MainTex", eyeTex);
mats[1].SetTexture("_MaskTex", eyeMask);
Texture2D mouthTex = AssetDatabase.LoadAssetAtPath<Texture2D>(string.Format(mouthPath, dataPerson.MouseType));
mats[2].SetTexture("_MainTex", mouthTex);
}
public static void RenameAnim(GameObject go)
{
string goPath = AssetDatabase.GetAssetPath(go);
ModelImporter importer = AssetImporter.GetAtPath(goPath) as ModelImporter;
importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
ModelImporterClipAnimation clip = importer.defaultClipAnimations[0];
clip.name = go.name;
if (clip.name.EndsWith("idle") || clip.name.EndsWith("move")) clip.loopTime = true;
importer.clipAnimations = new[] { clip };
importer.SaveAndReimport();
}
public static void CopyAnimToCompressed(GameObject obj)
{
const string tempFolder = "Assets/Art/temp/Animation";
const string destFilePath = "Assets/Art/Animations/compressed/{0}";
string[] fileNames = CopyAnim(obj, tempFolder, true);
foreach (string f in fileNames)
{
string animFile = string.Format(destFilePath, Path.GetFileName(f));
File.Copy(f, animFile, true);
Debug.Log(animFile);
}
AssetDatabase.Refresh();
}
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);
animPaths.Add(newPath);
}
return animPaths.ToArray();
}
public 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);
}
}
}
}