调整 Directive 逻辑并添加章节之间的承接功能

This commit is contained in:
SepComet 2026-04-24 14:15:40 +08:00
parent facfe7a437
commit d557153e28
18 changed files with 164999 additions and 163916 deletions

2
.gitignore vendored
View File

@ -101,3 +101,5 @@ crashlytics-build.properties
/.omx /.omx
/.dotnet_home /.dotnet_home
/.dotnet /.dotnet
/.claude
/.codex

View File

@ -1 +0,0 @@
{"session_id":"980cf702-c5aa-462f-a54f-9cf7f5a5882d","transcript_path":"C:\\Users\\September\\.claude\\projects\\D--Learn-GameLearn-UnityProjects-Biography-of-Li-Jian\\980cf702-c5aa-462f-a54f-9cf7f5a5882d.jsonl","cwd":"D:\\Learn\\GameLearn\\UnityProjects\\Biography of Li Jian","model":{"id":"MiniMax-M2.7-highspeed","display_name":"MiniMax-M2.7-highspeed"},"workspace":{"current_dir":"D:\\Learn\\GameLearn\\UnityProjects\\Biography of Li Jian","project_dir":"D:\\Learn\\GameLearn\\UnityProjects\\Biography of Li Jian","added_dirs":[]},"version":"2.1.114","output_style":{"name":"default"},"cost":{"total_cost_usd":0.45526774999999997,"total_duration_ms":743042,"total_api_duration_ms":19399,"total_lines_added":0,"total_lines_removed":0},"context_window":{"total_input_tokens":31113,"total_output_tokens":1126,"context_window_size":200000,"current_usage":{"input_tokens":17274,"output_tokens":521,"cache_creation_input_tokens":0,"cache_read_input_tokens":26409},"used_percentage":22,"remaining_percentage":78},"exceeds_200k_tokens":false}

View File

@ -1,6 +0,0 @@
{
"timestamp": "2026-04-18T09:34:00.712Z",
"backgroundTasks": [],
"sessionStartTimestamp": "2026-04-18T09:33:32.703Z",
"sessionId": "1b0f5c6b-460e-48ef-9f2b-eb516bfbee3e"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 99d811b0183246646a2ce8df996f4bca guid: 98380886ef3ddab4e913ead8133a2e42
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 0 mainObjectFileID: 11400000
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

@ -10,7 +10,7 @@ MonoBehaviour:
m_Enabled: 1 m_Enabled: 1
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 71c1514a6bd24e1e882cebbe1904ce04, type: 3} m_Script: {fileID: 11500000, guid: 71c1514a6bd24e1e882cebbe1904ce04, type: 3}
m_Name: simfang SDF m_Name: simfangTMP
m_EditorClassIdentifier: m_EditorClassIdentifier:
hashCode: 1985928552 hashCode: 1985928552
material: {fileID: 7425184593944002063} material: {fileID: 7425184593944002063}

View File

@ -55,6 +55,15 @@ namespace CustomComponent
private int _nextChapterTitleRequestId = 1; private int _nextChapterTitleRequestId = 1;
private bool _chapterTitleShownAfterInit; private bool _chapterTitleShownAfterInit;
[Header("章节标题过场")]
[SerializeField] [Min(0f)] private float _chapterTitleDimFadeInDuration = 0.2f;
[SerializeField] [Min(0f)] private float _chapterTitlePreBlackDuration = 0.25f;
[SerializeField] [Min(0f)] private float _chapterTitleTitleFadeInDelay = 0f;
[SerializeField] [Min(0f)] private float _chapterTitleFadeInDuration = 0.3f;
[SerializeField] [Min(0f)] private float _chapterTitleHoldDuration = 1.2f;
[SerializeField] [Min(0f)] private float _chapterTitleFadeOutDuration = 0.3f;
[SerializeField] [Min(0f)] private float _chapterTitleDimFadeOutDuration = 0.2f;
private int _currentChapterId; private int _currentChapterId;
private int _currentLineIndex = -1; private int _currentLineIndex = -1;
private bool _isInitialized; private bool _isInitialized;
@ -572,7 +581,14 @@ namespace CustomComponent
ChapterId = _currentChapterId, ChapterId = _currentChapterId,
RequestId = requestId, RequestId = requestId,
Title = BuildChapterTitle(_currentChapterId), Title = BuildChapterTitle(_currentChapterId),
Subtitle = subtitle Subtitle = subtitle,
DimFadeInDuration = _chapterTitleDimFadeInDuration,
PreBlackDuration = _chapterTitlePreBlackDuration,
TitleFadeInDelay = _chapterTitleTitleFadeInDelay,
TitleFadeInDuration = _chapterTitleFadeInDuration,
HoldDuration = _chapterTitleHoldDuration,
TitleFadeOutDuration = _chapterTitleFadeOutDuration,
DimFadeOutDuration = _chapterTitleDimFadeOutDuration
}; };
int? formSerialId = GameEntry.UI.OpenUIForm(UIFormId.ChapterTitleForm, context); int? formSerialId = GameEntry.UI.OpenUIForm(UIFormId.ChapterTitleForm, context);

