using System.Collections.Generic; using Framework; using Gameplay.Effect; using PhxhSDK; using UnityEngine; using Constants = Framework.Constants; namespace Gameplay.Emoji { public class UnitEmojiManager { // public readonly Character.Character character; public GameObject rootObj; private Material _matBg; private Material _matUp; private Material _matMid; private Material _matDown; private string _oldImgPathBg; private string _oldImgPathUp; private string _oldImgPathMid; private string _oldImgPathDown; private List _emojiEffectIds; private List _effectDatas; private Color _eyeColor; private Color _eyeBowColor; private string _bowType; private static readonly int MainColor = Shader.PropertyToID("_MainColor"); public readonly bool isInFight; private int _inFightEmojiId; private static readonly int MaskTex = Shader.PropertyToID("_MaskTex"); private Skeleton _skeleton; private Texture _cacheMaskTex; private static readonly int MainTex = Shader.PropertyToID("_MainTex"); private static readonly int ShadowScale = Shader.PropertyToID("_shadowScale"); private static readonly int FaceTex = Shader.PropertyToID("_FaceTex"); private static readonly int SkinColor = Shader.PropertyToID("_SkinColor"); private static readonly int EyeColor = Shader.PropertyToID("_EyeColor"); private static readonly int EyeBrowColor = Shader.PropertyToID("_EyeBrowColor"); internal UnitEmojiManager(GameObject obj, string eyeColor, string bowType, string eyebowColor, int inFightEmojiId) { isInFight = inFightEmojiId >= 0; _inFightEmojiId = inFightEmojiId; // Character_1 rootObj = obj; _eyeColor = ColorUtility.TryParseHtmlString(eyeColor, out var color) ? color : Color.white; _eyeBowColor = ColorUtility.TryParseHtmlString(eyebowColor, out var color2) ? color2 : Color.white; _bowType = bowType; var collector = new ComponentCollector(obj); _skeleton = new Skeleton(collector.Transforms); if (isInFight) { // 战斗模式 // _InitFightEmoji(); } else { // 剧情模式 _BindObj(); } } public void Dispose() { if (_cacheMaskTex != null) { Object.Destroy(_cacheMaskTex); _cacheMaskTex = null; } } private async void _InitFightEmoji() { var emojiObj = _skeleton.GetBone("F_emoji"); if (emojiObj != null) { // 删除 Object.Destroy(emojiObj.gameObject); } emojiObj = _skeleton.GetBone("G_emoji"); if (emojiObj != null) { // 删除 Object.Destroy(emojiObj.gameObject); } if (_inFightEmojiId == 0) { return; } // 找到头部 var playerObj = rootObj.transform.GetChild(0).GetChild(0); var headObj = playerObj.GetChild(1); if (!headObj.name.EndsWith("_face")) { DebugUtil.LogError("找不到头部模型,取消创建默认表情:" + playerObj.name); return; } var skinMeshRender = headObj.GetComponent(); if (skinMeshRender == null) { DebugUtil.LogError("找不到头部模型,取消创建默认表情:" + playerObj.name); return; } var oldMat = skinMeshRender.sharedMaterial; if (oldMat.shader.name != "NLD_URP/NLD_Charactor" && oldMat.shader.name != "NLD_URP/NLD_Charactor_NoOutline") { DebugUtil.LogError("头部模型使用的默认材质不对,取消创建默认表情:" + playerObj.name); return; } var newMatSample = await AssetManager.Instance.LoadAssetAsync(Constants.EMOJI_HEAD_MAT); if (newMatSample == null) { DebugUtil.LogError("找不到表情的材质球"); return; } var newMat = Object.Instantiate(newMatSample); var oldFaceTex = oldMat.GetTexture(MainTex); // 从贴图读取颜色,读取中心的颜色 var faceColor = _GetColorFromTex(oldFaceTex); var shadowScale = oldMat.GetFloat(ShadowScale); var oldMaskTex = oldMat.GetTexture(MaskTex); var defaultFaceId = _inFightEmojiId; // Assets/Art/Character/Models/Emoji/Tex/T_emoji_lian_01.png var combieFaceTexPath = $"Assets/Art/Character/Models/Emoji/Tex/T_emoji_lian_{_GetFixNum(defaultFaceId.ToString())}.png"; var combieFaceTex = await AssetManager.Instance.LoadAssetAsync(combieFaceTexPath); // Assets/Art/Character/Models/Emoji/Tex/T_emoji_lian_01_mask.png var combieFaceMaskTexPath = $"Assets/Art/Character/Models/Emoji/Tex/T_emoji_lian_{_GetFixNum(defaultFaceId.ToString())}_mask.png"; var combieFaceMaskTex = await AssetManager.Instance.LoadAssetAsync(combieFaceMaskTexPath); if (combieFaceTex == null || combieFaceMaskTex == null) { DebugUtil.LogError("找不到表情的贴图"); return; } // 将oldMaskTex和combieFaceMaskTex合并 var combieMaskTex = _CombieMaskTex(oldMaskTex, combieFaceMaskTex); _cacheMaskTex = combieMaskTex; // 设置材质 // _FaceTex ("FaceTexture", 2D) = "white" {} // _MaskTex ("Mask", 2D) = "white" {} // _SkinColor ("Skin Color", color) = (1,1,1,1) // _EyeColor ("Eye Color", color) = (1,1,1,1) // _EyeBrowColor ("EyeBrow Color", color) = (1,1,1,1) // _shadowScale("ShadowScale", range(0, 1.0)) = 0.5 newMat.SetTexture(FaceTex, combieFaceTex); newMat.SetTexture(MaskTex, combieMaskTex); newMat.SetColor(SkinColor, faceColor); newMat.SetColor(EyeColor, _eyeColor); newMat.SetColor(EyeBrowColor, _eyeBowColor); newMat.SetFloat(ShadowScale, shadowScale); skinMeshRender.material = newMat; } private Texture _CombieMaskTex(Texture rMask, Texture gbMask) { // 将rMask的r和gMask的gb合并 var rMask2D = rMask as Texture2D; var gMask2D = gbMask as Texture2D; if (rMask2D == null || gMask2D == null) { DebugUtil.LogError("表情的贴图不是Texture2D"); return null; } var width = rMask2D.width; var height = rMask2D.height; var pixels = rMask2D.GetPixels(); var pixels2 = gMask2D.GetPixels(); var newPixels = new Color[pixels.Length]; for (var i = 0; i < pixels.Length; ++i) { var r = pixels[i].r; var g = pixels2[i].g; var b = pixels2[i].b; newPixels[i] = new Color(r, g, b, 0); } var newTex = new Texture2D(width, height); newTex.SetPixels(newPixels); newTex.Apply(); return newTex; } private Color _GetColorFromTex(Texture tex) { var tex2D = tex as Texture2D; if (tex2D == null) { DebugUtil.LogError("表情的贴图不是Texture2D"); return Color.white; } var width = tex2D.width; var height = tex2D.height; return tex2D.GetPixel(width / 2, height / 2); } private void _BindObj() { var emojiObj = _skeleton.GetBone("F_emoji"); if (emojiObj != null) { // 删除 Object.Destroy(emojiObj.gameObject); } var newEmojiObj = _skeleton.GetBone("G_emoji"); if (newEmojiObj == null) { DebugUtil.LogError("找不到表情的骨骼:G_emoji"); return; } rootObj = newEmojiObj.gameObject; var meshRender = rootObj.GetComponentInChildren(); // 这里直接去Inst的 var mats = meshRender.sharedMaterials; if (mats.Length != 4) { DebugUtil.LogError("表情的材质球数量不对"); return; } _matUp = Object.Instantiate(mats[0]); _matMid = Object.Instantiate(mats[1]); _matDown = Object.Instantiate(mats[2]); _matBg = Object.Instantiate(mats[3]); meshRender.materials = new[] { _matUp, _matMid, _matDown, _matBg }; _emojiEffectIds = new List(); _effectDatas = new List(); } public void Show() { if (rootObj == null) { DebugUtil.LogError("表情的根节点为空"); return; } if (rootObj.activeSelf) return; rootObj.SetActive(true); } public void Hide() { if (!rootObj.activeSelf) return; rootObj.SetActive(false); } private bool _CheckIsEmpty(string imgName) { if (string.IsNullOrEmpty(imgName)) return true; if (imgName == "0") return true; if (!int.TryParse(imgName, out var result)) { return true; } if (result == 0) { return true; } return false; } private string _GetFixNum(string imgName) { if (imgName.Length < 2) { return "0" + imgName; } return imgName; } public async void SwitchEmoji(int emojiId) { if (_matUp == null) return; if (emojiId == 0) return; #if UNITY_EDITOR var emojiCfg = EditorTableManager.instance.tables.EmojiConfig.Get(emojiId); #else var emojiCfg = TableManager.Instance.Tables.EmojiConfig.Get(emojiId); #endif if (emojiCfg == null) { DebugUtil.LogError("无效的emojiId:{0}", emojiId); return; } // 眉毛 T_emoji_eyebows_1_01 // 眼睛 T_emoji_eyes_01 // 嘴巴 T_emoji_mouth_01 // 背景 T_emoji_acc_01 const string emptyPath = "T_emoji_empty"; const string prefix = "Assets/Art/Character/Models/Emoji/Tex/"; var imgPathBg = _CheckIsEmpty(emojiCfg.BgImg) ? emptyPath : $"T_emoji_acc_{_GetFixNum(emojiCfg.BgImg)}"; var imgPathUp = _CheckIsEmpty(emojiCfg.UpImg) ? emptyPath : $"T_emoji_eyebows_{_bowType}_{_GetFixNum(emojiCfg.UpImg)}"; var imgPathUpMask = _CheckIsEmpty(emojiCfg.UpImg) ? emptyPath : $"T_emoji_eyebows_{_bowType}_{_GetFixNum(emojiCfg.UpImg)}_mask"; var imgPathMid = _CheckIsEmpty(emojiCfg.MidImg) ? emptyPath : $"T_emoji_eyes_{_GetFixNum(emojiCfg.MidImg)}"; var imgPathMidMask = _CheckIsEmpty(emojiCfg.MidImg) ? emptyPath : $"T_emoji_eyes_{_GetFixNum(emojiCfg.MidImg)}_mask"; var imgPathDown = _CheckIsEmpty(emojiCfg.DownImg) ? emptyPath : $"T_emoji_mouth_{_GetFixNum(emojiCfg.DownImg)}"; imgPathBg = prefix + imgPathBg + ".png"; imgPathUp = prefix + imgPathUp + ".png"; imgPathMid = prefix + imgPathMid + ".png"; imgPathDown = prefix + imgPathDown + ".png"; imgPathMidMask = prefix + imgPathMidMask + ".png"; imgPathUpMask = prefix + imgPathUpMask + ".png"; if (_oldImgPathBg != imgPathBg) { _oldImgPathBg = imgPathBg; _matBg.mainTexture = await AssetManager.Instance.LoadAssetAsync(imgPathBg); } if (_oldImgPathUp != imgPathUp) { _oldImgPathUp = imgPathUp; var isWithMask = await AssetManager.Instance.CheckAssetInPack(imgPathUpMask); if (isWithMask) { var maskTex = await AssetManager.Instance.LoadAssetAsync(imgPathUpMask); _matUp.SetTexture(MaskTex, maskTex); } else { _matUp.SetTexture(MaskTex, null); } _matUp.mainTexture = await AssetManager.Instance.LoadAssetAsync(imgPathUp); _matUp.SetColor(MainColor, _eyeBowColor); } if (_oldImgPathMid != imgPathMid) { _oldImgPathMid = imgPathMid; var isWithMask = await AssetManager.Instance.CheckAssetInPack(imgPathMidMask); if (isWithMask) { var maskTex = await AssetManager.Instance.LoadAssetAsync(imgPathMidMask); _matMid.SetTexture(MaskTex, maskTex); } else { _matMid.SetTexture(MaskTex, null); } _matMid.mainTexture = await AssetManager.Instance.LoadAssetAsync(imgPathMid); _matMid.SetColor(MainColor, _eyeColor); } if (_oldImgPathDown != imgPathDown) { _oldImgPathDown = imgPathDown; _matDown.mainTexture = await AssetManager.Instance.LoadAssetAsync(imgPathDown); } // 特效的判定 var effectIds = emojiCfg.EffectIds; var isSame = _CheckSame(effectIds, _emojiEffectIds); if (!isSame) { // 销毁旧的 for (var i = 0; i < _effectDatas.Count; ++i) { var effectData = _effectDatas[i]; effectData.Destroy(); } _effectDatas.Clear(); _emojiEffectIds = effectIds; if (Application.isPlaying) { // 获取特效 for (var i = 0; i < _emojiEffectIds.Count; ++i) { var effectId = _emojiEffectIds[i]; var effectData = EffectManager.instance.PlayEffectById(effectId, rootObj.transform.position, rootObj.transform.rotation, rootObj.transform); _effectDatas.Add(effectData); } } } Show(); } private static bool _CheckSame(IReadOnlyList a, IReadOnlyList b) { if (a.Count != b.Count) return false; for (var i = 0; i < a.Count; ++i) { if (a[i] != b[i]) return false; } return true; } } }