Skip to content

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(...)bool
  • AddSlider(...)double
  • AddIntSlider(...)int
  • AddChoice(...) / AddEnumChoice(...):候选列表
  • AddColor(...):颜色字符串
  • AddKeyBinding(...):按键绑定字符串
  • AddImage(...):通过 Func<Texture2D?> 提供图像预览
  • AddButton(...):自定义动作按钮
  • AddSubpage(...):跳转到已注册子页
  • AddList(...):可排序结构化集合
  • AddHeader(...) / AddParagraph(...):说明与结构辅助项
  • 可折叠分区:在分区构建器上调用 .Collapsible(startCollapsed: false)

自动镜像策略(BaseLib / ModConfig)

RitsuModSettingsSubmenu 会自动尝试镜像 BaseLibModConfig 的设置页。

当你的模组同时接入多套设置源时,可以通过程序集级 AssemblyMetadata 指令控制镜像行为。

支持的键(不区分大小写):

  • RitsuLib.ModSettingsMirror.Global.DisableSources
  • RitsuLib.ModSettingsMirror.Global.PreferredSource
  • RitsuLib.ModSettingsMirror.Mod.<ModId>.DisableSources
  • RitsuLib.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


适合暴露到设置页的内容

适合放入设置界面的内容:

  • 功能开关
  • 外观偏好
  • 辅助功能调整项
  • 玩家预期可调的玩法参数

不适合放入设置界面的内容:

  • 缓存
  • 迁移元数据
  • 运行时镜像状态
  • 纯内部实现字段