View File

@ -1,14 +1,22 @@
using System.Collections; using DataTable;
using System.Collections.Generic; using Event;
using GameFramework.DataTable;
using GameFramework.Event;
using GameFramework.Fsm; using GameFramework.Fsm;
using GameFramework.Procedure; using GameFramework.Procedure;
using UnityEngine;
using UI; using UI;
using UnityGameFramework.Runtime;
namespace Procedure namespace Procedure
{ {
public class ProcedureMain : ProcedureBase public class ProcedureMain : ProcedureBase
{ {
private const int InitialChapterId = 1;
private const int InitialDialogId = 1001;
private const int DialogChapterDivisor = 1000;
private IDataTable<DRDialog> _dtDialog;
public override bool UseNativeDialog => false; public override bool UseNativeDialog => false;
protected override void OnEnter(IFsm<IProcedureManager> procedureOwner) protected override void OnEnter(IFsm<IProcedureManager> procedureOwner)
@ -16,8 +24,104 @@ namespace Procedure
base.OnEnter(procedureOwner); base.OnEnter(procedureOwner);
AIChatEntryRuntime.EnsureOpen(); AIChatEntryRuntime.EnsureOpen();
GameEntry.Dialog.Init(1); GameEntry.Event.Subscribe(StoryChapterEndedEventArgs.EventId, OnStoryChapterEnded);
GameEntry.Dialog.StartDialog(1001);
GameEntry.Dialog.Init(InitialChapterId);
GameEntry.Dialog.StartDialog(InitialDialogId);
}
protected override void OnLeave(IFsm<IProcedureManager> procedureOwner, bool isShutdown)
{
GameEntry.Event.Unsubscribe(StoryChapterEndedEventArgs.EventId, OnStoryChapterEnded);
base.OnLeave(procedureOwner, isShutdown);
}
private void OnStoryChapterEnded(object sender, GameEventArgs e)
{
if (!(e is StoryChapterEndedEventArgs args))
{
return;
}
int currentChapterId = args.ChapterId > 0
? args.ChapterId
: (GameEntry.Dialog != null ? GameEntry.Dialog.CurrentChapterId : 0);
int nextChapterId = currentChapterId + 1;
if (nextChapterId <= 0)
{
return;
}
if (!TryResolveFirstDialogId(nextChapterId, out int nextDialogId))
{
Log.Info("ProcedureMain chapter switch stopped. No dialog found for chapter '{0}'.",
nextChapterId.ToString());
return;
}
if (GameEntry.Dialog == null)
{
Log.Warning("ProcedureMain chapter switch failed. Dialog component is missing.");
return;
}
if (!GameEntry.Dialog.Init(nextChapterId))
{
Log.Warning("ProcedureMain chapter switch failed. Init chapter '{0}' failed.",
nextChapterId.ToString());
return;
}
if (!GameEntry.Dialog.StartDialog(nextDialogId))
{
Log.Warning("ProcedureMain chapter switch failed. Start dialog '{0}' failed.",
nextDialogId.ToString());
}
}
private bool TryResolveFirstDialogId(int chapterId, out int dialogId)
{
dialogId = 0;
if (chapterId <= 0)
{
return false;
}
if (_dtDialog == null)
{
_dtDialog = GameEntry.DataTable.GetDataTable<DRDialog>();
}
if (_dtDialog == null)
{
Log.Warning("ProcedureMain chapter switch failed. Data table DRDialog is missing.");
return false;
}
DRDialog[] dialogRows = _dtDialog.GetDataRows((a, b) => a.Id.CompareTo(b.Id));
for (int i = 0; i < dialogRows.Length; i++)
{
DRDialog row = dialogRows[i];
if (row == null)
{
continue;
}
if (ParseChapterIdFromDialogId(row.Id) != chapterId)
{
continue;
}
dialogId = row.Id;
return dialogId > 0;
}
return false;
}
private static int ParseChapterIdFromDialogId(int dialogId)
{
return dialogId / DialogChapterDivisor;
} }
} }
} }

View File

@ -6,5 +6,14 @@ namespace UI
public int RequestId = 0; public int RequestId = 0;
public string Title = string.Empty; public string Title = string.Empty;
public string Subtitle = string.Empty; public string Subtitle = string.Empty;
// < 0 表示使用 ChapterTitleForm 上的默认配置。
public float DimFadeInDuration = -1f;
public float PreBlackDuration = -1f;
public float TitleFadeInDelay = -1f;
public float TitleFadeInDuration = -1f;
public float HoldDuration = -1f;
public float TitleFadeOutDuration = -1f;
public float DimFadeOutDuration = -1f;
} }
} }

