17 – Dev 控制台:SusConsoleService, SusConsoleDriver
服务:
SusConsoleService(sus-core/Runtime/Services/) 驱动:SusConsoleDriver(sus-core/Runtime/Services/)
1. 用途
一个游戏内 dev 控制台:拦截 Unity 日志,并把它们显示在整个 UI 之上 (分类 Console = 50 — 最顶层),支持过滤/搜索,并可以执行 命令。主要使用场景是在没有 Editor Console 的设备/build 上调试。

2. 接入
用 #if 包裹,把它排除在 release 构建之外:
#if DEVELOPMENT_BUILD || UNITY_EDITOR
var overlay = SusBootstrap.GetOrCreateOverlay(uiDocument.rootVisualElement);
var console = new SusConsoleService
{
OverlayHost = overlay,
ToggleKey = KeyCode.BackQuote, // ~
MaxEntries = 500,
};
console.Attach(); // subscribes to the log, spawns the hotkey driver, sets Instance
#endifAttach() 做了四件事:
- 订阅
Application.logMessageReceivedThreaded。 - 创建(或查找)一个
SusConsoleDriver,在Update中轮询ToggleKey。 - 注册内置命令(
clear、help、filter)。 - 把实例发布为
SusConsoleService.Instance(setter 是私有的 — 不要直接给它赋值)。
Detach() 会取消日志订阅并隐藏 UI。
3. 使用
界面
按下 ~ 会从底部打开控制台(占屏幕高度的 40%),是一个深色面板:
| 元素 | 说明 |
|---|---|
| All / Log / Warn / Err 按钮 | 按类型过滤 |
| 搜索框 | 按子串过滤(不区分大小写) |
| 命令输入框 | 输入命令 → Enter → 执行 |
| 命令输入框中的 Tab | 命令名补全 |
| ✕ | 关闭控制台 |
日志按颜色区分:灰色(Log)、黄色(Warning)、红色(Error/Exception/Assert)。 新消息出现时自动向下滚动;手动向上滚动会停止自动滚动。
样式
控制台没有 inline C# 样式:整个 overlay 由 Runtime/Resources/SusRuntime/sus-console.uss 描述,服务在首次 Show() 时 将其加载到自己的 root 上。颜色来自 design token(--sus-bg-overlay、--sus-primary、 --sus-warning、--sus-error 等),因此控制台会跟随当前主题。
要重新设置样式,可以在控制台自身的 sheet 之后加载的任意 sheet 中覆盖这些类 (面板上的项目 sheet,或通过 SusBootstrap.RegisterCascadeStyleSheet):
| 类 | 元素 |
|---|---|
.sus-console | Overlay 面板(position、height、background) |
.sus-console__toolbar | 顶部一行 |
.sus-console__filter / --active | All / Log / Warn / Err 过滤 chip |
.sus-console__field + __search / __command | 文本输入框 |
.sus-console__close | ✕ 按钮 |
.sus-console__list | 承载日志行的 ScrollView |
.sus-console__line + --warning / --error | 日志行 |
.sus-console__status | 底部状态行(过滤器 + 条目数) |
sheet 中的每一个 var() 都带有 fallback 值,因此即使控制台运行在从未经过 SusBootstrap、没有 token 级联的裸面板上,外观依然正确。
注册命令
#if DEVELOPMENT_BUILD || UNITY_EDITOR
SusConsoleService.Instance.RegisterCommand("spawn", args =>
{
if (args.Length > 0)
SpawnUnit(args[0]);
else
Debug.Log("Usage: spawn <unitId>");
}, help: "Spawn a unit by id");
SusConsoleService.Instance.RegisterCommand("gold", args =>
{
int amount = args.Length > 0 ? int.Parse(args[0]) : 1000;
player.Gold += amount;
Debug.Log($"Added {amount} gold. Total: {player.Gold}");
}, help: "Add gold (default 1000)");
#endif内置命令(在 Attach 中自动注册):
| 命令 | 说明 |
|---|---|
clear | 清空日志缓冲区 |
help | 列出所有命令 |
filter <all|log|warn|error> | 切换过滤器 |
4. 架构
线程安全
Application.logMessageReceivedThreaded 可能从后台线程调用。 条目会在 lock 保护下追加到 Queue<SusLogEntry>,SusConsoleDriver.Update() 在主线程调用 DrainPendingEntries() — 它把记录搬进环形 缓冲区并更新 UI。
环形缓冲区
溢出时(MaxEntries,默认 500),旧条目会被淘汰:
if (_buffer.Count >= MaxEntries)
_buffer.RemoveAt(0);
_buffer.Add(entry);惰性构建 UI
UI(_root、_scrollView、toolbar、命令输入框)在首次 Show() 时才构建。 关闭状态下,控制台不会创建任何 VisualElement — 零渲染开销。
Z-order
OverlayCategory.Console = 50 — screen-space OverlayHost 的最顶层。 控制台始终位于 modal、tooltip、dropdown 和 toast 之上。
World-space UI(血条)不属于这个层级栈:它使用一个独立的面板, 位于屏幕之下。参见 07-overlayhost.md。
World-space panel ← UNDER screens (not OverlayHost)
└── healthbar / nameplate / …
Screen UIDocument
├── SusRouteView ← screens
└── OverlayHost
├── [Transition = 10]
├── [Modal = 20]
├── [Tooltip = 30] ← above modals
├── [Dropdown = 40]
├── [Toast = 45]
└── [Console = 50] ← console here (topmost)5. API
SusConsoleService
| 方法/属性 | 类型 | 说明 |
|---|---|---|
Attach() | void | 日志订阅 + hotkey 驱动 |
Detach() | void | 取消订阅 + 关闭 |
Show() | void | 显示控制台 |
Hide() | void | 隐藏控制台 |
Toggle() | void | 切换显示/隐藏 |
Clear() | void | 清空缓冲区 |
DrainPendingEntries() | void | 把记录从队列搬进缓冲区(主线程) |
SetFilter(filter) | void | ConsoleFilter.All/Log/Warning/Error |
SetSearch(text) | void | 按子串过滤 |
RegisterCommand(name, handler, help) | void | 注册一个命令 |
ExecuteCommand(input) | bool | 执行一行命令 |
IsOpen | bool | 控制台是否已打开 |
OverlayHost | OverlayHost | Portal 容器 |
ToggleKey | KeyCode | 热键(默认 ~) |
MaxEntries | int | 环形缓冲区大小(500) |
Instance | static SusConsoleService | 单例 |
SusConsoleDriver
public class SusConsoleDriver : MonoBehaviour
{
public SusConsoleService Service;
private void Update()
{
if (Input.GetKeyDown(Service.ToggleKey))
Service.Toggle();
Service.DrainPendingEntries();
}
}SusLogEntry
叠加控制台的环形缓冲行(SusConsoleService 在拦截 Unity 日志后存储的内容)。不是进程级 gated logger SusLog / SusLogLevel — 见 11-api-reference。
public struct SusLogEntry
{
public LogType Type; // Log, Warning, Error, Exception, Assert
public string Message; // Text
public string StackTrace; // Stack (for errors)
public float Time; // Time.unscaledTime
}ConsoleFilter
public enum ConsoleFilter { All, Log, Warning, Error }6. 测试
| 文件 | 测试数 | 覆盖内容 |
|---|---|---|
sus-core/Runtime/Tests/SusConsoleServiceTests.cs | 10 playmode | Show/Hide/Toggle、日志拦截、缓冲区溢出、过滤、搜索、RegisterCommand/ExecuteCommand、Clear |