358 lines
12 KiB
C#
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
|
|
};
|
|
}
|
|
}
|
|
} |