剧情+对话完善
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user