Note: This document was generated by AI.
UnityModBase is an infrastructure library for Unity mods. It provides a shared lifecycle, per-mod user scopes, persistent configuration, in-memory and file logging, bilingual text, hotkeys, and IMGUI tool windows.
The repository includes a BepInEx launcher. When it is used, the framework initializes as the launcher loads, dispatches game-boot extensions after the first scene is loaded, and releases all resources when the launcher is destroyed.
- Process initialization, game boot, frame update, and quit events
- Attribute-based registration of startup methods and persistent
Componenttypes - Per-mod user contexts with owned child-context lifecycles
- Strongly typed configuration, automatic saving, transactional reload, and custom value codecs
- Bounded in-memory logs, duplicate merging, and hourly file logs
- Keyboard and gamepad hotkeys with alternative chords
- Chinese/English text with runtime language switching
- Automatically generated configuration, log, and live-control IMGUI windows
- Replaceable Unity and IMGUI providers for testing UI and input logic
- .NET Framework 4.7.2
- BepInEx, only for
UnityModBase.BepInExLauncher
When using the bundled BepInEx launcher, place these files in the game's BepInEx/plugins directory:
UnityModBase.dll
UnityModBase.BepInExLauncher.dll
Mod projects should reference UnityModBase.dll. A mod that directly uses hotkey types must also reference the target game's Unity.InputSystem.dll.
The bundled launcher provides these default actions:
| Hotkey | Action |
|---|---|
F1 |
Open the configuration window |
F2 |
Open the log window |
F3 |
Open the live-control window |
Ctrl+R |
Reload configuration from disk |
These hotkeys and the UI language can be changed in UnityModBase's own configuration file.
The following example registers a mod user at the game-boot extension point, then creates configuration, logging, and live-control entries. The bundled BepInEx launcher already owns global framework initialization and boot dispatch, so a mod must not call UnityModBase.Initialize or GameBootRegistry.Boot again.
using System.IO;
using BepInEx;
using UnityEngine;
using UnityModBase.HClassAttribute;
using UnityModBase.HConfigSpace;
using UnityModBase.HControlSpace;
using UnityModBase.HLogSpace;
using UnityModBase.HTranslatorSpace;
using UnityModBase.HUserSpace;
internal static class DemoMod
{
internal static UserContext Context { get; private set; }
[InitializeOnGameBoot]
private static void Initialize()
{
Context = UserManager.Register(
"com.example.demomod",
new Translator("示例模组", "Demo Mod"));
string dataDirectory = Path.Combine(Paths.ConfigPath, "DemoMod");
string configPath = Path.Combine(dataDirectory, "DemoMod.cfg");
string logDirectory = Path.Combine(dataDirectory, "logs");
Context.Service.RegisterConfig<DemoConfig>(configPath);
Context.Service.RegisterLog(logDirectory, "DemoMod", LogLevel.Info);
DemoConfig.Initialize(Context.Service.Config);
DemoControls.Initialize(Context.Service.Control);
Context.Service.LogDatabase.Info(
"Demo Mod initialized.",
nameof(Initialize),
string.Empty,
0);
}
}
internal static class DemoConfig
{
public static ConfigEntry<bool> Enabled { get; private set; }
[EntrySlider(0.25f, 3f, 0.25f)]
public static ConfigEntry<float> Speed { get; private set; }
internal static void Initialize(ConfigService config)
{
config.SaveOnConfigSet = false;
try
{
config.CreateTable(
"General",
new Translator("通用", "General"));
Enabled = config.Bind(
"General",
"Enabled",
true,
new Translator("启用", "Enabled"));
Speed = config.Bind(
"General",
"Speed",
1f,
new Translator("速度", "Speed"),
new Translator("运行速度倍率。", "Runtime speed multiplier."));
}
finally
{
config.SaveOnConfigSet = true;
}
config.Save();
}
}
internal static class DemoControls
{
internal static void Initialize(ControlService control)
{
control.CreateTable(
"Runtime",
new Translator("运行时", "Runtime"));
ControlEntry<float> timeScale = control.Bind(
"Runtime",
"TimeScale",
() => Time.timeScale,
ControlUpdatePolicy.WhenVisibleEveryFrame,
new Translator("时间倍率", "Time Scale"));
timeScale.OnValueChanged += (sender, value) => Time.timeScale = value;
}
}The configuration GUI uses the registered configuration-manager type to discover EntrySlider and other metadata on static configuration properties. Store bound entries in static properties declared on that type.
Without the bundled BepInEx launcher, the host must drive the framework lifecycle in this order:
UnityModBase.UnityModBase.Initialize(dataDirectory);
// Call once after the game can safely create Unity objects.
GameBootRegistry.Boot();
// Call when the host unloads or the process exits.
UnityModBase.UnityModBase.Dispose();Initialize only establishes base services and scans startup extensions; it does not mean the game has finished booting. Boot creates persistent components marked with RegisterOnGameBoot and invokes static methods marked with InitializeOnGameBoot. Startup and disposal operations that touch Unity objects must run on the Unity main thread.
- Single key:
F1,Space,Tab - Keyboard chord:
Ctrl+Shift+F - Side-specific modifiers:
LeftCtrl+RightShift+F - Gamepad chord:
GamepadStart+GamepadA - Alternative chords:
Ctrl+F,GamepadStart+GamepadA
A single chord cannot mix keyboard and gamepad input. Use the Gamepad prefix for gamepad buttons to avoid ambiguous names.
UnityModBase.InitializeandUnityModBase.Disposetolerate repeated calls and serialize top-level lifecycle transitions.GameBootRegistry.Bootruns only once per initialization cycle.UserManager,ConfigService,ControlService, and most GUI models do not provide complete thread safety. Use them serially on a controlled thread.FrameUpdateManager.OnFrameUpdate, GUI rendering, input queries, and Unity object creation or destruction must run on the Unity main thread.- Static event subscribers should unsubscribe when their own lifetime ends. Global framework disposal clears most process-level subscriptions, but it does not replace cleanup by shorter-lived components.