using System.Collections.Generic; using System.IO; using cfg.WeaponCfg; using Gameplay.Character; using Gameplay.Utils; using PhxhSDK; using PhxhSDK.Phxh.AutoCombine; using SkillEditor; using UnityEditor; using UnityEditor.Animations; using UnityEngine; namespace CharacterBuilder { public abstract class BaseRoleBuildData { public List weaponConfigList = new List(); public string weaponIdsStr { get { var result = ""; for (int j = 0; j < weaponConfigList.Count; j++) { var weaponConfig = weaponConfigList[j]; result += weaponConfig.ID; if (j < weaponConfigList.Count - 1) { result += ", "; } } return result; } } public bool noSearchSkill = false; public EmojiExtInfo emojiExt; public string errorInfo; public bool isChecked; protected List _keepAnims = new List() { "ani_common_crawl_idle001", "ani_common_crawl_moveforward001", "ani_common_crawl_raise001", "ani_common_dead001", "ani_common_dizziness001", "ani_common_retreat001", "ani_common_stand_moveback001", "ani_common_stand_moveforward001", "ani_common_stand_moveleft001", "ani_common_stand_moveright001", "ani_common_move001", "ani_common_ready001", "ani_common_walk001", "ani_common_surrender001", "ani_common_crouchwalk001", "ani_common_build001_attack", "ani_common_build001_idle", "ani_common_build001_reload", "ani_common_build002_attack", "ani_common_build002_idle", "ani_common_build002_reload", "ani_common_build003_attack", "ani_common_build003_idle", "ani_common_build003_reload", "ani_common_build004_attack", "ani_common_build004_idle", "ani_common_build004_reload", "ani_common_build005_attack", "ani_common_build005_idle", "ani_common_build005_reload" }; protected string _GetNewSkillAnimName(string idName) { return $"ani_{idName}_skill01"; } protected string _GetNewAnimName(string oldAnimName, string idName, string weaponModName) { var splits = oldAnimName.Split('_'); if (splits.Length != 2) { Debug.LogError("动画名字不符合规范:" + oldAnimName); return ""; } var action1 = splits[0]; var action2 = splits[1]; return $"ani_{weaponModName}_{action1}_{action2}"; } protected string _CheckAnims(Dictionary dict, string nameId, string defaultSkinNameId) { /*** ani_common_dizziness001 ani_common_retreat001 ani_common_dead001 保持原样 ani_g01_stand_ready001 使用 ani_g04_stand_idle001 skill_01 使用 ani_a00005_skill01 其他使用 ani_g05_stand_idle001 */ var loadAnimator = AssetDatabase.LoadAssetAtPath("Assets/Art/Animations/animator/animator_pure_n3.controller"); var allClips = loadAnimator.animationClips; for (int i = 0; i < allClips.Length; i++) { var oldClip = allClips[i]; var oldClipName = oldClip.name; if (_keepAnims.Contains(oldClipName)) { continue; } if (oldClipName == "skill01") { if (noSearchSkill) { continue; } var newClipName = _GetNewSkillAnimName(nameId); if (!dict.ContainsKey(newClipName)) { newClipName = _GetNewSkillAnimName(defaultSkinNameId); if (!dict.ContainsKey(newClipName)) { return $"动画{oldClipName}找不到对应的{newClipName}"; } } continue; } for (int j = 0; j < weaponConfigList.Count; j++) { var weaponConfig = weaponConfigList[j]; var newClipName = _GetNewAnimName(oldClipName, nameId, weaponConfig.MotionGroupName); if (!dict.ContainsKey(newClipName)) { return $"动画{oldClipName}找不到对应的{newClipName}"; } } } return null; } private AnimatorOverrideController _AutoBuildAnimator(Dictionary clipDict, string nameId, DataWeapon weaponConfig, string defaultSkinNameId) { // 创建overrideController var overrideController = new AnimatorOverrideController(); // 建立动画关系 var loadAnimator = AssetDatabase.LoadAssetAtPath("Assets/Art/Animations/animator/animator_pure_n3.controller"); overrideController.runtimeAnimatorController = loadAnimator; var oList = new List>(); var allClips = loadAnimator.animationClips; var reCacheDict = new Dictionary(); for (int i = 0; i < allClips.Length; i++) { var oldClip = allClips[i]; var oldClipName = oldClip.name; // 保持不变的 if (_keepAnims.Contains(oldClipName)) { var pairOld = new KeyValuePair(oldClip, oldClip); oList.Add(pairOld); reCacheDict.TryAdd(oldClipName, oldClip); continue; } if (oldClipName == "skill01") { if (noSearchSkill) { var pairOld = new KeyValuePair(oldClip, oldClip); oList.Add(pairOld); reCacheDict.TryAdd(oldClipName, oldClip); continue; } var newClipName = _GetNewSkillAnimName(nameId); if (!clipDict.TryGetValue(newClipName, out var newClip)) { newClipName = _GetNewSkillAnimName(defaultSkinNameId); if (!clipDict.TryGetValue(newClipName, out newClip)) { DebugUtil.LogError($"找不到动画{newClipName}"); var pairOld = new KeyValuePair(oldClip, oldClip); oList.Add(pairOld); reCacheDict.TryAdd(oldClipName, oldClip); continue; } } var pair = new KeyValuePair(oldClip, newClip); oList.Add(pair); reCacheDict.TryAdd(oldClipName, newClip); } else { // 需要替换的 var newClipName = _GetNewAnimName(oldClipName, nameId, weaponConfig.MotionGroupName); if (clipDict.TryGetValue(newClipName, out var newClip)) { var pair = new KeyValuePair(oldClip, newClip); oList.Add(pair); reCacheDict.TryAdd(oldClipName, newClip); } else { // 找不到的 DebugUtil.LogError($"找不到动画{newClipName}"); var pairOld = new KeyValuePair(oldClip, oldClip); oList.Add(pairOld); reCacheDict.TryAdd(oldClipName, oldClip); } } } overrideController.ApplyOverrides(oList); _CompressOverrideController(overrideController); // 导出动画数据 AnimatorInfoExporter.ExportAnimatorInfo(loadAnimator, reCacheDict, nameId, weaponConfig.ID); return overrideController; } private void _CompressOverrideController(AnimatorOverrideController overrideController) { var overrideClips = new List>(); overrideController.GetOverrides(overrideClips); var newClips = new List>(); foreach (var oldKV in overrideClips) { var srcClip = oldKV.Key; var dstClip = oldKV.Value; if (dstClip == null) { dstClip = srcClip; } var newClip = LTCompressor.AutoCompress(dstClip); newClips.Add(new KeyValuePair(srcClip, newClip)); } overrideController.ApplyOverrides(newClips); } private bool _CombineEmoji(GameObject prefab, int forceSize) { var emojiObj = _DeadSearchTrans(prefab.transform, "F_emoji"); if (emojiObj != null) { Object.DestroyImmediate(emojiObj.gameObject); } emojiObj = _DeadSearchTrans(prefab.transform, "G_emoji"); if (emojiObj != null) { Object.DestroyImmediate(emojiObj.gameObject); } if (emojiExt.emojiId == 0) { DebugUtil.LogError("没有表情id,取消创建默认表情:" + prefab.name); return false; } // 找到头部 var playerObj = prefab.transform.GetChild(0); var headObj = playerObj.GetChild(1); if (!headObj.name.EndsWith("_face")) { DebugUtil.LogError("找不到头部模型,取消创建默认表情:" + playerObj.name); return false; } var skinMeshRender = headObj.GetComponent(); if (skinMeshRender == null) { DebugUtil.LogError("找不到头部模型skninmeshrenderer,取消创建默认表情:" + playerObj.name); return false; } var oldMat = skinMeshRender.sharedMaterial; var mainTex = oldMat.GetTexture("_MainTex") as Texture2D; var newMat = new Material(oldMat); mainTex = _DrawFace(mainTex, forceSize); newMat.SetTexture("_MainTex", mainTex); // 设置材质 skinMeshRender.sharedMaterial = newMat; return true; } private Texture2D _DrawFace(Texture2D t2d, int forceSize) { // Assets/Art/Character/Models/Emoji/Tex/T_emoji_lian_01.png // Assets/Art/Character/Models/Emoji/Tex/T_emoji_lian_01_mask.png var numStr = emojiExt.emojiId.ToString("D2"); var texturePath = $"Assets/Art/Character/Models/Emoji/Tex/T_emoji_lian_{numStr}.png"; var emojiTex = AssetDatabase.LoadAssetAtPath(texturePath); if (emojiTex == null) { DebugUtil.LogError("找不到表情贴图:" + texturePath); return t2d; } var maskTexPath = texturePath.Replace(".png", "_mask.png"); var maskTex = AssetDatabase.LoadAssetAtPath(maskTexPath); // 画到主贴图上 var newTex = _DrawEmojiToMainTex(t2d, emojiTex, maskTex, forceSize); return newTex; } private Texture2D _DrawEmojiToMainTex(Texture2D mainTex, Texture2D emojiTex, Texture2D maskTex, int forceSize) { var eyebowsColor = ColorUtils.FromHex(emojiExt.eyebowsColor); if (string.IsNullOrEmpty(emojiExt.eyebowsColor)) { eyebowsColor = ColorUtils.Random(); } var eyesColor = ColorUtils.FromHex(emojiExt.eyesColor); if (string.IsNullOrEmpty(emojiExt.eyesColor)) { eyesColor = ColorUtils.Random(); } var shader = Shader.Find("Unlit/DrawFace"); var material = new Material(shader); material.SetTexture("_MainTex", mainTex); material.SetTexture("_FaceTex", emojiTex); material.SetTexture("_FaceMask", maskTex); material.SetColor("_EyebowsColor", eyebowsColor); material.SetColor("_EyeColor", eyesColor); // 创建一个新的贴图 var newTex = new Texture2D(forceSize, forceSize, TextureFormat.RGBA32, false); // 画到新的贴图上 var rt = RenderTexture.GetTemporary(forceSize, forceSize, 0, RenderTextureFormat.ARGB32); Graphics.Blit(mainTex, rt, material); RenderTexture.active = rt; newTex.ReadPixels(new Rect(0, 0, forceSize, forceSize), 0, 0); newTex.Apply(); RenderTexture.active = null; RenderTexture.ReleaseTemporary(rt); return newTex; } private Transform _DeadSearchTrans(Transform rootTrans, string findName) { var childCount = rootTrans.childCount; for (int i = 0; i < childCount; i++) { var child = rootTrans.GetChild(i); if (child.name == findName) { return child; } var result = _DeadSearchTrans(child, findName); if (result != null) { return result; } } return null; } private void _SetHitEffectPoints(GameObject animatorObj) { var oldCmp = animatorObj.GetComponent(); if (oldCmp == null) { oldCmp = animatorObj.AddComponent(); } oldCmp._AutoSet(); } private GameObject _CreateTempPrefab(string srcPrefabPath, string nameId, DataWeapon weaponConfig) { var loadPrefab = AssetDatabase.LoadAssetAtPath(srcPrefabPath); if (loadPrefab == null) { Debug.LogError($"{nameId}找不到原始prefab:{srcPrefabPath},取消导出"); return null; } // 加载新的武器模型 var weaponModelPath = $"Assets/Art/Character/Prefabs/Guns/{weaponConfig.ModelPath}.prefab"; var weaponModel = AssetDatabase.LoadAssetAtPath(weaponModelPath); if (weaponModel == null) { Debug.LogError($"{nameId}找不到武器模型:{weaponModelPath},取消导出"); return null; } var instPrefab = Object.Instantiate(loadPrefab); // 找到武器挂点 var weaponParentPoint = instPrefab.transform.SearchChild("Grip_point01"); if (weaponParentPoint == null) { Debug.LogError($"{srcPrefabPath}找不到武器挂点,取消导出"); return null; } // 清空节点 weaponParentPoint.ClearChilds(); // 创建武器到挂点上 var weaponInst = Object.Instantiate(weaponModel, weaponParentPoint); // 设置武器的位置 weaponInst.transform.ResetLocals(); // 获取原始路径父文件夹 var parentPath = Path.GetDirectoryName(srcPrefabPath); var tempPath = $"{parentPath}/P_{nameId}_{weaponConfig.ID}.prefab"; var tempPrefab = PrefabUtility.SaveAsPrefabAsset(instPrefab, tempPath); // 删除临时对象 Object.DestroyImmediate(instPrefab); return tempPrefab; } private bool _RebuildAnimator(Dictionary clipDict, GameObject loadPrefab, string nameId, DataWeapon weaponConfig, string defaultSkinNameId) { // 找到animator var animator = loadPrefab.GetComponentInChildren(true); if (animator == null) { Debug.LogError($"{nameId}原始prefab没有animator,取消导出"); return false; } animator.enabled = true; var overrideController = _AutoBuildAnimator(clipDict, nameId, weaponConfig, defaultSkinNameId); // Assets/Art_Out/AutoGen/Characters/character_{id} var parentPath = $"Assets/Art_Out/AutoGen/Characters/character_{nameId}_{weaponConfig.ID}"; if (Directory.Exists(parentPath)) { Directory.Delete(parentPath, true); } Directory.CreateDirectory(parentPath); // 保存overrideController // {parentPath}/character_{id}_{weapon}.overrideController var savePath = $"{parentPath}/ov_{nameId}_{weaponConfig.MotionGroupName}.overrideController"; AssetDatabase.CreateAsset(overrideController, savePath); _SetHitEffectPoints(animator.gameObject); // 原来的prefab也需要保存 // 设置overrideController到animator上 animator.runtimeAnimatorController = overrideController; animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; EditorUtility.SetDirty(loadPrefab); AssetDatabase.SaveAssets(); return true; } private string[] _PreSetAll(GameObject prefab) { var allSkin = prefab.GetComponentsInChildren(); 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 != null) { var texPath = AssetDatabase.GetAssetPath(tex); var importer = AssetImporter.GetAtPath(texPath) as TextureImporter; if (importer.isReadable == false) { importer.isReadable = true; importer.SaveAndReimport(); } } } } return allTexNames; } public static int FORCE_TEXTURE_SIZE = 512; private string _CombineSkins(GameObject loadPrefab, string nameId, DataWeapon weaponConfig) { var parentPath = $"Assets/Art_Out/AutoGen/Characters/character_{nameId}_{weaponConfig.ID}"; var instPrefab = Object.Instantiate(loadPrefab); var allTexNames = _PreSetAll(instPrefab); AssetDatabase.Refresh(); // 将表情画到头上 var faceImgSize = FORCE_TEXTURE_SIZE / 2; var result = _CombineEmoji(instPrefab, faceImgSize); if (!result) { Debug.LogError("合并表情失败,该角色进入战场后将没有表情"); } AutoCombine.ReSetAllTextureSize(instPrefab, allTexNames, faceImgSize); AutoCombine.CombineSkinMesh(instPrefab, allTexNames, FORCE_TEXTURE_SIZE); // 合并完了将中间生成的素材进行保存,这里合并了之后理论上只有一个 var skinMeshRenderer = instPrefab.GetComponentInChildren(); var material = skinMeshRenderer.sharedMaterial; // textures foreach (var texName in allTexNames) { var tex = material.GetTexture(texName); var texSavePath = $"{parentPath}/INFIGHT_{texName}.png"; var newTex = _SaveAndReloadTexture2D(tex as Texture2D, texSavePath); material.SetTexture(texName, newTex); } // material var matSavePath = $"{parentPath}/mat_{nameId}.mat"; AssetDatabase.CreateAsset(material, matSavePath); // mesh var mesh = skinMeshRenderer.sharedMesh; var meshSavePath = $"{parentPath}/mesh_{nameId}.asset"; AssetDatabase.CreateAsset(mesh, meshSavePath); // 保存新的prefab // {parentPath}/character_{id}_{weapon}.prefab var prefabSavePath = $"{parentPath}/character_{nameId}.prefab"; PrefabUtility.SaveAsPrefabAsset(instPrefab, prefabSavePath); // 删除临时对象 Object.DestroyImmediate(instPrefab); return parentPath; } private Texture2D _SaveAndReloadTexture2D(Texture2D t2d, string savePath) { var bytes = t2d.EncodeToPNG(); File.WriteAllBytes(savePath, bytes); AssetDatabase.Refresh(); var newT2d = AssetDatabase.LoadAssetAtPath(savePath); return newT2d; } protected void _Rebuild(Dictionary clipDict, string prefabPath, string nameId, string defaultSkinNameId) { for (int i = 0; i < weaponConfigList.Count; i++) { var weaponConfig = weaponConfigList[i]; var tempPrefab = _CreateTempPrefab(prefabPath, nameId, weaponConfig); if (tempPrefab == null) { continue; } var result = _RebuildAnimator(clipDict, tempPrefab, nameId, weaponConfig, defaultSkinNameId); if (!result) { continue; } _CombineSkins(tempPrefab, nameId, weaponConfig); } // 打印日志 Debug.Log($"重建{prefabPath}成功"); } public abstract string CheckAnims(Dictionary dict); public abstract void RebuildAll(Dictionary clipDict); } }