452 lines
13 KiB
C#
452 lines
13 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using cfg.CampCfg;
|
||
using Cysharp.Threading.Tasks;
|
||
using Framework;
|
||
using Gameplay;
|
||
using PhxhSDK;
|
||
using Sirenix.OdinInspector;
|
||
using UnityEngine;
|
||
using UnityEngine.Playables;
|
||
using UnityEngine.Serialization;
|
||
using UnityEngine.Timeline;
|
||
using Object = UnityEngine.Object;
|
||
|
||
public class CampShowConfigGroup
|
||
{
|
||
public int groupID;
|
||
|
||
public List<DataCampShowConfig> cfgs = new();
|
||
|
||
public bool hasToBeFull => cfgs.Any(cfg => cfg.HasCharacter);
|
||
}
|
||
|
||
public class CampShowPointResult
|
||
{
|
||
public int groupID;
|
||
|
||
public int showPoint;
|
||
|
||
public int timelineID;
|
||
|
||
public CharacterDataInfo characterDataInfo;
|
||
}
|
||
|
||
//抽取流程的记录
|
||
[Serializable]
|
||
public class ExtractRecord
|
||
{
|
||
[Serializable]
|
||
public class SinglePointRecord
|
||
{
|
||
[LabelText("演出点ID")]
|
||
public int showPoint;
|
||
|
||
public int timelineID;
|
||
|
||
public uint characterID;
|
||
}
|
||
|
||
[LabelText("组ID")]
|
||
public int groupID;
|
||
|
||
[LabelText("是否抛弃")]
|
||
public bool isDispose;
|
||
|
||
[LabelText("抛弃原因")]
|
||
[ShowIf("isDispose")]
|
||
public EDisposeReason disposeReason;
|
||
|
||
[LabelText("角色记录")]
|
||
public List<SinglePointRecord> records = new();
|
||
}
|
||
|
||
//被弃用的原因
|
||
public enum EDisposeReason
|
||
{
|
||
None,
|
||
|
||
[LabelText("演出点重复")]
|
||
ShowPointRepeat,
|
||
|
||
[LabelText("组内人数不满")]
|
||
CharacterCountNotFull,
|
||
|
||
[LabelText("固定角色不存在")]
|
||
FixedCharacterNotExist,
|
||
}
|
||
|
||
public class CampSceneManager : MonoBehaviour
|
||
{
|
||
private enum ECurrState
|
||
{
|
||
None = 0,
|
||
LoadingCampObjects,
|
||
LoadingCharacters,
|
||
Present,
|
||
}
|
||
|
||
[LabelText("抽取角色数")]
|
||
[SerializeField]
|
||
private int _characterMaxCount = 20;
|
||
|
||
[LabelText("timeline导演")]
|
||
[HideInInspector]
|
||
[SerializeField]
|
||
private PlayableDirector _director;
|
||
|
||
[LabelText("角色根节点")]
|
||
[HideInInspector]
|
||
[SerializeField]
|
||
private Transform _characterRoot;
|
||
|
||
[LabelText("营地物品根节点")]
|
||
[HideInInspector]
|
||
[SerializeField]
|
||
private Transform _campObjectsRoot;
|
||
|
||
[LabelText("演出点")]
|
||
[FormerlySerializedAs("interactionPoints")]
|
||
[ListDrawerSettings(ShowIndexLabels = true, AlwaysAddDefaultValue = true)]
|
||
[SerializeField]
|
||
private List<GameObject> _showPoints = new();
|
||
|
||
[ReadOnly]
|
||
[LabelText("抽取记录")]
|
||
[SerializeField]
|
||
[ShowIf("@_extractRecords.Count > 0")]
|
||
private List<ExtractRecord> _extractRecords = new();
|
||
|
||
private List<CharacterDataInfo> _allCharacterInfos = new();
|
||
|
||
private Dictionary<int, CampShowConfigGroup> _allCampShowConfigGroups = new();
|
||
|
||
private List<CampShowConfigGroup> _remindCampShowConfigGroups = new();
|
||
|
||
private List<CharacterDataInfo> _remindCharacters = new();
|
||
|
||
private List<CampShowPointResult> _campShowPointResults = new();
|
||
|
||
private TimelineAsset _mainTimeline;
|
||
|
||
public IEnumerable<GameObject> ShowPoints => _showPoints;
|
||
|
||
//asset相关
|
||
private Dictionary<CharacterDataInfo, GameObject> _models = new();
|
||
|
||
private Dictionary<int, TimelineAsset> _timeLines = new();
|
||
|
||
private HashSet<string> _assetToUnload = new();
|
||
|
||
public int CharacterMaxCount => Mathf.Min(_allCharacterInfos.Count, _characterMaxCount, _showPoints.Count);
|
||
|
||
private ECurrState _currState = ECurrState.None;
|
||
|
||
private void Awake()
|
||
{
|
||
if (!CommonUtils.IsInGame())
|
||
return;
|
||
_currState = ECurrState.None;
|
||
_mainTimeline = _director.playableAsset as TimelineAsset;
|
||
LoadData();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
foreach (var path in _assetToUnload)
|
||
{
|
||
AssetManager.Instance.Unload(path);
|
||
}
|
||
}
|
||
|
||
public async UniTask Refresh()
|
||
{
|
||
await RefreshCampObjects();
|
||
await RefreshCampShow();
|
||
_currState = ECurrState.Present;
|
||
}
|
||
|
||
private async UniTask RefreshCampObjects()
|
||
{
|
||
_currState = ECurrState.LoadingCampObjects;
|
||
return;
|
||
}
|
||
|
||
private async UniTask RefreshCampShow()
|
||
{
|
||
_currState = ECurrState.LoadingCharacters;
|
||
RefreshCampShowData();
|
||
CampShowExtract();
|
||
await ExecuteCampShowResult();
|
||
}
|
||
|
||
private void LoadData()
|
||
{
|
||
_allCharacterInfos = new List<CharacterDataInfo>(CharacterDataInfoManager.Instance.DataList);
|
||
var campShowConfigList = TableManager.Instance.Tables.CampShowConfig.DataList;
|
||
_allCampShowConfigGroups.Clear();
|
||
|
||
foreach (var campShowConfig in campShowConfigList)
|
||
{
|
||
var groupID = campShowConfig.GroupID;
|
||
if (!_allCampShowConfigGroups.TryGetValue(groupID, out var group))
|
||
{
|
||
group = new();
|
||
_allCampShowConfigGroups.Add(groupID, group);
|
||
}
|
||
|
||
group.cfgs.Add(campShowConfig);
|
||
group.groupID = groupID;
|
||
}
|
||
|
||
foreach (var group in _allCampShowConfigGroups.Values)
|
||
{
|
||
//有指定ID的先选
|
||
group.cfgs.Sort((a, b) => b.CharacterID.CompareTo(a.CharacterID));
|
||
}
|
||
}
|
||
|
||
private void RefreshCampShowData()
|
||
{
|
||
_remindCampShowConfigGroups.Clear();
|
||
_remindCharacters.Clear();
|
||
_campShowPointResults.Clear();
|
||
_extractRecords.Clear();
|
||
|
||
_remindCampShowConfigGroups = new(_allCampShowConfigGroups.Values);
|
||
_remindCharacters = new(_allCharacterInfos);
|
||
}
|
||
|
||
//抽取
|
||
private void CampShowExtract()
|
||
{
|
||
List<CampShowPointResult> _tempResultList = new();
|
||
while (_campShowPointResults.Count < CharacterMaxCount)
|
||
{
|
||
var randomGroup = CommonUtils.GetRandomItem(_remindCampShowConfigGroups, true);
|
||
if (randomGroup == null)
|
||
{
|
||
//如果所有组都抽完了,则直接返回
|
||
return;
|
||
}
|
||
|
||
ExtractRecord record = new()
|
||
{
|
||
groupID = randomGroup.groupID
|
||
};
|
||
_extractRecords.Add(record);
|
||
|
||
|
||
//如果组里的演出点和当前已经使用的演出点重复,则跳过该组
|
||
if (IsShowPointExist(randomGroup))
|
||
{
|
||
record.isDispose = true;
|
||
record.disposeReason = EDisposeReason.ShowPointRepeat;
|
||
continue;
|
||
}
|
||
|
||
|
||
//如果组里有限定演员,但演员已使用,则跳过该组
|
||
if (!CanUseCharacter(randomGroup))
|
||
{
|
||
record.isDispose = true;
|
||
record.disposeReason = EDisposeReason.FixedCharacterNotExist;
|
||
continue;
|
||
}
|
||
|
||
//临时存放抽取的结果
|
||
_tempResultList.Clear();
|
||
foreach (var cfg in randomGroup.cfgs)
|
||
{
|
||
//如果当前没有剩余的演员,则直接跳出抽取
|
||
if (_remindCharacters.Count == 0)
|
||
break;
|
||
ExtractRecord.SinglePointRecord singlePointRecord = new();
|
||
record.records.Add(singlePointRecord);
|
||
//抽取timeline
|
||
var timelineID = CommonUtils.GetRandomItem(cfg.TimelineIDs);
|
||
singlePointRecord.timelineID = timelineID;
|
||
if (timelineID == 0)
|
||
//说明不站人
|
||
continue;
|
||
|
||
CharacterDataInfo characterDataInfo = null;
|
||
if (cfg.CharacterID > 0)
|
||
{
|
||
//抽取指定演员
|
||
var index = _remindCharacters.FindIndex(characterInfo => characterInfo.ID == cfg.CharacterID);
|
||
characterDataInfo = _remindCharacters[index];
|
||
_remindCharacters.RemoveAt(index);
|
||
}
|
||
else
|
||
{
|
||
//随机抽取演员
|
||
characterDataInfo = CommonUtils.GetRandomItem(_remindCharacters, true);
|
||
}
|
||
|
||
CampShowPointResult result = new()
|
||
{
|
||
groupID = randomGroup.groupID,
|
||
showPoint = cfg.PointID,
|
||
timelineID = timelineID,
|
||
characterDataInfo = characterDataInfo
|
||
};
|
||
|
||
singlePointRecord.showPoint = cfg.PointID;
|
||
singlePointRecord.characterID = characterDataInfo.ID;
|
||
_tempResultList.Add(result);
|
||
}
|
||
|
||
if (randomGroup.hasToBeFull && _tempResultList.Count < randomGroup.cfgs.Count)
|
||
{
|
||
//如果该组必须满员但没满员
|
||
//取出来的CharacterDataInfo归还
|
||
foreach (var result in _tempResultList)
|
||
{
|
||
_remindCharacters.Add(result.characterDataInfo);
|
||
}
|
||
|
||
record.isDispose = true;
|
||
record.disposeReason = EDisposeReason.CharacterCountNotFull;
|
||
}
|
||
else
|
||
{
|
||
//添加到最后的result列表中
|
||
_campShowPointResults.AddRange(_tempResultList);
|
||
}
|
||
}
|
||
}
|
||
|
||
//执行抽取结果
|
||
private async UniTask ExecuteCampShowResult()
|
||
{
|
||
if (_campShowPointResults.Count == 0)
|
||
return;
|
||
foreach (var result in _campShowPointResults)
|
||
{
|
||
var showPointID = result.showPoint;
|
||
if (showPointID < 0 || showPointID >= _showPoints.Count || _showPoints[showPointID] == null)
|
||
{
|
||
DebugUtil.LogError($"找不到演出点!演出点ID:{showPointID}");
|
||
continue;
|
||
}
|
||
|
||
var showPointTransform = _showPoints[showPointID];
|
||
|
||
var model = await GetModel(result.characterDataInfo);
|
||
if (model == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
model.transform.position = showPointTransform.transform.position;
|
||
|
||
var timelineAsset = await GetTimeLineAsset(result.timelineID);
|
||
if (timelineAsset == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var director = model.GetComponent<PlayableDirector>();
|
||
if (director == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
director.playableAsset = timelineAsset;
|
||
director.extrapolationMode = DirectorWrapMode.Loop;
|
||
foreach (var playableBinding in timelineAsset.outputs)
|
||
{
|
||
director.SetGenericBinding(playableBinding.sourceObject, model.GetComponentInChildren<Animator>());
|
||
}
|
||
|
||
director.Play();
|
||
}
|
||
}
|
||
|
||
private async UniTask<GameObject> GetModel(CharacterDataInfo characterDataInfo)
|
||
{
|
||
if (!_models.TryGetValue(characterDataInfo, out var model))
|
||
{
|
||
string prefabPath = characterDataInfo.PrefabPath;
|
||
model = await AssetManager.Instance.LoadAssetAsync<GameObject>(prefabPath);
|
||
if (model == null)
|
||
{
|
||
DebugUtil.LogError($"找不到模型, path:{prefabPath}");
|
||
return null;
|
||
}
|
||
|
||
model = Instantiate(model, _characterRoot);
|
||
model.AddComponent<PlayableDirector>();
|
||
model.SetLayerEx(LayerMask.NameToLayer("UIView"));
|
||
_assetToUnload.Add(prefabPath);
|
||
_models.Add(characterDataInfo, model);
|
||
}
|
||
|
||
return model;
|
||
}
|
||
|
||
private async UniTask<TimelineAsset> GetTimeLineAsset(int timelineID)
|
||
{
|
||
if (!_timeLines.TryGetValue(timelineID, out var timelineAsset))
|
||
{
|
||
string timelinePath = CampTimeLineCollector.Instance[timelineID];
|
||
if (string.IsNullOrEmpty(timelinePath))
|
||
{
|
||
DebugUtil.LogError($"找不到timelinePath,id:{timelineID}");
|
||
return null;
|
||
}
|
||
|
||
timelineAsset = await AssetManager.Instance.LoadAssetAsync<TimelineAsset>(timelinePath);
|
||
if (timelineAsset == null)
|
||
{
|
||
DebugUtil.LogError($"找不到TimelineAsset, timelineID:{timelineID} path:{timelinePath}");
|
||
return null;
|
||
}
|
||
|
||
_assetToUnload.Add(timelinePath);
|
||
_timeLines.Add(timelineID, timelineAsset);
|
||
}
|
||
|
||
return timelineAsset;
|
||
}
|
||
|
||
private bool IsShowPointExist(CampShowConfigGroup group)
|
||
{
|
||
return group.cfgs.Any(cfg => IsShowPointExist(cfg.PointID));
|
||
}
|
||
|
||
private bool IsShowPointExist(int showPointID)
|
||
{
|
||
return _campShowPointResults.Any(result => result.showPoint == showPointID);
|
||
}
|
||
|
||
private bool CanUseCharacter(CampShowConfigGroup group)
|
||
{
|
||
return !group.cfgs.Any(cfg => cfg.CharacterID > 0 && !CanUseCharacter(cfg.CharacterID));
|
||
}
|
||
|
||
private bool CanUseCharacter(int characterID)
|
||
{
|
||
return _remindCharacters.Any(characterInfo => characterInfo.ID == characterID);
|
||
}
|
||
|
||
public float GetRefreshProcess()
|
||
{
|
||
switch (_currState)
|
||
{
|
||
case ECurrState.None:
|
||
return 0;
|
||
case ECurrState.LoadingCampObjects:
|
||
return 0.5f;
|
||
case ECurrState.LoadingCharacters:
|
||
return 0.5f + (float)_campShowPointResults.Count / CharacterMaxCount * 0.5f;
|
||
case ECurrState.Present:
|
||
return 1;
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
} |