fix: 修正文字速度修改不生效的问题

This commit is contained in:
2026-07-22 21:17:16 +08:00
parent 34d2ac40eb
commit 2ae6f712a4
13 changed files with 270 additions and 30 deletions
+75
View File
@@ -0,0 +1,75 @@
using NUnit.Framework;
namespace AibisDream.SystemEditor.Tests
{
public sealed class TextSpeedSettingsTests
{
[SetUp]
public void SetUp()
{
TextSpeedSettings.Reset();
}
[TearDown]
public void TearDown()
{
TextSpeedSettings.Reset();
}
[TestCase("0.5", TextSpeedSettings.SlowMultiplier, TextSpeedSettings.SlowValue)]
[TestCase("0.50", TextSpeedSettings.SlowMultiplier, TextSpeedSettings.SlowValue)]
[TestCase("1", TextSpeedSettings.DefaultMultiplier, TextSpeedSettings.DefaultValue)]
[TestCase("1.0", TextSpeedSettings.DefaultMultiplier, TextSpeedSettings.DefaultValue)]
[TestCase("3", TextSpeedSettings.FastMultiplier, TextSpeedSettings.FastValue)]
[TestCase("3.0", TextSpeedSettings.FastMultiplier, TextSpeedSettings.FastValue)]
public void TryNormalize_SupportedNumericValue_ReturnsCanonicalValue(
string rawValue,
float expectedMultiplier,
string expectedCanonicalValue)
{
var result = TextSpeedSettings.TryNormalize(
rawValue,
out var multiplier,
out var canonicalValue);
Assert.That(result, Is.True);
Assert.That(multiplier, Is.EqualTo(expectedMultiplier));
Assert.That(canonicalValue, Is.EqualTo(expectedCanonicalValue));
}
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
[TestCase("invalid")]
[TestCase("NaN")]
[TestCase("Infinity")]
[TestCase("-1")]
[TestCase("0")]
[TestCase("2")]
[TestCase("4")]
public void TryNormalize_InvalidOrUnsupportedValue_ReturnsDefault(
string rawValue)
{
var result = TextSpeedSettings.TryNormalize(
rawValue,
out var multiplier,
out var canonicalValue);
Assert.That(result, Is.False);
Assert.That(multiplier, Is.EqualTo(TextSpeedSettings.DefaultMultiplier));
Assert.That(canonicalValue, Is.EqualTo(TextSpeedSettings.DefaultValue));
}
[Test]
public void Reset_AfterApplyingFastValue_RestoresDefault()
{
Assert.That(TextSpeedSettings.ApplyStoredValue("3"), Is.True);
Assert.That(TextSpeedSettings.CurrentMultiplier, Is.EqualTo(TextSpeedSettings.FastMultiplier));
TextSpeedSettings.Reset();
Assert.That(TextSpeedSettings.CurrentMultiplier, Is.EqualTo(TextSpeedSettings.DefaultMultiplier));
Assert.That(TextSpeedSettings.CurrentCanonicalValue, Is.EqualTo(TextSpeedSettings.DefaultValue));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58d2e4c8752446518dc1357dd586e504
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -112,11 +112,12 @@ namespace AibisDream
private void Advance() private void Advance()
{ {
EnumEventSystem.Global.Send(DialogEventEnum.LineEnd);
OnAdvance?.Invoke(); OnAdvance?.Invoke();
OnAdvance = null; OnAdvance = null;
_nextStep?.Invoke(); _nextStep?.Invoke();
state = LineSyncState.Advanced; state = LineSyncState.Advanced;
// LineEnd 表示当前行的所有推进回调已经完成,可安全替换其 UI 资源。
EnumEventSystem.Global.Send(DialogEventEnum.LineEnd);
_completion.TrySetResult(true); _completion.TrySetResult(true);
} }
@@ -2,7 +2,6 @@
using AibisDream.UI; using AibisDream.UI;
using AibisDream.Framework; using AibisDream.Framework;
using System; using System;
using System.Globalization;
using TMPro; using TMPro;
namespace AibisDream.FixSystem namespace AibisDream.FixSystem
@@ -52,7 +51,7 @@ namespace AibisDream.FixSystem
title.overrideColorTags = true; title.overrideColorTags = true;
title.color = isWarning ? WarningRedColor : AmberColor; title.color = isWarning ? WarningRedColor : AmberColor;
title.text = token.lineInfo.character.GetActorName(); title.text = token.lineInfo.character.GetActorName();
logText.SetTypewriterSpeed(GetTextSpeedValue()); logText.SetTypewriterSpeed(TextSpeedSettings.CurrentMultiplier);
token.TextStart(); token.TextStart();
logText.AddText(token.lineInfo.lineText); logText.AddText(token.lineInfo.lineText);
} }
@@ -62,12 +61,6 @@ namespace AibisDream.FixSystem
} }
} }
private float GetTextSpeedValue()
{
var textSpeedStr = SettingLoader.Instance.TryRead("TextSpeed", out string textSpeed) ? textSpeed : "1";
return float.TryParse(textSpeedStr, NumberStyles.Float, CultureInfo.InvariantCulture, out float result) ? result : 1f;
}
public void ClearScreen() public void ClearScreen()
{ {
logText.Clear(); logText.Clear();
+6 -1
View File
@@ -21,6 +21,8 @@ namespace AibisDream
private SettingLoader() private SettingLoader()
{ {
// Enter Play Mode 关闭 Domain Reload 时静态状态可能保留,加载配置前先恢复默认值。
TextSpeedSettings.Reset();
// 初始化设置文件(如果不存在) // 初始化设置文件(如果不存在)
InitSettingFile(); InitSettingFile();
// 读取数据 // 读取数据
@@ -66,7 +68,10 @@ namespace AibisDream
AudioManager.Instance.SetVolume(float.Parse(value, CultureInfo.InvariantCulture)); AudioManager.Instance.SetVolume(float.Parse(value, CultureInfo.InvariantCulture));
break; break;
case "TextSpeed": case "TextSpeed":
EnumEventSystem.Global.Send(SettingChangeEvent.TextSpeed, value); TextSpeedSettings.ApplyStoredValue(value);
EnumEventSystem.Global.Send(
SettingChangeEvent.TextSpeed,
TextSpeedSettings.CurrentCanonicalValue);
break; break;
case "Resolution": case "Resolution":
// TODO 调整分辨率 // TODO 调整分辨率
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
namespace AibisDream
{
/// <summary>
/// 文字速度设置的运行时快照与规范化规则。
/// 各文字视图在开始新行时读取 CurrentMultiplier,确保设置不会改变正在播放的行。
/// </summary>
public static class TextSpeedSettings
{
public const float SlowMultiplier = 0.5f;
public const float DefaultMultiplier = 1f;
public const float FastMultiplier = 3f;
public const string SlowValue = "0.5";
public const string DefaultValue = "1.0";
public const string FastValue = "3.0";
private static readonly HashSet<string> WarnedInvalidValues = new(StringComparer.Ordinal);
public static float CurrentMultiplier { get; private set; } = DefaultMultiplier;
public static string CurrentCanonicalValue { get; private set; } = DefaultValue;
public static void Reset()
{
CurrentMultiplier = DefaultMultiplier;
CurrentCanonicalValue = DefaultValue;
WarnedInvalidValues.Clear();
}
public static bool ApplyStoredValue(string value)
{
var isValid = TryNormalize(value, out var multiplier, out var canonicalValue);
CurrentMultiplier = multiplier;
CurrentCanonicalValue = canonicalValue;
if (!isValid)
{
WarnInvalidValueOnce(value);
}
return isValid;
}
public static bool TryNormalize(string value, out float multiplier, out string canonicalValue)
{
multiplier = DefaultMultiplier;
canonicalValue = DefaultValue;
if (string.IsNullOrWhiteSpace(value) ||
!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed) ||
float.IsNaN(parsed) ||
float.IsInfinity(parsed))
{
return false;
}
if (Mathf.Approximately(parsed, SlowMultiplier))
{
multiplier = SlowMultiplier;
canonicalValue = SlowValue;
return true;
}
if (Mathf.Approximately(parsed, DefaultMultiplier))
{
multiplier = DefaultMultiplier;
canonicalValue = DefaultValue;
return true;
}
if (Mathf.Approximately(parsed, FastMultiplier))
{
multiplier = FastMultiplier;
canonicalValue = FastValue;
return true;
}
return false;
}
private static void WarnInvalidValueOnce(string value)
{
var warningKey = value ?? "<null>";
if (!WarnedInvalidValues.Add(warningKey))
{
return;
}
Debug.LogWarning(
$"[TextSpeedSettings] 不支持的文字速度 '{warningKey}',本次运行使用默认值 {DefaultValue}。");
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7bc89b836ad94a929a395d48a94f17f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -248,6 +248,7 @@ namespace AibisDream.UI
{ {
if (typewriter != null) if (typewriter != null)
{ {
typewriter.resetTypingSpeedAtStartup = false;
typewriter.SetTypewriterSpeed(speed); typewriter.SetTypewriterSpeed(speed);
} }
} }
+7 -12
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Globalization;
using AibisDream.Framework; using AibisDream.Framework;
using AibisDream.Kit; using AibisDream.Kit;
using AibisDream.Utility; using AibisDream.Utility;
@@ -60,7 +59,6 @@ namespace AibisDream
SetStyle(data.bubbleStyle, data.pivotType); SetStyle(data.bubbleStyle, data.pivotType);
// 注册事件 // 注册事件
_typewriter.onTextShowed.AddListener(InvokeLineShown); _typewriter.onTextShowed.AddListener(InvokeLineShown);
EnumEventSystem.Global.Register<SettingChangeEvent, string>(SettingChangeEvent.TextSpeed, OnTextSpeedChanged);
} }
private void InvokeLineShown() private void InvokeLineShown()
@@ -88,16 +86,6 @@ namespace AibisDream
: $"{bubbleStyle.bubbleSpriteAddress}[{pivotType}]"; : $"{bubbleStyle.bubbleSpriteAddress}[{pivotType}]";
_bubbleImage.sprite = ResourceKit.LoadAssetSync<Sprite>(spritePath); _bubbleImage.sprite = ResourceKit.LoadAssetSync<Sprite>(spritePath);
// 初始化文字速度
var speedStr = SettingLoader.Instance.TryRead("TextSpeed", out string textSpeed) ? textSpeed : "1";
OnTextSpeedChanged(speedStr);
}
private void OnTextSpeedChanged(string value)
{
float speed = float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float result) ? result : 1f;
_typewriter.SetTypewriterSpeed(speed);
} }
private void InitRefs() private void InitRefs()
@@ -135,9 +123,16 @@ namespace AibisDream
public void ShowLine(string line) public void ShowLine(string line)
{ {
HandleLayout(line); HandleLayout(line);
ApplyTextSpeedSnapshot();
_typewriter.ShowText(line); _typewriter.ShowText(line);
} }
private void ApplyTextSpeedSnapshot()
{
_typewriter.resetTypingSpeedAtStartup = false;
_typewriter.SetTypewriterSpeed(TextSpeedSettings.CurrentMultiplier);
}
public bool SkipLine() public bool SkipLine()
{ {
if (_typewriter.isShowingText) if (_typewriter.isShowingText)
+7
View File
@@ -38,10 +38,17 @@ namespace AibisDream
syncToken.OnAdvance += AfterAdvance; syncToken.OnAdvance += AfterAdvance;
// 播放文字 // 播放文字
ApplyTextSpeedSnapshot();
syncToken.TextStart(); syncToken.TextStart();
content.ShowText(syncToken.lineInfo.lineText); content.ShowText(syncToken.lineInfo.lineText);
} }
private void ApplyTextSpeedSnapshot()
{
content.resetTypingSpeedAtStartup = false;
content.SetTypewriterSpeed(TextSpeedSettings.CurrentMultiplier);
}
private async void AfterAdvance() private async void AfterAdvance()
{ {
_autoHideToken = new CancellationTokenSource(); _autoHideToken = new CancellationTokenSource();
@@ -13,6 +13,7 @@ namespace AibisDream
[SerializeField] private TMP_Text widthEstimation; [SerializeField] private TMP_Text widthEstimation;
private CanvasGroup _canvasGroup; private CanvasGroup _canvasGroup;
private BubbleSlotGroupData? _pendingBubbleData;
public override void OnSingletonInit() public override void OnSingletonInit()
{ {
@@ -25,19 +26,24 @@ namespace AibisDream
EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(DialogEventEnum.LineStart, AddDialogueLine); EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(DialogEventEnum.LineStart, AddDialogueLine);
EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(DialogEventEnum.LineHistoryAppend, AddDialogueLine); EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(DialogEventEnum.LineHistoryAppend, AddDialogueLine);
EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(DialogEventEnum.OptionSelected, AddDialogueLine); EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(DialogEventEnum.OptionSelected, AddDialogueLine);
EnumEventSystem.Global.Register(DialogEventEnum.LineEnd, ApplyPendingBubbleLoad);
EnumEventSystem.Global.Register(EventEnum.NextYarn, CleanDialogHistory); EnumEventSystem.Global.Register(EventEnum.NextYarn, CleanDialogHistory);
EnumEventSystem.Global.Register(GameLifecycleEvent.SessionEnded, CleanDialogHistory); EnumEventSystem.Global.Register(GameLifecycleEvent.SessionEnded, CleanDialogHistory);
EnumEventSystem.Global.Register(GameLifecycleEvent.SessionEnded, ClearPendingBubbleLoad);
EnumEventSystem.Global.Register(GameLifecycleEvent.SessionPaused, CloseCanvas); EnumEventSystem.Global.Register(GameLifecycleEvent.SessionPaused, CloseCanvas);
EnumEventSystem.Global.Register(GameLifecycleEvent.SessionResumed, OpenCanvas); EnumEventSystem.Global.Register(GameLifecycleEvent.SessionResumed, OpenCanvas);
} }
public override void OnSingletonDestroy() public override void OnSingletonDestroy()
{ {
ClearPendingBubbleLoad();
EnumEventSystem.Global.UnRegister<DialogEventEnum, LineInfo>(DialogEventEnum.LineStart, AddDialogueLine); EnumEventSystem.Global.UnRegister<DialogEventEnum, LineInfo>(DialogEventEnum.LineStart, AddDialogueLine);
EnumEventSystem.Global.UnRegister<DialogEventEnum, LineInfo>(DialogEventEnum.LineHistoryAppend, AddDialogueLine); EnumEventSystem.Global.UnRegister<DialogEventEnum, LineInfo>(DialogEventEnum.LineHistoryAppend, AddDialogueLine);
EnumEventSystem.Global.UnRegister<DialogEventEnum, LineInfo>(DialogEventEnum.OptionSelected, AddDialogueLine); EnumEventSystem.Global.UnRegister<DialogEventEnum, LineInfo>(DialogEventEnum.OptionSelected, AddDialogueLine);
EnumEventSystem.Global.UnRegister(DialogEventEnum.LineEnd, ApplyPendingBubbleLoad);
EnumEventSystem.Global.UnRegister(EventEnum.NextYarn, CleanDialogHistory); EnumEventSystem.Global.UnRegister(EventEnum.NextYarn, CleanDialogHistory);
EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionEnded, CleanDialogHistory); EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionEnded, CleanDialogHistory);
EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionEnded, ClearPendingBubbleLoad);
EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionPaused, CloseCanvas); EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionPaused, CloseCanvas);
EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionResumed, OpenCanvas); EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionResumed, OpenCanvas);
} }
@@ -99,11 +105,45 @@ namespace AibisDream
} }
public void LoadBubbles(BubbleSlotGroupData data) public void LoadBubbles(BubbleSlotGroupData data)
{
var token = DialogController.Instance != null
? DialogController.Instance.lineSyncToken
: null;
if (token != null && token.state != LineSyncState.Advanced)
{
// 同一行内的连续请求采用最后一次请求,当前气泡保留到台词推进。
_pendingBubbleData = data;
return;
}
_pendingBubbleData = null;
ApplyBubbleLoadImmediately(data);
}
private void ApplyPendingBubbleLoad()
{
if (!_pendingBubbleData.HasValue)
{
return;
}
var data = _pendingBubbleData.Value;
_pendingBubbleData = null;
ApplyBubbleLoadImmediately(data);
}
private void ApplyBubbleLoadImmediately(BubbleSlotGroupData data)
{ {
lineView.LoadBubbleData(data); lineView.LoadBubbleData(data);
optionView.LoadBubbles(data); optionView.LoadBubbles(data);
} }
private void ClearPendingBubbleLoad()
{
_pendingBubbleData = null;
}
public float EstimateTextWidth(string text, float fontSize) public float EstimateTextWidth(string text, float fontSize)
{ {
widthEstimation.fontSize = fontSize; widthEstimation.fontSize = fontSize;
+8 -1
View File
@@ -46,6 +46,7 @@ namespace AibisDream
var lineText = GetOneLineText(syncToken.lineInfo); var lineText = GetOneLineText(syncToken.lineInfo);
HandleLayout(lineText); HandleLayout(lineText);
ApplyTextSpeedSnapshot();
// 注册事件(UI 就绪后再挂回调) // 注册事件(UI 就绪后再挂回调)
OnTextShowed += syncToken.TextShown; OnTextShowed += syncToken.TextShown;
@@ -56,6 +57,12 @@ namespace AibisDream
content.ShowText(lineText); content.ShowText(lineText);
} }
private void ApplyTextSpeedSnapshot()
{
content.resetTypingSpeedAtStartup = false;
content.SetTypewriterSpeed(TextSpeedSettings.CurrentMultiplier);
}
private string GetOneLineText(LineInfo lineInfo) private string GetOneLineText(LineInfo lineInfo)
{ {
if (string.IsNullOrEmpty(lineInfo.character.GetActorName())) if (string.IsNullOrEmpty(lineInfo.character.GetActorName()))
@@ -172,4 +179,4 @@ namespace AibisDream
HideDialog(); HideDialog();
} }
} }
} }
@@ -40,9 +40,9 @@ namespace AibisDream.UI
private static readonly UIFormOption[] TextSpeedOptions = private static readonly UIFormOption[] TextSpeedOptions =
{ {
new UIFormOption { prop = "0.5", label = "setting_textSpeed_slow" }, new UIFormOption { prop = TextSpeedSettings.SlowValue, label = "setting_textSpeed_slow" },
new UIFormOption { prop = "1.0", label = "setting_textSpeed_default" }, new UIFormOption { prop = TextSpeedSettings.DefaultValue, label = "setting_textSpeed_default" },
new UIFormOption { prop = "3.0", label = "setting_textSpeed_fast" }, new UIFormOption { prop = TextSpeedSettings.FastValue, label = "setting_textSpeed_fast" },
}; };
public void SetOwner(ITerminalPanelHost owner) public void SetOwner(ITerminalPanelHost owner)
@@ -134,10 +134,7 @@ namespace AibisDream.UI
volumeSlider?.SetValue(volume); volumeSlider?.SetValue(volume);
} }
if (settings.TryGetValue("TextSpeed", out var textSpeed)) textSpeedOption?.SetValue(TextSpeedSettings.CurrentCanonicalValue);
{
textSpeedOption?.SetValue(textSpeed);
}
if (settings.TryGetValue("WindowMode", out var windowMode)) if (settings.TryGetValue("WindowMode", out var windowMode))
{ {