biography-of-lijie/Assets/GameMain/Scripts/CustomComponent/StoryDirectorComponent.cs

353 lines
12 KiB
C#

using System.Collections;
using System.Collections.Generic;
using Event;
using Sound;
using UI;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace CustomComponent
{
[DisallowMultipleComponent]
public class StoryDirectorComponent : GameFrameworkComponent
{
[SerializeField] [Tooltip("按顺序执行。每个元素是一个 StoryDirectiveAsset 资产引用。")]
private List<StoryDirectiveAsset> _directives = new List<StoryDirectiveAsset>();
[SerializeField] private bool _allowRepeatTrigger = false;
[SerializeField] private bool _verboseLog = true;
[SerializeField] private string _backgroundAssetNamePrefix = string.Empty;
private readonly HashSet<string> _consumedDirectiveTokens = new HashSet<string>();
private readonly HashSet<string> _queuedDirectiveTokens = new HashSet<string>();
private readonly Queue<PendingDirectiveInvocation> _pendingDirectives =
new Queue<PendingDirectiveInvocation>();
private BgFormController _bgFormController;
private Coroutine _directiveExecutionCoroutine;
private int _nextBackgroundRequestId = 1;
private int _lastBackgroundRequestId;
private int _completedBackgroundRequestId;
private int _lastStartedCombineTriggerId;
private struct PendingDirectiveInvocation
{
public StoryDirectiveAsset Directive;
public StoryTriggerType TriggerType;
public int TriggerId;
public string DirectiveToken;
}
private void Start()
{
GameEntry.Event.Subscribe(DialogCompletedEventArgs.EventId, OnDialogCompleted);
GameEntry.Event.Subscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted);
GameEntry.Event.Subscribe(BgTransitionCompletedEventArgs.EventId, OnBgTransitionCompleted);
}
private void OnDestroy()
{
GameEntry.Event.Unsubscribe(DialogCompletedEventArgs.EventId, OnDialogCompleted);
GameEntry.Event.Unsubscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted);
GameEntry.Event.Unsubscribe(BgTransitionCompletedEventArgs.EventId, OnBgTransitionCompleted);
if (_directiveExecutionCoroutine != null)
{
StopCoroutine(_directiveExecutionCoroutine);
_directiveExecutionCoroutine = null;
}
_bgFormController?.CloseUI();
_bgFormController = null;
}
public void ResetConsumedDirectives()
{
_consumedDirectiveTokens.Clear();
_queuedDirectiveTokens.Clear();
_pendingDirectives.Clear();
_lastStartedCombineTriggerId = 0;
}
private void OnDialogCompleted(object sender, GameFramework.Event.GameEventArgs e)
{
if (!(e is DialogCompletedEventArgs args))
{
return;
}
ExecuteDirectives(StoryTriggerType.DialogCompleted, args.DialogId);
}
private void OnCombineCompleted(object sender, GameFramework.Event.GameEventArgs e)
{
if (!(e is CombineCompletedEventArgs))
{
return;
}
ExecuteDirectives(StoryTriggerType.CombineCompleted, _lastStartedCombineTriggerId);
}
private void ExecuteDirectives(StoryTriggerType triggerType, int triggerId)
{
bool hasEnqueuedDirective = false;
for (int i = 0; i < _directives.Count; i++)
{
StoryDirectiveAsset directive = _directives[i];
if (directive == null || !directive.IsMatch(triggerType, triggerId))
{
continue;
}
string directiveToken = BuildDirectiveToken(directive, i);
if (!_allowRepeatTrigger &&
(_consumedDirectiveTokens.Contains(directiveToken) || _queuedDirectiveTokens.Contains(directiveToken)))
{
continue;
}
_pendingDirectives.Enqueue(new PendingDirectiveInvocation
{
Directive = directive,
TriggerType = triggerType,
TriggerId = triggerId,
DirectiveToken = directiveToken
});
hasEnqueuedDirective = true;
if (!_allowRepeatTrigger)
{
_queuedDirectiveTokens.Add(directiveToken);
}
}
if (!hasEnqueuedDirective || _directiveExecutionCoroutine != null)
{
return;
}
_directiveExecutionCoroutine = StartCoroutine(ProcessDirectiveQueue());
}
private static string BuildDirectiveToken(StoryDirectiveAsset directive, int directiveIndex)
{
return $"{directive.GetInstanceID()}:{directiveIndex}";
}
private void ExecuteDirective(StoryDirectiveAsset directive, StoryTriggerType triggerType, int triggerId)
{
if (directive is StoryStartCombineDirectiveAsset)
{
_lastStartedCombineTriggerId = triggerId;
}
directive.Execute(this);
if (_verboseLog)
{
Log.Info("StoryDirector executed action '{0}' by trigger '{1}' ({2}).",
directive.ActionName, triggerType.ToString(), triggerId.ToString());
}
}
private IEnumerator ProcessDirectiveQueue()
{
while (_pendingDirectives.Count > 0)
{
PendingDirectiveInvocation invocation = _pendingDirectives.Dequeue();
if (!_allowRepeatTrigger)
{
_queuedDirectiveTokens.Remove(invocation.DirectiveToken);
}
ExecuteDirective(invocation.Directive, invocation.TriggerType, invocation.TriggerId);
if (invocation.Directive.RequiresCompletion)
{
yield return WaitForDirectiveCompletion(invocation.Directive);
}
if (!_allowRepeatTrigger)
{
_consumedDirectiveTokens.Add(invocation.DirectiveToken);
}
}
_directiveExecutionCoroutine = null;
}
private IEnumerator WaitForDirectiveCompletion(StoryDirectiveAsset directive)
{
if (!(directive is StoryChangeBackgroundDirectiveAsset))
{
yield break;
}
int requestId = _lastBackgroundRequestId;
if (requestId <= 0)
{
yield break;
}
const float timeoutSeconds = 10f;
float elapsed = 0f;
while (_completedBackgroundRequestId < requestId)
{
elapsed += Time.unscaledDeltaTime;
if (elapsed >= timeoutSeconds)
{
Log.Warning("StoryDirector wait background transition timeout. requestId={0}", requestId.ToString());
break;
}
yield return null;
}
}
public void ExecuteStartDialog(int dialogId)
{
if (dialogId <= 0)
{
Log.Warning("StoryDirector start dialog skipped. dialogId must be positive.");
return;
}
if (GameEntry.Dialog == null)
{
Log.Warning("StoryDirector start dialog failed. Dialog component is missing.");
return;
}
GameEntry.Dialog.StartDialog(dialogId);
}
public void ExecuteStartCombine(StoryCombineConfig config)
{
if (GameEntry.Combine == null)
{
Log.Warning("StoryDirector start combine failed. Combine component is missing.");
return;
}
CombineFormContext context = BuildCombineFormContext(config);
int? formSerialId = GameEntry.Combine.StartLevel(context);
if (!formSerialId.HasValue)
{
Log.Warning("StoryDirector start combine failed. Open combine UI failed.");
return;
}
}
public void ExecuteChangeBackground(string backgroundAssetName)
{
_lastBackgroundRequestId = 0;
string assetName = string.IsNullOrWhiteSpace(backgroundAssetName)
? string.Empty
: backgroundAssetName.Trim();
if (string.IsNullOrEmpty(assetName))
{
Log.Warning("StoryDirector change background skipped. assetName is empty.");
return;
}
if (_bgFormController == null)
{
_bgFormController = new BgFormController();
}
int requestId = _nextBackgroundRequestId++;
_lastBackgroundRequestId = requestId;
int? formSerialId = _bgFormController.OpenUI(new BgFormContext
{
BackgroundAssetName = _backgroundAssetNamePrefix + assetName,
TransitionRequestId = requestId
});
if (!formSerialId.HasValue)
{
Log.Warning("StoryDirector change background failed. BgForm open returned null.");
_completedBackgroundRequestId = Mathf.Max(_completedBackgroundRequestId, requestId);
}
}
public void ExecuteChangeBgm(int bgmId)
{
if (GameEntry.Sound == null)
{
Log.Warning("StoryDirector change BGM failed. Sound component is missing.");
return;
}
if (bgmId <= 0)
{
GameEntry.Sound.StopMusic();
return;
}
int? serialId = GameEntry.Sound.PlayBGM(bgmId);
if (!serialId.HasValue)
{
Log.Warning("StoryDirector change BGM failed. bgmId={0}", bgmId.ToString());
}
}
public void ExecuteEndChapter(int chapterId)
{
int finalChapterId = chapterId > 0
? chapterId
: (GameEntry.Dialog != null ? GameEntry.Dialog.CurrentChapterId : 0);
GameEntry.Event.Fire(this, StoryChapterEndedEventArgs.Create(finalChapterId));
}
private void OnBgTransitionCompleted(object sender, GameFramework.Event.GameEventArgs e)
{
if (!(e is BgTransitionCompletedEventArgs args))
{
return;
}
if (args.RequestId <= 0)
{
return;
}
_completedBackgroundRequestId = Mathf.Max(_completedBackgroundRequestId, args.RequestId);
}
private static CombineFormContext BuildCombineFormContext(StoryCombineConfig config)
{
if (config == null)
{
return null;
}
return new CombineFormContext
{
PartSprites = ClonePartSprites(config.PartSprites),
AutoStart = config.AutoStart
};
}
private static List<Sprite> ClonePartSprites(List<Sprite> source)
{
var result = new List<Sprite>();
if (source == null)
{
return result;
}
for (int i = 0; i < source.Count; i++)
{
result.Add(source[i]);
}
return result;
}
}
}