剧情+对话完善

This commit is contained in:
SoulliesOfficial
2026-07-21 15:24:42 -04:00
parent 8f230831e9
commit 810d019619
161 changed files with 7271 additions and 1893 deletions

View File

@@ -94,6 +94,13 @@ namespace Ichni.Story.Dialogue
public override async YarnTask OnDialogueStartedAsync()
{
// 快进是一次 Yarn 对话会话的临时操作,绝不能遗留到下一次进入 TextBlock。
_isFastForwarding = false;
_typewriterComplete = false;
// 对话记录只保留当前一次 TextBlock每次 Yarn 对话真正开始时清空上一段残留内容。
DialogueHistory.BeginSession();
if (Page != null)
{
var tcs = new YarnTaskCompletionSource<bool>();
@@ -119,6 +126,8 @@ namespace Ichni.Story.Dialogue
// ── 打字效果 ──
string fullText = line.TextWithoutCharacterName.Text;
// 记录的是本次实际展示时已经解析完成的本地化文本,而非 Yarn Key。
DialogueHistory.AddLine(line.CharacterName, fullText);
_typewriterComplete = false;
if (Page.dialogueText != null)
@@ -163,6 +172,16 @@ namespace Ichni.Story.Dialogue
{
if (Page == null) return null;
// 同一 TextBlock 的选项已被玩家选择过时,直接把原 Option 返回给 Yarn
// 让后续指令和分支仍由 Yarn 正常执行,而不是在 C# 中复制分支逻辑。
string sourceBlockId = StoryDialogueController.instance?.ActiveBlockId;
if (StoryChoiceMemory.TryGetRememberedOption(sourceBlockId, options, out DialogueOption rememberedOption))
{
// 已记忆选项在普通回顾与快进时都会自动采用;记录页仍应如实显示本次经过的分支。
DialogueHistory.AddChoice(rememberedOption.Line.TextWithoutCharacterName.Text);
return rememberedOption;
}
// 遇到选项强制打断快进
_isFastForwarding = false;
@@ -173,9 +192,12 @@ namespace Ichni.Story.Dialogue
// 显示选项容器
if (Page.choiceFrame != null) Page.choiceFrame.SetActive(true);
_optionTcs = new YarnTaskCompletionSource<DialogueOption?>();
YarnTaskCompletionSource<DialogueOption?> optionTcs = new YarnTaskCompletionSource<DialogueOption?>();
_optionTcs = optionTcs;
WaitForExternalCancelAsync(token.NextContentToken).Forget();
// 将本组选项自己的 TCS 传入取消监听,避免上一组选项的 Token 在稍后取消时
// 错误结束下一组选项。_optionTcs 只用于外部 Back 流程定位当前正在等待的选择。
WaitForExternalCancelAsync(token.NextContentToken, optionTcs).Forget();
// ── 配置每个选项按钮 ──
for (int i = 0; i < Page.choiceButtons.Length; i++)
@@ -183,13 +205,24 @@ namespace Ichni.Story.Dialogue
if (Page.choiceButtons[i] == null) continue;
if (i < options.Length)
Page.choiceButtons[i].Setup(options[i], opt => _optionTcs.TrySetResult(opt));
{
Page.choiceButtons[i].Setup(options[i], opt =>
{
// 先写入选项记忆,再结束异步等待;这样即使紧接着 Yarn 修改路线变量,
// Timeline 快照也仍然代表“本组选择之前”的状态。
StoryChoiceMemory.RememberChoice(sourceBlockId, options, opt);
DialogueHistory.AddChoice(opt.Line.TextWithoutCharacterName.Text);
optionTcs.TrySetResult(opt);
});
}
else
Page.choiceButtons[i].Cleanup();
}
// ── 等待玩家选择 ──
DialogueOption? selected = await _optionTcs.Task;
DialogueOption? selected = await optionTcs.Task;
if (_optionTcs == optionTcs)
_optionTcs = null;
// 清理所有按钮并隐藏选项容器
foreach (ChoiceButton btn in Page.choiceButtons)
@@ -202,6 +235,10 @@ namespace Ichni.Story.Dialogue
public override async YarnTask OnDialogueCompleteAsync()
{
// 无论正常完成还是被 StoryDialogueController 通过 Stop 取消,都要结束本次快进状态。
_isFastForwarding = false;
_typewriterComplete = true;
if (Page != null)
{
var tcs = new YarnTaskCompletionSource<bool>();
@@ -210,10 +247,48 @@ namespace Ichni.Story.Dialogue
}
}
/// <summary>
/// 打开对话记录前的 UI 收束操作。
/// 停止自动快进,并补全当前正在打字的一句,但绝不请求下一句,
/// 因而 History 覆盖层打开期间不会改变 Yarn 的剧情进度。
/// </summary>
public void PrepareForHistory()
{
_isFastForwarding = false;
if (!_typewriterComplete && _runner != null)
_runner.RequestHurryUpLine();
}
/// <summary>
/// 为 DialogUIPage 的 Back 取消流程收起所有临时交互。
/// 真正终止 Yarn 和恢复章节快照由 <see cref="StoryDialogueController"/> 负责;
/// 本方法不写入任何剧情变量或选项。
/// </summary>
public void CancelCurrentPresentation()
{
_isFastForwarding = false;
_typewriterComplete = true;
_optionTcs?.TrySetResult(null);
if (Page == null)
return;
foreach (ChoiceButton button in Page.choiceButtons)
button?.Cleanup();
if (Page.choiceFrame != null)
Page.choiceFrame.SetActive(false);
}
// ── 内部方法 ─────────────────────────────────────────────────────────────
private void OnAdvanceButtonClicked()
{
// 未记忆选项必须由 ChoiceButton 明确选择;推进按钮不能越过当前选择。
if (_optionTcs != null)
return;
// 如果玩家在快进时点击屏幕/推进键,则打断快进状态并停止当前动作
if (_isFastForwarding)
{
@@ -231,6 +306,10 @@ namespace Ichni.Story.Dialogue
private void OnFastForwardButtonClicked()
{
// 快进可以跨过已经记忆的选项,但不能跳过正在等待玩家首次决定的选项。
if (_optionTcs != null)
return;
_isFastForwarding = true;
if (_runner == null) return;
@@ -263,10 +342,12 @@ namespace Ichni.Story.Dialogue
}
}
private async YarnTask WaitForExternalCancelAsync(CancellationToken externalToken)
private static async YarnTask WaitForExternalCancelAsync(
CancellationToken externalToken,
YarnTaskCompletionSource<DialogueOption?> optionTcs)
{
await YarnTask.WaitUntilCanceled(externalToken).SuppressCancellationThrow();
_optionTcs?.TrySetResult(null);
optionTcs.TrySetResult(null);
}
}
}