
ShadowsOfDoubt UI Scaler
Unifies UI scale via Canvas Scaler for Shadows of Doubt. Config hot-reload, configurable hotkeys (F9 toggle, F11/F10 ±5%), and safe per-canvas application. Keeps fonts/sprites crisp; world-space optional. Requires BepInEx 6 IL2CPP.
By Khundian
Last updated | 2 months ago |
Total downloads | 257 |
Total rating | 1 |
Categories | Mods |
Dependency string | Khundian-ShadowsOfDoubt_UI_Scaler-1.0.1 |
Dependants | 0 other packages depend on this package |
This mod requires the following mods to function

BepInEx-BepInExPack_IL2CPP
BepInEx pack for IL2CPP x64 Unity games. Preconfigured and ready to use.
Preferred version: 6.0.733README
Shadows of Doubt UI Scaler
Plugin GUID: khundian.sod.uiscaler
Name: Shadows of Doubt UI Scaler
Version: 1.0.1
Description: UI scaler + configurable hotkeys.
Unifies UI scale via Canvas Scaler for Shadows of Doubt. Config hot-reload, configurable hotkeys (F9 toggle, F11/F10 ±5%), and safe per-canvas application. Keeps fonts/sprites crisp; world-space optional. Requires BepInEx 6 IL2CPP.
Features
- Global UI scaling via Canvas Scaler (
Scale With Screen Size
) with configurable reference resolution (default1920×1080
), match factor (0–1), and a multiplier (0.10×–5.00×). - Configurable hotkeys (defaults: Toggle F9, Scale Up F11, Scale Down F10). Each press adjusts by 5% and saves after a short idle.
- Hot reload of the config file. Edits on disk are debounced and re-applied safely while the game is running.
- Scene-change detection by polling (
SceneManager.GetActiveScene()
), ensuring compatibility with IL2CPP/Roslyn edge-cases (no event subscription required). - Batched layout rebuilds:
Canvas.ForceUpdateCanvases()
is called once per rescan/revert pass to avoid spikes. - Efficient discovery of canvases using
Object.FindObjectsOfType<Canvas>(true)
, with filters:ActiveSceneOnly
(affect canvases in the active scene)IncludeWorldSpace
(optional world-space canvases)- Name filters via include/exclude tokens
- Zero-alloc pruning of destroyed canvases and best‑effort pre‑sizing of internal collections for large scenes.
- Periodic rescan interval is configurable (
Engine:RescanEveryMilliseconds
, default 2000 ms). Heavier work runs on a timed tick; only input runs every frame.
Requirements
- Shadows of Doubt (Unity 2021.3)
- BepInEx 6 (IL2CPP)
Installation
Manual (zip)
-
Install BepInEx 6 (IL2CPP) for the game.
-
Copy the plugin DLL to:
<GameFolder>\BepInEx\plugins\ShadowsOfDoubt.UIScaler\ShadowsOfDoubt.UIScaler.dll
-
Launch the game once. The config file will be created at:
<GameFolder>\BepInEx\config\khundian.sod.uiscaler.cfg
Thunderstore (optional)
Zip structure:
BepInEx/
plugins/
ShadowsOfDoubt.UIScaler/
ShadowsOfDoubt.UIScaler.dll
manifest.json
README.md
LICENSE
Usage
Defaults
- Enabled:
true
- Scale:
UiScaleMultiplier = 1.00
(vanilla size) - Reference Resolution:
1920 × 1080
- Match:
0.50
(balanced width/height) - Rescan:
2000 ms
- Hotkeys:
- Toggle:
F9
(on/off) - ScaleUp:
F11
(+5%) - ScaleDown:
F10
(−5%)
- Toggle:
In-game
- Press F9 to enable/disable (when disabling, original Canvas settings are restored).
- Press F11 / F10 to adjust the global UI scale by ±5%.
- A small toast confirms the change (e.g.,
UI Scale: 1.15x
).
Configuration
Location:
<GameFolder>\BepInEx\config\khundian.sod.uiscaler.cfg
The file includes a readable header and per-setting comments. Saving the file hot-reloads and re-applies settings while the game is running—no restart needed.
Key Settings (overview)
[General]
Enabled
— master switch
[CanvasScaler]
UiScaleMultiplier
(0.10–5.00) — global UI size multiplier (1.00 = vanilla)ReferenceWidth
/ReferenceHeight
— reference resolution (default 1920×1080)MatchWidthOrHeight
(0..1) — 0 = width, 1 = height, 0.5 = balanced
[Filter]
ActiveSceneOnly
— consider canvases only from active sceneIncludeWorldSpaceCanvases
— include World Space canvases (default false)CanvasInclude
/CanvasExclude
— comma-separated name tokens; exclude wins over include
[Perf]
RescanEveryMilliseconds
— periodic re-scan to catch newly spawned canvases
[Hotkeys] (names from Unity KeyCode
)
Toggle
(defaultF9
)ScaleUp
(defaultF11
)ScaleDown
(defaultF10
)
How It Works (Technical)
- Finds canvases (including inactive) and attaches/updates a
CanvasScaler
set to Scale With Screen Size. - Applies
UiScaleMultiplier
by adjusting the reference resolution inversely (preserves pixel density). - Stores original scaler settings and reverts them when disabling the plugin.
- Watches the config file; on any save, it reloads and re-applies if still enabled.
Troubleshooting
- World-space UI breaks
World-space canvases are excluded by default. Enabling them can affect 3D interaction/depth; use with caution.
Changelog
1.0.1 — 2025-08-23
- Changed
- Scene change handling: replaced event subscription attempts with polling in
UiDriver.Tick()
usingSceneManager.GetActiveScene()
(comparename
/buildIndex
) and trigger a single rescan on change. - Batched layout rebuilds:
Canvas.ForceUpdateCanvases()
is now called once per rescan/revert pass (not per-canvas). - Discovery path: use
Object.FindObjectsOfType<Canvas>(true)
and filter by active scene, world-space inclusion, and name tokens. - Work cadence: only hotkey reads in
Update()
; heavier work (rescans, reload debounce, heartbeat) runs on a timedTick()
. - Logging prefix cleanup: removed manual
[{PluginName}]
from messages; rely on BepInEx’s[Level: Source]
prefix.
- Scene change handling: replaced event subscription attempts with polling in
- Added
- Debounced config reloads: file watcher sets a flag; main thread reloads once after ~200 ms quiet time.
- Debounced config saves: hotkey changes are persisted once after ~500 ms idle.
- Name-filter token cache: include/exclude strings are pre-split on (re)load;
NameAllowed()
uses cached arrays. - Stale-entry cleanup: each rescan prunes destroyed canvases from
_originalById
.
- Fixed / Compatibility
- Event interop issues: removed direct subscriptions to
SceneManager.activeSceneChanged
to avoid IL2CPP/Roslyn edge cases; polling replaces it. - Profiler scopes: swapped
ProfilerMarker.Auto()
forProfiler.BeginSample/EndSample
for broad IL2CPP/editor compatibility. - Float/double mismatches: consistent
float
math forMathf
anddouble
timestamps for scheduling. - Nullable warnings: initialized static refs and marked optional fields nullable; guarded UI style usage.
- Event interop issues: removed direct subscriptions to
- Optimizations
- No-alloc pruning: replaced
foreach (_originalById.ToArray())
with a reusable buffer to remove dead entries. - Best-effort pre-sizing: reflective
EnsureCapacity
for dictionaries/sets (no-op if unavailable). - Quieter logs in steady state: nonessential reload/apply lines moved to Debug (visible only when
Verbose=true
).
- No-alloc pruning: replaced
1.0.0
- Initial release
License
Copyright (c) [2025] [Khundian]. All rights reserved.
You may redistribute this mod in its original, unmodified form.
You may not decompile, reverse engineer, modify, or distribute modified versions of this mod without prior written permission from the author.
This mod may not be used for commercial purposes or included in any paid mod packs or compilations.
The mod is provided "as is," without warranty of any kind. Use at your own risk.