716 lines
20 KiB
C#
716 lines
20 KiB
C#
using cfg.ShowCfg;
|
|
using Cysharp.Threading.Tasks;
|
|
using Framework;
|
|
using Gameplay;
|
|
using PhxhSDK;
|
|
using Sirenix.OdinInspector;
|
|
using SRF;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Playables;
|
|
using UnityEngine.Timeline;
|
|
using static ShowManager;
|
|
using Object = UnityEngine.Object;
|
|
|
|
/// <summary>
|
|
/// 演出管理器
|
|
/// </summary>
|
|
public sealed class ShowManager : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// 当前状态
|
|
/// </summary>
|
|
private enum E_CurState
|
|
{
|
|
None = 0,
|
|
/// <summary>
|
|
/// 加载角色
|
|
/// </summary>
|
|
LoadingCharacters,
|
|
/// <summary>
|
|
/// 当前
|
|
/// </summary>
|
|
Present,
|
|
}
|
|
|
|
/// <summary>
|
|
/// 被弃用的原因
|
|
/// </summary>
|
|
public enum E_DisposeReason
|
|
{
|
|
None,
|
|
|
|
/// <summary>
|
|
/// 演出点重复
|
|
/// </summary>
|
|
[LabelText("演出点重复")]
|
|
ShowPointRepeat,
|
|
|
|
/// <summary>
|
|
/// 组内人数不满
|
|
/// </summary>
|
|
[LabelText("组内人数不满")]
|
|
CharacterCountNotFull,
|
|
|
|
/// <summary>
|
|
/// 固定角色不存在
|
|
/// </summary>
|
|
[LabelText("固定角色不存在")]
|
|
FixedCharacterNotExist,
|
|
}
|
|
|
|
#region 外部序列化字段
|
|
/// <summary>
|
|
/// 演出场景类型
|
|
/// </summary>
|
|
[LabelText("演出场景类型")]
|
|
[SerializeField]
|
|
private E_ShowSceneType ShowSceneType;
|
|
|
|
/// <summary>
|
|
/// 角色根节点
|
|
/// </summary>
|
|
[LabelText("角色根节点")]
|
|
[SerializeField]
|
|
private Transform tsCharacterRoot;
|
|
|
|
/// <summary>
|
|
/// 演出点根节点
|
|
/// </summary>
|
|
[LabelText("演出点根节点")]
|
|
[SerializeField]
|
|
private Transform tsShowPointRoot;
|
|
|
|
/// <summary>
|
|
/// 最大抽取角色数量
|
|
/// </summary>
|
|
[LabelText("最大抽取角色数")]
|
|
[SerializeField]
|
|
private int characterMaxCount = 20;
|
|
|
|
/// <summary>
|
|
/// 是否忽略看板娘
|
|
/// </summary>
|
|
[LabelText("是否忽略看板娘")]
|
|
[SerializeField]
|
|
private bool IsIgnoreKanbanban;
|
|
|
|
/// <summary>
|
|
/// 抽取记录
|
|
/// </summary>
|
|
[ReadOnly]
|
|
[LabelText("抽取记录")]
|
|
[SerializeField]
|
|
[ShowIf("@listExtractRecord.Count > 0")]
|
|
private List<ExtractRecord> listExtractRecord = new();
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// 当前场景所有演出点
|
|
/// </summary>
|
|
private List<Transform> listShowPoint;
|
|
/// <summary>
|
|
/// 当前场景所有演出组字典 key:组id value:演出组数据
|
|
/// </summary>
|
|
private readonly Dictionary<int, ShowGroupData> dicShowGroup = new();
|
|
|
|
/// <summary>
|
|
/// 最终的演出点数据
|
|
/// </summary>
|
|
protected readonly List<ShowPointResult> listShowPointResult = new();
|
|
/// <summary>
|
|
/// Timeline资源字典
|
|
/// </summary>
|
|
private readonly Dictionary<int, TimelineAsset> dicTimeLineAsset = new();
|
|
/// <summary>
|
|
/// 已经加载的资源集合
|
|
/// </summary>
|
|
private readonly HashSet<string> setAssetToUnload = new();
|
|
/// <summary>
|
|
/// 角色模型字典
|
|
/// </summary>
|
|
private readonly Dictionary<CharacterDataInfo, GameObject> dicCharacterModel = new();
|
|
/// <summary>
|
|
/// 当前状态
|
|
/// </summary>
|
|
private E_CurState currState = E_CurState.None;
|
|
|
|
#region (刷新数据,抽取)过程临时变量
|
|
/// <summary>
|
|
/// 可以抽取的演出组数据(排序用)
|
|
/// </summary>
|
|
private List<ShowGroupData> listRemindShowGroup = new();
|
|
/// <summary>
|
|
/// 可以抽取演出的角色数据(排序用)
|
|
/// </summary>
|
|
protected List<CharacterDataInfo> listRemindCharacter = new();
|
|
/// <summary>
|
|
/// 随机抽取的演出组链表
|
|
/// </summary>
|
|
private LinkedList<ShowGroupData> linkShowGroup;
|
|
/// <summary>
|
|
/// 随机抽取的角色链表
|
|
/// </summary>
|
|
private LinkedList<CharacterDataInfo> linkCharacter;
|
|
/// <summary>
|
|
/// 已经抽取的演出点索引集合
|
|
/// </summary>
|
|
private readonly HashSet<int> setUseShowPointIndex = new();
|
|
/// <summary>
|
|
/// 已经抽取的角色id
|
|
/// </summary>
|
|
private readonly HashSet<int> setUseCharactrtId = new();
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// 可以抽取的角色最大数量
|
|
/// </summary>
|
|
public int CharacterMaxCount
|
|
{
|
|
get { return Mathf.Min(listRemindCharacter.Count, characterMaxCount, listShowPoint.Count); }
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// 演出点根节点
|
|
/// </summary>
|
|
public Transform TsShowPointRoot
|
|
{
|
|
get { return tsShowPointRoot; }
|
|
}
|
|
#endif
|
|
|
|
private void Awake()
|
|
{
|
|
if (!CommonUtils.IsInGame())
|
|
return;
|
|
|
|
currState = E_CurState.None;
|
|
|
|
#region 初始化数据
|
|
|
|
#region 获取演出点
|
|
if (tsShowPointRoot == null)
|
|
listShowPoint = new();
|
|
else
|
|
{
|
|
listShowPoint = new(tsShowPointRoot.childCount);
|
|
for (int i = 0; i < tsShowPointRoot.childCount; ++i)
|
|
{
|
|
listShowPoint.Add(tsShowPointRoot.GetChild(i));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 获取演出组配置
|
|
List<DataShowConfig> 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);
|
|
dicShowGroup.Add(groupID, group);
|
|
}
|
|
|
|
group.Add(showConfig);
|
|
}
|
|
|
|
foreach (ShowGroupData group in dicShowGroup.Values)
|
|
{
|
|
group.Shuffle();
|
|
}
|
|
#endregion
|
|
|
|
#endregion
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
foreach (string path in setAssetToUnload)
|
|
{
|
|
AssetManager.Instance.Unload(path);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 刷新演出
|
|
/// </summary>
|
|
public async UniTask RefreshShow()
|
|
{
|
|
currState = E_CurState.LoadingCharacters;
|
|
|
|
RefreshShowData();
|
|
ExtractShowCharacter();
|
|
await ExecuteShowResult();
|
|
|
|
currState = E_CurState.Present;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 刷新演出数据
|
|
/// </summary>
|
|
private void RefreshShowData()
|
|
{
|
|
listRemindShowGroup.Clear();
|
|
listRemindCharacter.Clear();
|
|
|
|
listRemindShowGroup.AddRange(dicShowGroup.Values);
|
|
|
|
foreach (CharacterDataInfo characterDataInfo in CharacterDataInfoManager.Instance.DataList)
|
|
{
|
|
if (IsIgnoreKanbanban)
|
|
{
|
|
// TODO 看板娘不加进可以演出的抽取列表
|
|
}
|
|
listRemindCharacter.Add(characterDataInfo);
|
|
}
|
|
|
|
CommonUtils.Shuffle(listRemindShowGroup);
|
|
CommonUtils.Shuffle(listRemindCharacter);
|
|
|
|
linkShowGroup = new(listRemindShowGroup);
|
|
linkCharacter = new(listRemindCharacter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 抽取演出角色
|
|
/// </summary>
|
|
private void ExtractShowCharacter()
|
|
{
|
|
listExtractRecord.Clear();
|
|
listShowPointResult.Clear();
|
|
|
|
List<ShowPointResult> listTempResult = new(); // 临时存放抽取的结果
|
|
|
|
while (linkShowGroup.First != null)
|
|
{
|
|
ShowGroupData groupData = linkShowGroup.First.Value;
|
|
linkShowGroup.RemoveFirst();
|
|
|
|
// 如果组里的演出点和当前已经使用的演出点重复,则踢出该组
|
|
if (IsShowPointExist(groupData))
|
|
{
|
|
listExtractRecord.Add(new ExtractRecord(groupData.GroupID, E_DisposeReason.ShowPointRepeat));
|
|
continue;
|
|
}
|
|
|
|
// 如果是固定角色的组的角色已经被使用,则踢出该组
|
|
if (!IsCanUseFixCharacter(groupData))
|
|
{
|
|
listExtractRecord.Add(new ExtractRecord(groupData.GroupID, E_DisposeReason.FixedCharacterNotExist));
|
|
continue;
|
|
}
|
|
|
|
listTempResult.Clear();
|
|
ExtractRecord extractRecord = new(groupData.GroupID, E_DisposeReason.None);
|
|
foreach (DataShowConfig dataShowConfig in groupData.ListConfig)
|
|
{
|
|
// 如果当前没有剩余的演员,则直接跳出抽取
|
|
if (linkCharacter.First == null)
|
|
break;
|
|
|
|
// 抽取timeline
|
|
int timelineID = CommonUtils.GetRandomItem(dataShowConfig.TimelineIDs);
|
|
ExtractRecord.SinglePointRecord singlePointRecord = new()
|
|
{
|
|
timelineID = timelineID
|
|
};
|
|
extractRecord.Add(singlePointRecord);
|
|
|
|
if (timelineID == 0) // 说明不站人
|
|
continue;
|
|
|
|
#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 = new(groupData.GroupID, dataShowConfig.PointID, timelineID, characterDataInfo);
|
|
|
|
singlePointRecord.showPoint = dataShowConfig.PointID;
|
|
singlePointRecord.characterID = characterDataInfo.ID;
|
|
listTempResult.Add(result);
|
|
}
|
|
|
|
if (groupData.IsMustHasCharacter && listTempResult.Count < groupData.ListConfig.Count)
|
|
{
|
|
// 如果该组必须满员但没满员
|
|
// 取出来的CharacterDataInfo归还
|
|
foreach (ShowPointResult result in listTempResult)
|
|
{
|
|
listRemindCharacter.Add(result.CharacterData);
|
|
}
|
|
|
|
listExtractRecord.Add(new ExtractRecord(groupData.GroupID, E_DisposeReason.CharacterCountNotFull));
|
|
}
|
|
else // 添加到最后的result列表中
|
|
{
|
|
foreach (ShowPointResult result in listTempResult)
|
|
{
|
|
listShowPointResult.Add(result);
|
|
setUseCharactrtId.Add((int)result.CharacterData.ID);
|
|
setUseShowPointIndex.Add(result.ShowPointIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
setUseShowPointIndex.Clear();
|
|
setUseCharactrtId.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行演出结果
|
|
/// </summary>
|
|
private async UniTask ExecuteShowResult()
|
|
{
|
|
if (listShowPointResult.Count == 0)
|
|
return;
|
|
|
|
foreach (ShowPointResult result in listShowPointResult)
|
|
{
|
|
int showPointIndex = result.ShowPointIndex;
|
|
if (showPointIndex < 0 || showPointIndex >= listShowPoint.Count || listShowPoint[showPointIndex] == null)
|
|
{
|
|
DebugUtil.LogError($"找不到演出点!演出点索引:{showPointIndex}");
|
|
continue;
|
|
}
|
|
|
|
Transform tsShowPoint = listShowPoint[showPointIndex].transform;
|
|
|
|
GameObject goModel = await GetModel(result.CharacterData);
|
|
if (goModel == null)
|
|
continue;
|
|
|
|
goModel.transform.SetPositionAndRotation(tsShowPoint.position, tsShowPoint.rotation);
|
|
|
|
TimelineAsset timelineAsset = await GetTimeLineAsset(result.TimelineID);
|
|
if (timelineAsset == null)
|
|
continue;
|
|
|
|
if (!goModel.TryGetComponent(out PlayableDirector director))
|
|
{
|
|
DebugUtil.LogError($"{goModel.name}未绑定PlayableDirector组件");
|
|
continue;
|
|
}
|
|
|
|
director.playableAsset = timelineAsset;
|
|
director.extrapolationMode = DirectorWrapMode.Loop;
|
|
foreach (PlayableBinding playableBinding in timelineAsset.outputs)
|
|
{
|
|
director.SetGenericBinding(playableBinding.sourceObject, goModel.GetComponentInChildren<Animator>());
|
|
}
|
|
|
|
director.Play();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 异步加载资源
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="path"></param>
|
|
/// <returns></returns>
|
|
private async UniTask<T> LoadAssetAsync<T>(string path) where T : Object
|
|
{
|
|
T res = await AssetManager.Instance.LoadAssetAsync<T>(path);
|
|
if (res != null)
|
|
setAssetToUnload.Add(path);
|
|
|
|
return res;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取角色模型
|
|
/// </summary>
|
|
/// <param name="characterDataInfo"></param>
|
|
/// <returns></returns>
|
|
private async UniTask<GameObject> GetModel(CharacterDataInfo characterDataInfo)
|
|
{
|
|
if (!dicCharacterModel.TryGetValue(characterDataInfo, out GameObject goModel))
|
|
{
|
|
string prefabPath = characterDataInfo.PrefabPath;
|
|
goModel = await LoadAssetAsync<GameObject>(prefabPath);
|
|
if (goModel == null)
|
|
{
|
|
DebugUtil.LogError($"找不到模型, path:{prefabPath}");
|
|
return null;
|
|
}
|
|
|
|
goModel = Instantiate(goModel, tsCharacterRoot);
|
|
goModel.GetComponentOrAdd<PlayableDirector>();
|
|
goModel.SetLayerEx(Framework.Constants.Layer.UIVIEW);
|
|
dicCharacterModel.Add(characterDataInfo, goModel);
|
|
}
|
|
|
|
return goModel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取Timeline资源
|
|
/// </summary>
|
|
/// <param name="timelineID"></param>
|
|
/// <returns></returns>
|
|
private async UniTask<TimelineAsset> GetTimeLineAsset(int timelineID)
|
|
{
|
|
if (!dicTimeLineAsset.TryGetValue(timelineID, out TimelineAsset timelineAsset))
|
|
{
|
|
string timelinePath = CampTimeLineCollector.Instance[ShowSceneType, timelineID];
|
|
if (string.IsNullOrWhiteSpace(timelinePath))
|
|
{
|
|
DebugUtil.LogError($"找不到timelinePath,id:{timelineID}");
|
|
return null;
|
|
}
|
|
|
|
timelineAsset = await LoadAssetAsync<TimelineAsset>(timelinePath);
|
|
if (timelineAsset == null)
|
|
{
|
|
DebugUtil.LogError($"找不到TimelineAsset, timelineID:{timelineID} path:{timelinePath}");
|
|
return null;
|
|
}
|
|
|
|
dicTimeLineAsset.Add(timelineID, timelineAsset);
|
|
}
|
|
|
|
return timelineAsset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 演出点是否重复
|
|
/// </summary>
|
|
/// <param name="group"></param>
|
|
/// <returns></returns>
|
|
private bool IsShowPointExist(ShowGroupData group)
|
|
{
|
|
foreach (DataShowConfig showConfig in group.ListConfig)
|
|
{
|
|
if (setUseShowPointIndex.Contains(showConfig.PointID))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 是否可以使用限定角色
|
|
/// </summary>
|
|
/// <param name="group"></param>
|
|
/// <returns></returns>
|
|
private bool IsCanUseFixCharacter(ShowGroupData group)
|
|
{
|
|
foreach(DataShowConfig showConfig in group.ListConfig)
|
|
{
|
|
if (!ValueValidator.IsIdValid(showConfig.CharacterID) && !IsCanUseCharacter(showConfig.CharacterID))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 角色是否可用
|
|
/// </summary>
|
|
/// <param name="characterId"></param>
|
|
/// <returns></returns>
|
|
private bool IsCanUseCharacter(int characterId)
|
|
{
|
|
return !setUseCharactrtId.Contains(characterId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取进度
|
|
/// </summary>
|
|
/// <returns>0f-1f</returns>
|
|
public float GetRefreshProcess()
|
|
{
|
|
return currState switch
|
|
{
|
|
E_CurState.None => 0f,
|
|
E_CurState.LoadingCharacters => (float)listShowPointResult.Count / CharacterMaxCount,
|
|
E_CurState.Present => 1f,
|
|
_ => 0f,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 抽取流程的记录
|
|
/// </summary>
|
|
[Serializable]
|
|
public struct ExtractRecord
|
|
{
|
|
/// <summary>
|
|
/// 单个演出点记录
|
|
/// </summary>
|
|
[Serializable]
|
|
public struct SinglePointRecord
|
|
{
|
|
[LabelText("演出点ID")]
|
|
public int showPoint;
|
|
|
|
public int timelineID;
|
|
|
|
public uint characterID;
|
|
}
|
|
|
|
[LabelText("组ID")]
|
|
private int groupID;
|
|
|
|
[LabelText("抛弃原因")]
|
|
[ShowIf("isDispose")]
|
|
private E_DisposeReason disposeReason;
|
|
|
|
[LabelText("角色记录")]
|
|
private List<SinglePointRecord> listRecord;
|
|
|
|
public ExtractRecord(int groupID, E_DisposeReason disposeReason)
|
|
{
|
|
this.groupID = groupID;
|
|
this.disposeReason = disposeReason;
|
|
|
|
listRecord = new();
|
|
}
|
|
|
|
public readonly void Add(SinglePointRecord singlePointRecord)
|
|
{
|
|
listRecord.Add(singlePointRecord);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 演出组数据
|
|
/// </summary>
|
|
public struct ShowGroupData
|
|
{
|
|
/// <summary>
|
|
/// 演出组id
|
|
/// </summary>
|
|
private readonly int groupId;
|
|
/// <summary>
|
|
/// 是否必须有角色
|
|
/// </summary>
|
|
private bool isMustHasCharacter;
|
|
/// <summary>
|
|
/// 该组演出点配置数据
|
|
/// </summary>
|
|
private readonly List<DataShowConfig> listConfig;
|
|
|
|
/// <summary>
|
|
/// 演出组id
|
|
/// </summary>
|
|
public readonly int GroupID
|
|
{
|
|
get { return groupId; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 是否必须有角色
|
|
/// </summary>
|
|
public readonly bool IsMustHasCharacter { get { return isMustHasCharacter; } }
|
|
|
|
/// <summary>
|
|
/// 该组演出点配置数据
|
|
/// </summary>
|
|
public readonly List<DataShowConfig> ListConfig
|
|
{
|
|
get { return listConfig; }
|
|
}
|
|
|
|
public ShowGroupData(int groupId)
|
|
{
|
|
this.groupId = groupId;
|
|
|
|
isMustHasCharacter = false;
|
|
listConfig = new();
|
|
}
|
|
|
|
public void Add(DataShowConfig dataShowConfig)
|
|
{
|
|
isMustHasCharacter |= dataShowConfig.HasCharacter;
|
|
listConfig.Add(dataShowConfig);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 打乱顺序
|
|
/// </summary>
|
|
public readonly void Shuffle()
|
|
{
|
|
CommonUtils.Shuffle(listConfig);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 演出点结果
|
|
/// </summary>
|
|
public readonly struct ShowPointResult
|
|
{
|
|
/// <summary>
|
|
/// 演出组id
|
|
/// </summary>
|
|
private readonly int groupID;
|
|
/// <summary>
|
|
/// 演出点索引
|
|
/// </summary>
|
|
private readonly int showPointIndex;
|
|
/// <summary>
|
|
/// Timelineid
|
|
/// </summary>
|
|
private readonly int timelineID;
|
|
/// <summary>
|
|
/// 角色数据
|
|
/// </summary>
|
|
private readonly CharacterDataInfo characterData;
|
|
|
|
public int GroupID
|
|
{
|
|
get { return groupID; }
|
|
}
|
|
|
|
public int ShowPointIndex
|
|
{
|
|
get { return showPointIndex; }
|
|
}
|
|
|
|
public int TimelineID
|
|
{
|
|
get { return timelineID; }
|
|
}
|
|
|
|
public CharacterDataInfo CharacterData
|
|
{
|
|
get { return characterData; }
|
|
}
|
|
|
|
public ShowPointResult(int groupID, int showPointIndex, int timelineID, CharacterDataInfo characterData)
|
|
{
|
|
this.groupID = groupID;
|
|
this.showPointIndex = showPointIndex;
|
|
this.timelineID = timelineID;
|
|
this.characterData = characterData;
|
|
}
|
|
} |