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

358 lines
12 KiB
C#

using System.Collections.Generic;
using Definition.Enum;
using Event;
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 BgFormController _bgFormController;
private int _bgRequestVersion;
private bool _isExecuting;
private void Start()
{
GameEntry.Event.Subscribe(DialogCompletedEventArgs.EventId, OnDialogCompleted);
GameEntry.Event.Subscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted);
}
private void OnDestroy()
{
GameEntry.Event.Unsubscribe(DialogCompletedEventArgs.EventId, OnDialogCompleted);
GameEntry.Event.Unsubscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted);
_bgFormController?.CloseUI();
_bgFormController = null;
}
public void ResetConsumedDirectives()
{
_consumedDirectiveTokens.Clear();
}
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, 0);
}
private void ExecuteDirectives(StoryTriggerType triggerType, int triggerId)
{
if (_isExecuting)
{
return;
}
_isExecuting = true;
try
{
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))
{
continue;
}
ExecuteDirective(directive, triggerType, triggerId);
if (!_allowRepeatTrigger)
{
_consumedDirectiveTokens.Add(directiveToken);
}
}
}
finally
{
_isExecuting = false;
}
}
private static string BuildDirectiveToken(StoryDirectiveAsset directive, int directiveIndex)
{
return $"{directive.GetInstanceID()}:{directiveIndex}";
}
private void ExecuteDirective(StoryDirectiveAsset directive, StoryTriggerType triggerType, int triggerId)
{
directive.Execute(this);
if (_verboseLog)
{
Log.Info("StoryDirector executed action '{0}' by trigger '{1}' ({2}).",
directive.ActionName, triggerType.ToString(), triggerId.ToString());
}
}
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)
{
string assetName = string.IsNullOrWhiteSpace(backgroundAssetName)
? string.Empty
: backgroundAssetName.Trim();
if (string.IsNullOrEmpty(assetName))
{
Log.Warning("StoryDirector change background skipped. assetName is empty.");
return;
}
if (GameEntry.SpriteCache == null)
{
Log.Warning("StoryDirector change background failed. SpriteCache component is missing.");
return;
}
int requestVersion = ++_bgRequestVersion;
GameEntry.SpriteCache.GetSprite(_backgroundAssetNamePrefix + assetName,
sprite => { OnBackgroundLoaded(requestVersion, assetName, sprite); });
}
private void OnBackgroundLoaded(int requestVersion, string assetName, Sprite sprite)
{
if (requestVersion != _bgRequestVersion)
{
return;
}
if (sprite == null)
{
Log.Warning("StoryDirector change background failed. Loaded sprite is null for '{0}'.", assetName);
return;
}
if (_bgFormController == null)
{
_bgFormController = new BgFormController();
}
_bgFormController.OpenUI(new BgFormContext
{
Sprite = sprite
});
}
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 static CombineFormContext BuildCombineFormContext(StoryCombineConfig config)
{
if (config != null && config.Slots != null && config.Slots.Count > 0)
{
return new CombineFormContext
{
Slots = CloneSlots(config.Slots),
Parts = CloneParts(config.Parts),
AutoStart = config.AutoStart
};
}
return BuildDefaultCombineContext();
}
private static List<CombineSlotContext> CloneSlots(List<CombineSlotContext> source)
{
var result = new List<CombineSlotContext>();
if (source == null)
{
return result;
}
for (int i = 0; i < source.Count; i++)
{
CombineSlotContext slot = source[i];
if (slot == null)
{
continue;
}
result.Add(new CombineSlotContext
{
RequiredPartType = slot.RequiredPartType,
BuildOrder = slot.BuildOrder,
RequireStrictOrder = slot.RequireStrictOrder,
AnchoredPosition = slot.AnchoredPosition,
SizeDelta = slot.SizeDelta,
MechanicsExplanation = slot.MechanicsExplanation,
MismatchHint = slot.MismatchHint
});
}
return result;
}
private static List<CombinePartContext> CloneParts(List<CombinePartContext> source)
{
var result = new List<CombinePartContext>();
if (source == null)
{
return result;
}
for (int i = 0; i < source.Count; i++)
{
CombinePartContext part = source[i];
if (part == null)
{
continue;
}
result.Add(new CombinePartContext
{
PartType = part.PartType,
PartDisplayName = part.PartDisplayName,
MechanicsExplanation = part.MechanicsExplanation,
LockAfterPlaced = part.LockAfterPlaced
});
}
return result;
}
private static CombineFormContext BuildDefaultCombineContext()
{
List<CombineSlotContext> slots = new List<CombineSlotContext>
{
new CombineSlotContext
{
RequiredPartType = CombinePartType.Dou,
BuildOrder = 0,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(-320f, -160f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Dou transfers upper load and works as the base node."
},
new CombineSlotContext
{
RequiredPartType = CombinePartType.Sheng,
BuildOrder = 1,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(-320f, -20f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Sheng raises layer height to form the bracket hierarchy."
},
new CombineSlotContext
{
RequiredPartType = CombinePartType.Gong,
BuildOrder = 2,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(-320f, 120f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Gong spreads force laterally through overhang."
},
new CombineSlotContext
{
RequiredPartType = CombinePartType.Qiao,
BuildOrder = 3,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(-160f, 120f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Qiao continues force transfer to the outer side."
},
new CombineSlotContext
{
RequiredPartType = CombinePartType.Ang,
BuildOrder = 4,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(0f, 120f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Ang uses leverage to redirect eave load inward."
}
};
List<CombinePartContext> parts = new List<CombinePartContext>
{
new CombinePartContext
{ PartType = CombinePartType.Dou, PartDisplayName = "Dou", LockAfterPlaced = true },
new CombinePartContext
{ PartType = CombinePartType.Sheng, PartDisplayName = "Sheng", LockAfterPlaced = true },
new CombinePartContext
{ PartType = CombinePartType.Gong, PartDisplayName = "Gong", LockAfterPlaced = true },
new CombinePartContext
{ PartType = CombinePartType.Qiao, PartDisplayName = "Qiao", LockAfterPlaced = true },
new CombinePartContext
{ PartType = CombinePartType.Ang, PartDisplayName = "Ang", LockAfterPlaced = true }
};
return new CombineFormContext
{
Slots = slots,
Parts = parts,
AutoStart = true
};
}
}
}