测试与调试
| 想做的事 | 方法 |
|---|---|
| 查看日志 | 输出日志面板 — [我的 id] …(host.Log),被隔离时显示异常内容 |
| 查看状态 | 插件管理器 · 控制台 plugins |
| 重新加载修改后的 dll | 插件管理器「重新加载 (所选)」 |
| 快速测试逻辑 | 用伪 INpWorld 做单元测试 |
| 设置断点 | 将调试器附加到正在运行的编辑器 |
| 连画面一起自动检查 | AddShotScenario(仅限开发用构建,参见下方限制) |
输出日志是下方区域的面板。若已关闭,请从「窗口」菜单打开。与插件相关的行如下。
| 行 | 含义 |
|---|---|
[<id>] … | 通过 host.Log 记录的文字 |
[插件 <id>] <版本> 已启用 — 메뉴 1 · 빠른 추가 1 | 加载成功及注册摘要 |
[插件] <名称> 已挂载 — … | 已挂接到编辑器 |
[插件 <id>] 加载失败: <异常类型>: <内容> | Register · 构造函数中发生异常 |
[插件 <id>] 错误 → 已隔离 — <位置>: <异常类型>: <内容> | 回调中发生异常。<位置> 显示为 菜单 <名称> · 命令 <名称> · Committed 等。 |
[插件 <id>] 已卸载 — <原因> | 因禁用 · 重新加载 · 错误而卸载 |
被隔离时,画面下方也会出现「插件“<名称>”因错误已禁用 — …」通知。
| 命令 | 作用 |
|---|---|
plugins | 插件列表:id · 版本 · 状态 · 错误 |
plugins reload | 重新扫描文件夹并加载已启用的插件(已启用的不会重新加载) |
修改并重新加载
Section titled “修改并重新加载”- 重新构建。编辑器不锁定 dll,因此可以在编辑器运行时构建。
- 用新的 dll(和 pdb)覆盖插件文件夹中的文件。
- 在插件管理器中选中该行,点击「重新加载 (所选)」。
若直接把构建输出文件夹用作插件文件夹,就不需要第 2 步。在环境变量 NP_EDITOR_PLUGINS 中填入构建输出的父文件夹。其下每个子文件夹就是一个插件。
NP_EDITOR_PLUGINS=D:\dev\np_plugins_outD:\dev\np_plugins_out\my_first_plugin\ ← plugin.json · MyFirstPlugin.dll · .pdb环境变量需要重新启动编辑器才会读取。
单元测试(伪 INpWorld)
Section titled “单元测试(伪 INpWorld)”SDK 只有接口,因此可以用一个模拟 INpWorld 的小类,在没有编辑器的情况下测试插件逻辑。把生成方块的代码提取为接收 INpWorld 的函数。
using NP.Editor.Sdk;
/// <summary>测试用伪世界:格 → 方块状态。</summary>public sealed class FakeWorld : INpWorld{ private readonly Dictionary<NpCell, string> _b = new(); public string? FilePath => null; public NpBox? Selection { get; set; } public NpBox? Bounds => null; public string GetBlock(int x, int y, int z) => _b.TryGetValue(new NpCell(x, y, z), out var s) ? s : "minecraft:air"; public bool IsAir(int x, int y, int z) => !_b.ContainsKey(new NpCell(x, y, z)); public IEnumerable<NpBlock> Blocks(NpBox? box = null) => _b.Where(kv => box is not { } bb || bb.Contains(kv.Key.X, kv.Key.Y, kv.Key.Z)) .Select(kv => new NpBlock(kv.Key.X, kv.Key.Y, kv.Key.Z, kv.Value)); public long CountBlocks() => _b.Count; public int Edit(string label, Action<INpEdit> build) { var e = new Ed(_b); build(e); return e.Changed; }
private sealed class Ed : INpEdit { private readonly Dictionary<NpCell, string> _b; public int Changed; public Ed(Dictionary<NpCell, string> b) => _b = b; public void Set(int x, int y, int z, string state) { var c = new NpCell(x, y, z); if (state == "minecraft:air") { if (_b.Remove(c)) Changed++; return; } if (!_b.TryGetValue(c, out var old) || old != state) { _b[c] = state; Changed++; } } public void Clear(int x, int y, int z) => Set(x, y, z, "minecraft:air"); }}
// 要测试的代码(插件内)public static class PillarBuilder{ public static int Build(INpWorld w, NpCell at) => w.Edit("3×3 기둥", e => { for (int y = 0; y < 6; y++) for (int x = 0; x < 3; x++) for (int z = 0; z < 3; z++) e.Set(at.X + x, at.Y + y, at.Z + z, "minecraft:stone_bricks"); });}xUnit 测试示例如下。
public class PillarTests{ [Fact] public void Pillar_is_3x3x6() { var w = new FakeWorld(); int n = PillarBuilder.Build(w, new NpCell(0, 1, 0)); Assert.Equal(54, n); Assert.Equal("minecraft:stone_bricks", w.GetBlock(2, 6, 2)); Assert.True(w.IsAir(0, 7, 0)); }}| 容易测试的内容 | 方法 |
|---|---|
| 放置方块的计算 | 对伪 INpWorld 执行 Edit 后检查 GetBlock |
| 命令结果字符串 | 检查 NpCommand.Run(new[] { "6" }) 的返回值 |
| CSV 之类的导出 | 用临时路径调用 NpFileFormat.Run(path) 并检查文件 |
| 世界逻辑预览 | 直接创建 new NpSceneSim(...) 并调用 Simulate |
与编辑器完全相同的锁定 · 撤销行为无法用伪世界确认。最后请在编辑器中手动确认。
- 以 Debug 构建插件。
- 将
.dll和.pdb一起放入插件文件夹。编辑器会一并读取同一文件夹中的.pdb。 - 启动编辑器并启用插件。
- 在 IDE 中用「附加到进程」选择
NPEditor.exe(.NET / CoreCLR 调试器)。 - 在代码中设置断点。点击菜单或执行命令时即会停下。
- 在断点处停留过久,编辑器画面也会停住。因为所有回调都在主线程上运行。
- 若要在
Register中设置断点,请在附加后点击「重新加载 (所选)」。
画面验证场景(AddShotScenario)
Section titled “画面验证场景(AddShotScenario)”AddShotScenario 为编辑器画面验证运行器注册场景。场景会更改世界、放置相机,并留下判定结果和截图。
host.AddShotScenario(new NpShot("myroad_smoke", async api =>{ api.World.Edit("시험", e => e.Set(0, 1, 0, "minecraft:stone")); api.LookAt(6, 6, -6, 0, 1, 0); // 从眼睛 (6,6,-6) 看向 (0,1,0) await api.Frames(5); // 等待 5 帧 api.Check(api.World.GetBlock(0, 1, 0) == "minecraft:stone", "돌 놓임"); await api.Shot("smoke"); // 保存 PNG}));运行器命令行如下。
NPEditor.exe -- --np-shot=myroad_smoke --np-out=<结果文件夹> --np-quit| 参数 | 含义 |
|---|---|
--np-shot=<id>[,<id>…] | 要运行的场景 |
--np-out=<文件夹> | 写入 PNG 和结果文件(shot.json — 各场景的通过/失败)的文件夹 |
--np-quit | 结束后关闭编辑器 |
- 场景中发生异常时,该判定为失败,插件被隔离。
- 场景开始时会收到
WorldChanged(Reason = ShotScenario)。
限制:画面验证运行器只包含在编辑器开发用构建中,安装版中没有。此外,该模式下不会自动读取用户插件文件夹。因此当前版本的安装版无法运行外部插件的场景。外部插件请使用单元测试和手动确认。
手动确认清单
Section titled “手动确认清单”| 确认 | 原因 |
|---|---|
| 启用 → 禁用 → 启用 | 菜单 · 面板是否重复生成或残留 |
| 新建世界 · 打开其他世界 | 是否在 WorldChanged 中清空状态 |
| 执行一次功能 → 按一次 Ctrl+Z | 是否能以一步撤销干净地恢复 |
| 无选择 · 空世界 | Selection · Bounds 为 null 时是否没有异常 |
| 大型世界 | Committed · 动态文字是否不会卡住编辑器 |