Mod 设置界面
RitsuLib 提供一套用于玩家可编辑值的设置 UI。它构建在 ModDataStore 之上,但不替代底层持久化模型。
这套系统适合用于暴露一部分持久化字段、按页面和分区组织设置项,并统一管理界面文案。所有设置项都需要显式注册,这一限制是有意设计。
架构分层
建议保持以下职责分离:
ModDataStore:持久化、作用域、默认值、迁移IModSettingsValueBinding<T>:UI 与存储值之间的读写桥接- 页面 / 分区构建器:页面结构、层级与排序
ModSettingsText:标签与描述的文本来源抽象
这样可以避免把运行时状态、内部元数据与玩家配置混入同一个模型。
核心 API
| API | 作用 |
|---|---|
RitsuLibFramework.RegisterModSettings(modId, configure, pageId?) | 注册设置页;省略 pageId 时默认为 modId |
RitsuLibFramework.GetRegisteredModSettings() | 返回当前所有已注册设置页 |
ModSettingsBindings.Global(...) / Profile(...) | 将控件绑定到持久化数据 |
ModSettingsBindings.InMemory(...) | 绑定到仅预览状态 |
ModSettingsText.Literal(...) | 纯文本 |
ModSettingsText.I18N(...) | 基于 I18N 的设置界面文本 |
ModSettingsText.LocString(...) | 游戏原生本地化文本 |
ModSettingsText.Dynamic(...) | 在 UI 刷新时重新求值 |
WithModDisplayName(...) | 覆盖侧栏中的 Mod 名称 |
WithSortOrder(...) | 控制同级页面排序 |
AsChildOf(parentPageId) | 将页面注册为子页 |
界面行为
- 入口:主菜单 ->
设置->General。当至少存在一个已注册页面时,RitsuLib 会注入Mod Settings (RitsuLib)入口。 - 侧栏:按 Mod 分组,同一时间只展开一个分组。
- 保存时机:绑定被标记为脏后,约
0.35s防抖保存。
最小示例
先注册持久化数据:
csharp
using STS2RitsuLib.Data;
using STS2RitsuLib.Utils.Persistence;
public sealed class MyModSettings
{
public bool EnableFancyVfx { get; set; } = true;
public double ScreenShakeScale { get; set; } = 1.0;
public MyDifficultyMode DifficultyMode { get; set; } = MyDifficultyMode.Normal;
}
using (RitsuLibFramework.BeginModDataRegistration("MyMod"))
{
var store = RitsuLibFramework.GetDataStore("MyMod");
store.Register<MyModSettings>(
key: "settings",
fileName: "settings.json",
scope: SaveScope.Global,
defaultFactory: () => new MyModSettings(),
autoCreateIfMissing: true);
}然后创建绑定并注册设置页:
csharp
using STS2RitsuLib.Settings;
var settingsLoc = RitsuLibFramework.CreateModLocalization(
modId: "MyMod",
instanceName: "MyMod-Settings",
resourceFolders: ["MyMod.Localization.Settings"]);
var fancyVfx = ModSettingsBindings.Global<MyModSettings, bool>(
"MyMod",
"settings",
model => model.EnableFancyVfx,
(model, value) => model.EnableFancyVfx = value);
var shakeScale = ModSettingsBindings.Global<MyModSettings, double>(
"MyMod",
"settings",
model => model.ScreenShakeScale,
(model, value) => model.ScreenShakeScale = value);
var difficulty = ModSettingsBindings.Global<MyModSettings, MyDifficultyMode>(
"MyMod",
"settings",
model => model.DifficultyMode,
(model, value) => model.DifficultyMode = value);
RitsuLibFramework.RegisterModSettings("MyMod", page => page
.WithModDisplayName(ModSettingsText.I18N(settingsLoc, "mod.display_name", "My Fancy Mod"))
.WithTitle(ModSettingsText.I18N(settingsLoc, "page.title", "Settings"))
.AddSection("general", section => section
.WithTitle(ModSettingsText.I18N(settingsLoc, "general.title", "General"))
.AddToggle(
"fancy_vfx",
ModSettingsText.I18N(settingsLoc, "fancy_vfx.label", "Fancy VFX"),
fancyVfx,
ModSettingsText.I18N(settingsLoc, "fancy_vfx.desc", "Enable additional visual polish."))
.AddSlider(
"screen_shake_scale",
ModSettingsText.I18N(settingsLoc, "screen_shake.label", "Screen Shake Scale"),
shakeScale,
minValue: 0.0,
maxValue: 2.0,
step: 0.05,
valueFormatter: value => $"{value:0.00}x")
.AddEnumChoice(
"difficulty_mode",
ModSettingsText.I18N(settingsLoc, "difficulty.label", "Difficulty"),
difficulty,
value => ModSettingsText.I18N(settingsLoc, $"difficulty.{value}", value.ToString()))));支持的控件类型
AddToggle(...):boolAddSlider(...):doubleAddIntSlider(...):intAddChoice(...)/AddEnumChoice(...):候选列表AddColor(...):颜色字符串AddKeyBinding(...):按键绑定字符串AddImage(...):通过Func<Texture2D?>提供图像预览AddButton(...):自定义动作按钮AddSubpage(...):跳转到已注册子页AddList(...):可排序结构化集合AddHeader(...)/AddParagraph(...):说明与结构辅助项- 可折叠分区:在分区构建器上调用
.Collapsible(startCollapsed: false)
自动镜像策略(BaseLib / ModConfig)
RitsuModSettingsSubmenu 会自动尝试镜像 BaseLib 与 ModConfig 的设置页。
当你的模组同时接入多套设置源时,可以通过程序集级 AssemblyMetadata 指令控制镜像行为。
支持的键(不区分大小写):
RitsuLib.ModSettingsMirror.Global.DisableSourcesRitsuLib.ModSettingsMirror.Global.PreferredSourceRitsuLib.ModSettingsMirror.Mod.<ModId>.DisableSourcesRitsuLib.ModSettingsMirror.Mod.<ModId>.PreferredSource
优先级(高 -> 低):Type -> Mod -> Global。
示例:
csharp
using System.Reflection;
[assembly: AssemblyMetadata("RitsuLib.ModSettingsMirror.Mod.MyMod.DisableSources", "modconfig")]
[assembly: AssemblyMetadata("RitsuLib.ModSettingsMirror.Mod.MyMod.PreferredSource", "baselib")]运行时反射协议(无库引用)
模组无需引用 STS2RitsuLib,只需在程序集元数据中显式声明 provider 类型:
xml
<ItemGroup>
<AssemblyMetadata Include="RitsuLib.ModSettingsInterop.ProviderType" Value="YourMod.Scripts.RitsuLibModSettingsInteropProvider" />
</ItemGroup>Provider 约定(全部为 static 方法):
object CreateRitsuLibSettingsSchema()object? GetRitsuLibSettingValue(string key)void SetRitsuLibSettingValue(string key, object value)- 可选:
void SaveRitsuLibSettings() - 可选:
void InvokeRitsuLibSettingAction(string key)(用于 button)
CreateRitsuLibSettingsSchema() 可以返回:
Dictionary<string, object?>(或等价对象)- JSON 字符串(根节点必须是对象)
- JSON 文件路径(内容根节点必须是对象)
JSON Schema(编辑器校验): https://raw.githubusercontent.com/BAKAOLC/STS2-RitsuLib/main/schemas/mod-settings/runtime-interop/v1/schema.json
适合暴露到设置页的内容
适合放入设置界面的内容:
- 功能开关
- 外观偏好
- 辅助功能调整项
- 玩家预期可调的玩法参数
不适合放入设置界面的内容:
- 缓存
- 迁移元数据
- 运行时镜像状态
- 纯内部实现字段