View File

@ -13,14 +13,25 @@ namespace UI
[SerializeField] private Image _dimImage; [SerializeField] private Image _dimImage;
[SerializeField] private TMP_Text _titleText; [SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _subtitleText; [SerializeField] private TMP_Text _subtitleText;
[SerializeField] private float _dimFadeInDuration = 0.2f;
[SerializeField] private float _preBlackDuration = 0.25f;
[SerializeField] private float _titleFadeInDelay = 0f;
[SerializeField] private float _fadeInDuration = 0.3f; [SerializeField] private float _fadeInDuration = 0.3f;
[SerializeField] private float _holdDuration = 1.2f; [SerializeField] private float _holdDuration = 1.2f;
[SerializeField] private float _fadeOutDuration = 0.3f; [SerializeField] private float _fadeOutDuration = 0.3f;
[SerializeField] private float _dimFadeOutDuration = 0.2f;
private Coroutine _playCoroutine; private Coroutine _playCoroutine;
private int _requestId; private int _requestId;
private int _chapterId; private int _chapterId;
private bool _hasPublishedCompletion; private bool _hasPublishedCompletion;
private float _runtimeDimFadeInDuration;
private float _runtimePreBlackDuration;
private float _runtimeTitleFadeInDelay;
private float _runtimeTitleFadeInDuration;
private float _runtimeHoldDuration;
private float _runtimeTitleFadeOutDuration;
private float _runtimeDimFadeOutDuration;
protected override void OnOpen(object userData) protected override void OnOpen(object userData)
{ {
@ -33,7 +44,8 @@ namespace UI
protected override void OnClose(bool isShutdown, object userData) protected override void OnClose(bool isShutdown, object userData)
{ {
StopPlay(); StopPlay();
SetContentAlpha(0f); SetDimAlpha(0f);
SetTitleAlpha(0f);
if (!isShutdown) if (!isShutdown)
{ {
PublishCompletionIfNeeded(); PublishCompletionIfNeeded();
@ -55,6 +67,11 @@ namespace UI
_contentGroup = CreateContentGroup(rootRect); _contentGroup = CreateContentGroup(rootRect);
} }
if (_contentGroup != null)
{
_contentGroup.alpha = 1f;
}
RectTransform contentRect = _contentGroup != null && _contentGroup.transform is RectTransform contentTransform RectTransform contentRect = _contentGroup != null && _contentGroup.transform is RectTransform contentTransform
? contentTransform ? contentTransform
: rootRect; : rootRect;
@ -105,6 +122,7 @@ namespace UI
private void ApplyContext(ChapterTitleFormContext context) private void ApplyContext(ChapterTitleFormContext context)
{ {
_hasPublishedCompletion = false; _hasPublishedCompletion = false;
ResetRuntimeTimings(context);
if (context == null) if (context == null)
{ {
@ -157,10 +175,17 @@ namespace UI
private IEnumerator PlayRoutine() private IEnumerator PlayRoutine()
{ {
SetContentAlpha(0f); SetDimAlpha(0f);
yield return FadeAlpha(0f, 1f, _fadeInDuration); SetTitleAlpha(0f);
yield return WaitUnscaled(_holdDuration);
yield return FadeAlpha(1f, 0f, _fadeOutDuration); yield return FadeDimAlpha(0f, 1f, _runtimeDimFadeInDuration);
yield return WaitUnscaled(_runtimePreBlackDuration);
yield return WaitUnscaled(_runtimeTitleFadeInDelay);
yield return FadeTitleAlpha(0f, 1f, _runtimeTitleFadeInDuration);
yield return WaitUnscaled(_runtimeHoldDuration);
yield return FadeTitleAlpha(1f, 0f, _runtimeTitleFadeOutDuration);
yield return FadeDimAlpha(1f, 0f, _runtimeDimFadeOutDuration);
_playCoroutine = null; _playCoroutine = null;
PublishCompletionIfNeeded(); PublishCompletionIfNeeded();
@ -170,12 +195,12 @@ namespace UI
} }
} }
private IEnumerator FadeAlpha(float from, float to, float duration) private IEnumerator FadeDimAlpha(float from, float to, float duration)
{ {
float safeDuration = Mathf.Max(0f, duration); float safeDuration = Mathf.Max(0f, duration);
if (safeDuration <= 0f) if (safeDuration <= 0f)
{ {
SetContentAlpha(to); SetDimAlpha(to);
yield break; yield break;
} }
@ -184,11 +209,32 @@ namespace UI
{ {
elapsed += Time.unscaledDeltaTime; elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / safeDuration); float t = Mathf.Clamp01(elapsed / safeDuration);
SetContentAlpha(Mathf.Lerp(from, to, t)); SetDimAlpha(Mathf.Lerp(from, to, t));
yield return null; yield return null;
} }
SetContentAlpha(to); SetDimAlpha(to);
}
private IEnumerator FadeTitleAlpha(float from, float to, float duration)
{
float safeDuration = Mathf.Max(0f, duration);
if (safeDuration <= 0f)
{
SetTitleAlpha(to);
yield break;
}
float elapsed = 0f;
while (elapsed < safeDuration)
{
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / safeDuration);
SetTitleAlpha(Mathf.Lerp(from, to, t));
yield return null;
}
SetTitleAlpha(to);
} }
private static IEnumerator WaitUnscaled(float duration) private static IEnumerator WaitUnscaled(float duration)
@ -207,14 +253,57 @@ namespace UI
} }
} }
private void SetContentAlpha(float alpha) private void SetDimAlpha(float alpha)
{ {
if (_contentGroup == null) if (_dimImage == null)
{ {
return; return;
} }
_contentGroup.alpha = Mathf.Clamp01(alpha); Color color = _dimImage.color;
color.a = Mathf.Clamp01(alpha);
_dimImage.color = color;
}
private void SetTitleAlpha(float alpha)
{
float clampedAlpha = Mathf.Clamp01(alpha);
SetTextAlpha(_titleText, clampedAlpha);
SetTextAlpha(_subtitleText, clampedAlpha);
}
private static void SetTextAlpha(TMP_Text text, float alpha)
{
if (text == null)
{
return;
}
Color color = text.color;
color.a = alpha;
text.color = color;
}
private void ResetRuntimeTimings(ChapterTitleFormContext context)
{
_runtimeDimFadeInDuration = ResolveDuration(context != null ? context.DimFadeInDuration : -1f,
_dimFadeInDuration);
_runtimePreBlackDuration = ResolveDuration(context != null ? context.PreBlackDuration : -1f,
_preBlackDuration);
_runtimeTitleFadeInDelay = ResolveDuration(context != null ? context.TitleFadeInDelay : -1f,
_titleFadeInDelay);
_runtimeTitleFadeInDuration = ResolveDuration(context != null ? context.TitleFadeInDuration : -1f,
_fadeInDuration);
_runtimeHoldDuration = ResolveDuration(context != null ? context.HoldDuration : -1f, _holdDuration);
_runtimeTitleFadeOutDuration = ResolveDuration(context != null ? context.TitleFadeOutDuration : -1f,
_fadeOutDuration);
_runtimeDimFadeOutDuration = ResolveDuration(context != null ? context.DimFadeOutDuration : -1f,
_dimFadeOutDuration);
}
private static float ResolveDuration(float overrideDuration, float fallbackDuration)
{
return overrideDuration >= 0f ? overrideDuration : Mathf.Max(0f, fallbackDuration);
} }
private void PublishCompletionIfNeeded() private void PublishCompletionIfNeeded()

View File

@ -14,5 +14,5 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
_enabled: 1 _enabled: 1
_triggerType: 0 _triggerType: 0
_triggerId: 1005 _triggerId: 1006
_chapterId: 2 _chapterId: 1

View File

@ -15,4 +15,4 @@ MonoBehaviour:
_enabled: 1 _enabled: 1
_triggerType: 0 _triggerType: 0
_triggerId: 2006 _triggerId: 2006
_chapterId: 3 _chapterId: 2

View File

@ -15,4 +15,4 @@ MonoBehaviour:
_enabled: 1 _enabled: 1
_triggerType: 0 _triggerType: 0
_triggerId: 3005 _triggerId: 3005
_chapterId: 4 _chapterId: 3

View File

@ -15,4 +15,4 @@ MonoBehaviour:
_enabled: 1 _enabled: 1
_triggerType: 0 _triggerType: 0
_triggerId: 4004 _triggerId: 4004
_chapterId: 0 _chapterId: 4

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 77968dfed7b61334db617d490bb05477 guid: 0d041269ac2f63d47bc5f189d90dc73d
TextureImporter: TextureImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}

20
openspec/config.yaml Normal file
View File

@ -0,0 +1,20 @@
schema: spec-driven
# Project context (optional)
# This is shown to AI when creating artifacts.
# Add your tech stack, conventions, style guides, domain knowledge, etc.
# Example:
# context: |
# Tech stack: TypeScript, React, Node.js
# We use conventional commits
# Domain: e-commerce platform
# Per-artifact rules (optional)
# Add custom rules for specific artifacts.
# Example:
# rules:
# proposal:
# - Keep proposals under 500 words
# - Always include a "Non-goals" section
# tasks:
# - Break tasks into chunks of max 2 hours