using cfg.ShowCfg; using Cysharp.Threading.Tasks; using Framework; using Gameplay.Emoji; using PhxhSDK; using Sirenix.OdinInspector; using Sirenix.Utilities; using SRF; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Experimental.Playables; using UnityEngine.Playables; using UnityEngine.Timeline; namespace Gameplay.Show { public sealed class ShowController : IShow { /// /// 当前状态 /// private enum E_CurState { None = 0, /// /// 加载角色 /// LoadingCharacters, /// /// 当前 /// Present, } private ShowComponent showComponent; /// /// 演出Timeline路径 /// private const string SHOW_TIMELINE_PATH = "Assets/Art/Show/"; /// /// 道具Timeline上角色轨道名 /// private const string ITEM_TIMELINE_TRACK_NAME = "Character"; /// /// 角色Activation轨道名 /// private const string CHARACTER_ACTIVATION_TRACK_NAME = "characterActivationTrack"; /// /// 当前场景所有角色演出点 /// private List listCharacterShowPoint = new(); /// /// 当前场景所有道具演出点 /// private List listItemShowPoint = new(); /// /// 当前场景所有演出组字典 key:组id value:演出组数据 /// private readonly Dictionary dicShowGroup = new(); /// /// 最终的演出点数据 /// private readonly List listShowPointResult = new(); /// /// Timeline资源字典 /// private readonly Dictionary dicTimeLineAsset = new(); /// /// 道具字典,部分Timeline挂载到道具上,优先使用道具上的Timeline /// private readonly Dictionary> dicItemModel = new(); /// /// 角色模型字典 /// private readonly Dictionary dicCharacterModel = new(); /// /// 道具相对于角色的相对位置 /// private readonly Dictionary dicItemOffset = new(); /// /// 缓存的表情 /// private readonly List listCacheEmoji = new(); /// /// 已经加载的资源集合 /// private readonly HashSet setAssetToUnload = new(); /// /// 当前状态 /// private E_CurState currState = E_CurState.None; #region (刷新数据,抽取)过程临时变量 /// /// 可以抽取的演出组数据(排序用) /// private readonly List listRemindShowGroup = new(); /// /// 可以抽取演出的角色数据(排序用) /// private readonly List listRemindCharacter = new(); /// /// 随机抽取的演出组链表 /// private LinkedList linkShowGroup; /// /// 随机抽取的角色链表 /// private LinkedList linkCharacter; /// /// 已经抽取的演出点索引集合 /// private readonly HashSet setUseShowPointIndex = new(); /// /// 已经抽取的角色id /// private readonly HashSet setUseCharactrtId = new(); #endregion /// /// 可以抽取的角色最大数量 /// public int CharacterMaxCount { get { return Mathf.Min(listRemindCharacter.Count, showComponent.CharacterMaxCount, listCharacterShowPoint.Count); } } public E_ShowSceneType ShowSceneType => showComponent.ShowSceneTyp; public void Destory() { dicTimeLineAsset.Clear(); dicCharacterModel.Clear(); dicItemModel.Clear(); dicItemOffset.Clear(); foreach (string path in setAssetToUnload) { AssetManager.Instance.Unload(path); } foreach (var unitEmojiManager in listCacheEmoji) { unitEmojiManager.Dispose(); } } public void SetInfo(ShowComponent showComponent) { this.showComponent = showComponent; #region 获取角色演出点 showComponent.GetCharacterShowPoint(ref listCharacterShowPoint); #endregion #region 获取道具演出点 showComponent.GetItemShowPoint(ref listItemShowPoint); #endregion List listShowConfig = TableManager.Instance.Tables.ShowConfig.DataList; foreach (DataShowConfig showConfig in listShowConfig) { if (ShowSceneType != showConfig.ShowSceneType) continue; int groupID = showConfig.GroupID; if (!dicShowGroup.TryGetValue(groupID, out ShowGroupData group)) group = new(groupID); group.Add(showConfig); dicShowGroup[groupID] = group; } foreach (ShowGroupData group in dicShowGroup.Values) { group.Shuffle(); } } public void PlayeCameraAnim(Action callback, params string[] args) { if (null == showComponent.CameraPlayable) { DebugUtil.LogError("相机Playable未赋值"); return; } showComponent.CameraAction = callback; showComponent.CameraPlayable.Play(); } public async UniTask RefreshShow(object data = null) { currState = E_CurState.LoadingCharacters; RefreshShowData(); ExtractShowCharacter(); await ExecuteShowResult(); currState = E_CurState.Present; } /// /// 刷新演出数据 /// private void RefreshShowData() { listRemindShowGroup.Clear(); listRemindCharacter.Clear(); listRemindShowGroup.AddRange(dicShowGroup.Values); #region 获取看板娘id int kanbanbanId = 0; if (showComponent.IsIgnoreKanban) { MainPanelSceneStoreData storeData = StorageMgr.Instance.GetStorage("MainPanelSceneStoreData"); if (storeData == null) DebugUtil.LogError("没有看板娘数据"); else kanbanbanId = storeData.PosterID; } #endregion foreach (CharacterDataInfo characterDataInfo in CharacterDataInfoManager.Instance.DataList) { if (showComponent.IsIgnoreKanban && characterDataInfo.Cfg.RoleID == kanbanbanId) continue; // TODO 看板娘不加进可以演出的抽取列表 listRemindCharacter.Add(characterDataInfo); } CommonUtils.Shuffle(listRemindShowGroup); CommonUtils.Shuffle(listRemindCharacter); linkShowGroup = new(listRemindShowGroup); linkCharacter = new(listRemindCharacter); #region 隐藏之前的角色模型 foreach (GameObject go in dicCharacterModel.Values) { go.SetActive(false); } #endregion } /// /// 抽取演出角色 /// private void ExtractShowCharacter() { listShowPointResult.Clear(); List listTempResult = new(); // 临时存放抽取的结果 if (dicShowGroup.TryGetValue(30, out ShowGroupData value1)) { linkShowGroup.Remove(value1); CheckShowGroup(ref listTempResult, value1, false); } if (dicShowGroup.TryGetValue(31, out ShowGroupData value31)) { linkShowGroup.Remove(value31); CheckShowGroup(ref listTempResult, value31, false); } if (dicShowGroup.TryGetValue(32, out ShowGroupData value32)) { linkShowGroup.Remove(value32); CheckShowGroup(ref listTempResult, value32, false); } if (dicShowGroup.TryGetValue(33, out ShowGroupData value33)) { linkShowGroup.Remove(value33); CheckShowGroup(ref listTempResult, value33, false); } if (dicShowGroup.TryGetValue(34, out ShowGroupData value34)) { linkShowGroup.Remove(value34); CheckShowGroup(ref listTempResult, value34, false); } if (dicShowGroup.TryGetValue(35, out ShowGroupData value35)) { linkShowGroup.Remove(value31); CheckShowGroup(ref listTempResult, value35, false); } while (linkShowGroup.First != null) { ShowGroupData groupData = linkShowGroup.First.Value; linkShowGroup.RemoveFirst(); CheckShowGroup(ref listTempResult, groupData); } setUseShowPointIndex.Clear(); setUseCharactrtId.Clear(); } private void CheckShowGroup(ref List listTempResult, ShowGroupData groupData, bool isCheck = true) { if (isCheck) { // 如果组里的演出点和当前已经使用的演出点重复,则踢出该组 if (IsShowPointExist(groupData)) return; // 如果是固定角色的组的角色已经被使用,则踢出该组 if (!IsCanUseFixCharacter(groupData)) return; } listTempResult.Clear(); ExtractRecord extractRecord = new(groupData.GroupID); foreach (DataShowConfig dataShowConfig in groupData.ListConfig) { // 如果当前没有剩余的演员,则直接跳出抽取 if (linkCharacter.First == null) break; #region 抽取timeline string timelineName = CommonUtils.GetRandomItem(dataShowConfig.TimelineNames); ExtractRecord.SinglePointRecord singlePointRecord = new() { timelineName = timelineName }; extractRecord.Add(singlePointRecord); if (timelineName == "0" || timelineName.IsNullOrWhitespace()) // 说明不站人 continue; #endregion #region 角色抽取 CharacterDataInfo characterDataInfo = null; if (ValueValidator.IsIdValid(dataShowConfig.CharacterID)) // 抽取指定演员 { foreach (CharacterDataInfo character in linkCharacter) { if ((int)character.ID == dataShowConfig.CharacterID) { characterDataInfo = character; break; } } if (characterDataInfo == null) continue; else linkCharacter.Remove(characterDataInfo); } else { characterDataInfo = linkCharacter.First.Value; linkCharacter.RemoveFirst(); } #endregion ShowPointResult result; // 道具抽取 if (dataShowConfig.ItemNames.Count() > 0) { string itemName = CommonUtils.GetRandomItem(dataShowConfig.ItemNames); result = new(groupData.GroupID, dataShowConfig.PointID, timelineName, characterDataInfo, dataShowConfig.ItemPointId, itemName); } else { result = new(groupData.GroupID, dataShowConfig.PointID, timelineName, characterDataInfo, dataShowConfig.ItemPointId, string.Empty); } singlePointRecord.showPoint = dataShowConfig.PointID; singlePointRecord.characterID = characterDataInfo.ID; listTempResult.Add(result); } if (!groupData.IsMustExist && groupData.IsMustHasCharacter && listTempResult.Count < groupData.ListConfig.Count) { // 如果该组必须满员但没满员 // 取出来的CharacterDataInfo归还 foreach (ShowPointResult result in listTempResult) { listRemindCharacter.Add(result.CharacterData); } } else // 添加到最后的result列表中 { foreach (ShowPointResult result in listTempResult) { listShowPointResult.Add(result); setUseCharactrtId.Add((int)result.CharacterData.ID); setUseShowPointIndex.Add(result.ShowPointIndex); } } } /// /// 执行演出结果 /// private async UniTask ExecuteShowResult() { if (listShowPointResult.Count == 0) return; Dictionary> dicTempGoItem = new(); // 临时道具字典 foreach (ShowPointResult result in listShowPointResult) { #region 获取角色演出点 int showPointIndex = result.ShowPointIndex; if (showPointIndex < 0 || showPointIndex >= listCharacterShowPoint.Count || listCharacterShowPoint[showPointIndex] == null) { DebugUtil.LogError($"找不到演出点!演出点索引:{showPointIndex}"); continue; } Transform tsShowPoint = listCharacterShowPoint[showPointIndex].transform; #endregion int itemShowPointIndex = result.ItemPointIndex; if (itemShowPointIndex >= 0 && itemShowPointIndex < listItemShowPoint.Count && listItemShowPoint[itemShowPointIndex] != null) { Transform tsItemShowPoint = listItemShowPoint[itemShowPointIndex].transform; // 获取道具演出点,有道具演出 GameObject goFixItemModel = await GetItemModel(result.ItemName); // 加载固定道具 if (goFixItemModel != null) goFixItemModel.transform.SetPositionAndRotation(tsItemShowPoint.position, tsItemShowPoint.rotation); } GameObject goCharacterModel = await GetCharacterModel(result.CharacterData); if (goCharacterModel == null) continue; goCharacterModel.SetActive(true); goCharacterModel.transform.SetPositionAndRotation(tsShowPoint.position, tsShowPoint.rotation); GameObject goItemModel = await GetItemModel(result.TimelineName);// 优先使用道具上的Timeline if (goItemModel == null) // 没有道具,直接加载Timeline { TimelineAsset timelineAsset = await GetTimeLineAsset(result.TimelineName); if (timelineAsset == null) continue; PlayableDirector playableDirector = goCharacterModel.GetComponentOrAdd(); playableDirector.playableAsset = timelineAsset; playableDirector.extrapolationMode = DirectorWrapMode.Loop; foreach (PlayableBinding playableBinding in timelineAsset.outputs) { playableDirector.SetGenericBinding(playableBinding.sourceObject, goCharacterModel.GetComponentInChildren()); } foreach (TrackAsset trackAsset in timelineAsset.GetOutputTracks()) { if (trackAsset.name.Equals(CHARACTER_ACTIVATION_TRACK_NAME)) playableDirector.SetGenericBinding(trackAsset, goCharacterModel.transform.GetChild(0)?.gameObject); } playableDirector.enabled = true; playableDirector.time = UnityEngine.Random.Range(0f, (float)playableDirector.duration); playableDirector.Play(); } else // 有道具,不加载Timeline { #region 临时记录使用过的道具GameObject,方便后续归还给池子 if (!dicTempGoItem.TryGetValue(result.TimelineName, out Stack goStack)) { goStack = new(); dicTempGoItem[result.TimelineName] = goStack; } goStack.Push(goItemModel); #endregion PlayableDirector playableDirector = goItemModel.GetComponentOrAdd(); TimelineAsset timelineAsset = playableDirector.playableAsset as TimelineAsset; if (timelineAsset == null) { DebugUtil.LogError($"{goItemModel.name} 没有TimelineAsset"); continue; } if (dicItemOffset.TryGetValue(goItemModel.GetInstanceID(), out Vector3 offset)) goItemModel.transform.SetPositionAndRotation(goCharacterModel.transform.position + offset, Quaternion.identity); else goItemModel.transform.SetPositionAndRotation(goCharacterModel.transform.position, Quaternion.identity); goItemModel.transform.RotateAround(goCharacterModel.transform.position, Vector3.up, goCharacterModel.transform.localEulerAngles.y); PlayableDirector cPlayableDirector = goCharacterModel.GetComponentOrAdd(); cPlayableDirector.enabled = false; foreach (TrackAsset trackAsset in timelineAsset.GetOutputTracks()) { if (trackAsset.name.Equals(ITEM_TIMELINE_TRACK_NAME)) playableDirector.SetGenericBinding(trackAsset, goCharacterModel.GetComponentInChildren()); if (trackAsset.name.Equals(CHARACTER_ACTIVATION_TRACK_NAME)) { Transform tsGo = goCharacterModel.transform.GetChild(0); if (null == tsGo) DebugUtil.LogError("赋值空的角色到Timeline轨道上"); else playableDirector.SetGenericBinding(trackAsset, tsGo.gameObject); } } playableDirector.time = UnityEngine.Random.Range(0f, (float)playableDirector.duration); playableDirector.Play(); } } #region 放回道具 foreach (var kv in dicTempGoItem) { if (!dicItemModel.TryGetValue(kv.Key, out Stack stack)) continue; stack ??= new(); while (kv.Value.Count > 0) { stack.Push(kv.Value.Pop()); } } dicTempGoItem.Clear(); #endregion } /// /// 初始化emoji /// /// /// /// private UnitEmojiManager InitEmoji(GameObject cObj, CharacterDataInfo dataInfo) { var config = dataInfo.Cfg; var skinID = dataInfo.SkinID > 0 ? (int)dataInfo.SkinID : Framework.Constants.DEFULT_SKINID; var skinData = TableManager.Instance.Tables.SkinCfg.Get(skinID, dataInfo.Cfg.RoleID); if (skinData == null) { DebugUtil.LogError($"找不到皮肤数据, skinID:{skinID}, roleID:{dataInfo.Cfg.RoleID}"); return null; } var eyebowsType = string.IsNullOrEmpty(config.EmojiEyeBowsType) ? "1" : config.EmojiEyeBowsType; var eyesColor = string.IsNullOrEmpty(config.EmojiEyesColor) ? "#ff0000" : config.EmojiEyesColor; var eyebowsColor = string.IsNullOrEmpty(config.EmojiEyeBowsColor) ? "#ff0000" : config.EmojiEyeBowsColor; var unitEmojiManager = new UnitEmojiManager(cObj, eyesColor, eyebowsType, eyebowsColor); unitEmojiManager.SwitchEmoji(config.DefaultDisplayEmojiId); return unitEmojiManager; } /// /// 异步加载资源 /// /// /// /// private async UniTask LoadAssetAsync(string path) where T : UnityEngine.Object { T res = await AssetManager.Instance.LoadAssetAsync(path); if (res != null) setAssetToUnload.Add(path); return res; } /// /// 获取角色模型 /// /// /// private async UniTask GetCharacterModel(CharacterDataInfo characterDataInfo) { if (!dicCharacterModel.TryGetValue(characterDataInfo.Cfg.RoleID, out GameObject goModel)) { string prefabPath = CommonUtils.GetFightCharacterModelPath(characterDataInfo); goModel = await LoadAssetAsync(prefabPath); if (goModel == null) { DebugUtil.LogError($"找不到模型, path:{prefabPath}"); return null; } goModel = GameObject.Instantiate(goModel, showComponent.TsShowPointRoot); goModel.SetLayerEx(Framework.Constants.Layer.UIVIEW); HideCharacterWeapon(goModel); dicCharacterModel.Add(characterDataInfo.Cfg.RoleID, goModel); } return goModel; } /// /// 获取道具模型 /// /// /// private async UniTask GetItemModel(string name) { if (!dicItemModel.TryGetValue(name, out Stack stackItem) || stackItem.Count <= 0) { string itemPath = $"{SHOW_TIMELINE_PATH}{ShowSceneType}/{name}.prefab"; if (!AssetManager.Instance.CanLocateAsset(itemPath)) return null; stackItem ??= new(); GameObject goItem = await LoadAssetAsync(itemPath); if (goItem == null) return null; goItem = GameObject.Instantiate(goItem, showComponent.TsItemShowPointRoot); goItem.SetLayerEx(Framework.Constants.Layer.UIVIEW); dicItemModel[name] = stackItem; dicItemOffset[goItem.GetInstanceID()] = goItem.transform.position; return goItem; } return stackItem.Pop(); } /// /// 隐藏角色武器 /// private void HideCharacterWeapon(GameObject goCharacter) { if (goCharacter.transform.childCount < 1) { DebugUtil.LogError("角色节点错误:" + goCharacter.name); return; } // TODO 临时隐藏武器方式 Transform tsRoot = goCharacter.transform.GetChild(0).Find("Root"); if (null == tsRoot) return; Transform tsWeaponRoot = tsRoot.Find("Bip001/Bip001 Spine/Bip001 Spine1/Bip001 R Clavicle/Bip001 R UpperArm/Bip001 R Forearm/Bip001 R Hand/Grip_point01"); if (null == tsWeaponRoot) return; tsWeaponRoot.gameObject.IsScaleShow(false); for (int i = 0; i < tsWeaponRoot.childCount; ++i) { tsWeaponRoot.GetChild(i).gameObject.IsScaleShow(false); } } /// /// 获取Timeline资源 /// /// /// private async UniTask GetTimeLineAsset(string timelineName) { if (!dicTimeLineAsset.TryGetValue(timelineName, out TimelineAsset timelineAsset)) { string timelinePath = $"{SHOW_TIMELINE_PATH}{ShowSceneType}/{timelineName}.playable"; if (string.IsNullOrWhiteSpace(timelinePath)) { DebugUtil.LogError($"找不到timelinePath,{nameof(timelineName)}:{timelineName}"); return null; } timelineAsset = await LoadAssetAsync(timelinePath); if (timelineAsset == null) { DebugUtil.LogError($"找不到TimelineAsset, {nameof(timelineName)}:{timelineName} path:{timelinePath}"); return null; } dicTimeLineAsset.Add(timelineName, timelineAsset); } return timelineAsset; } /// /// 演出点是否重复 /// /// /// private bool IsShowPointExist(ShowGroupData group) { foreach (DataShowConfig showConfig in group.ListConfig) { if (setUseShowPointIndex.Contains(showConfig.PointID)) return true; } return false; } /// /// 是否可以使用限定角色 /// /// /// private bool IsCanUseFixCharacter(ShowGroupData group) { foreach (DataShowConfig showConfig in group.ListConfig) { if (!ValueValidator.IsIdValid(showConfig.CharacterID) && !IsCanUseCharacter(showConfig.CharacterID)) return false; } return true; } /// /// 角色是否可用 /// /// /// private bool IsCanUseCharacter(int characterId) { return !setUseCharactrtId.Contains(characterId); } } /// /// 抽取流程的记录 /// [Serializable] public class ExtractRecord { /// /// 单个演出点记录 /// [Serializable] public struct SinglePointRecord { [LabelText("演出点ID")] public int showPoint; public string timelineName; public uint characterID; } [LabelText("组ID")] private int groupID; [LabelText("角色记录")] private List listRecord; public ExtractRecord(int groupID) { this.groupID = groupID; listRecord = new(); } public void Add(SinglePointRecord singlePointRecord) { listRecord.Add(singlePointRecord); } } /// /// 演出组数据 /// public struct ShowGroupData { /// /// 演出组id /// private readonly int groupId; /// /// 是否必须有角色 /// private bool isMustHasCharacter; /// /// 是否必须存在 /// private bool isMustExist; /// /// 该组演出点配置数据 /// private readonly List listConfig; /// /// 演出组id /// public readonly int GroupID { get { return groupId; } } /// /// 是否必须有角色 /// public readonly bool IsMustHasCharacter { get { return isMustHasCharacter; } } /// /// 是否必须存在 /// public readonly bool IsMustExist { get { return isMustExist; } } /// /// 该组演出点配置数据 /// public readonly List ListConfig { get { return listConfig; } } public ShowGroupData(int groupId) { this.groupId = groupId; isMustHasCharacter = false; isMustExist = false; listConfig = new(); } public void Add(DataShowConfig dataShowConfig) { isMustHasCharacter |= dataShowConfig.HasCharacter; isMustExist |= dataShowConfig.IsMustExist; listConfig.Add(dataShowConfig); } /// /// 打乱顺序 /// public readonly void Shuffle() { CommonUtils.Shuffle(listConfig); } } /// /// 演出点结果 /// public readonly struct ShowPointResult { /// /// 演出组id /// private readonly int groupID; /// /// 演出点索引 /// private readonly int showPointIndex; /// /// 道具演出点索引 /// private readonly int itemPointIndex; /// /// Timeline名 /// private readonly string timelineName; /// /// 道具名 /// private readonly string itemName; /// /// 角色数据 /// private readonly CharacterDataInfo characterData; #region 属性 public int GroupID { get { return groupID; } } public int ShowPointIndex { get { return showPointIndex; } } public string TimelineName { get { return timelineName; } } public int ItemPointIndex { get { return itemPointIndex; } } public string ItemName { get { return itemName; } } public CharacterDataInfo CharacterData { get { return characterData; } } #endregion public ShowPointResult(int groupID, int showPointIndex, string timelineName, CharacterDataInfo characterData, int itemPointIndex, string itemName) { this.groupID = groupID; this.showPointIndex = showPointIndex; this.timelineName = timelineName; this.characterData = characterData; this.itemPointIndex = itemPointIndex; this.itemName = itemName; } } }