콘텐츠로 이동

테스트와 디버그

하고 싶은 것방법
로그 보기출력 로그 패널 — [내 id] …(host.Log), 격리되면 예외 내용
상태 보기플러그인 관리자 · 콘솔 plugins
고친 dll 다시 읽기플러그인 관리자 「다시 읽기(선택)」
로직만 빠르게 시험가짜 INpWorld 로 단위 테스트
중단점 걸기실행 중인 에디터에 디버거 붙이기
화면까지 자동 검사AddShotScenario(개발용 빌드 전용, 아래 제한 참고)

출력 로그는 아래 영역의 패널입니다. 닫았으면 창 메뉴에서 엽니다. 플러그인과 관련된 줄은 다음과 같습니다.

줄뜻
[<id>] …host.Log 로 남긴 글
[플러그인 <id>] <판> 켜짐 — 메뉴 1 · 빠른 추가 1불러오기 성공과 등록 요약
[플러그인] <이름> 연결 — …에디터에 붙임
[플러그인 <id>] 불러오기 실패: <예외 형식>: <내용>Register · 생성자에서 예외
[플러그인 <id>] 오류 → 격리 — <어디>: <예외 형식>: <내용>콜백에서 예외. <어디> 는 메뉴 <이름> · 명령 <이름> · Committed 처럼 나옵니다.
[플러그인 <id>] 내림 — <이유>끄기 · 다시 읽기 · 오류로 내림

격리되면 화면 아래에도 「플러그인 「<이름>」 오류로 꺼짐 — …」 알림이 나옵니다.

명령하는 일
plugins플러그인 목록: id · 판 · 상태 · 오류
plugins reload폴더를 다시 훑고 켜진 것을 불러옴(이미 켜진 것은 다시 읽지 않음)
  1. 다시 빌드합니다. 에디터는 dll 을 잠그지 않으므로 켠 채로 빌드됩니다.
  2. 새 dll(과 pdb)을 플러그인 폴더에 덮어씁니다.
  3. 플러그인 관리자에서 그 줄을 고르고 「다시 읽기(선택)」를 누릅니다.

빌드 출력 폴더를 바로 플러그인 폴더로 쓰면 2번이 필요 없습니다. 환경 변수 NP_EDITOR_PLUGINS 에 빌드 출력의 부모 폴더를 넣습니다. 그 아래 각 하위 폴더가 플러그인 하나입니다.

NP_EDITOR_PLUGINS=D:\dev\np_plugins_out
D:\dev\np_plugins_out\my_first_plugin\ ← plugin.json · MyFirstPlugin.dll · .pdb

환경 변수는 에디터를 다시 켜야 읽습니다.

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 를 부름

에디터와 똑같은 잠금 · 되돌리기 동작은 가짜 월드로 확인할 수 없습니다. 마지막에는 에디터에서 손으로 확인합니다.

  1. 플러그인을 Debug 로 빌드합니다.
  2. .dll 과 .pdb 를 함께 플러그인 폴더에 넣습니다. 에디터는 같은 폴더의 .pdb 를 함께 읽습니다.
  3. 에디터를 켜고 플러그인을 켭니다.
  4. IDE 에서 「프로세스에 연결」로 NPEditor.exe 를 고릅니다(.NET / CoreCLR 디버거).
  5. 코드에 중단점을 겁니다. 메뉴를 누르거나 명령을 실행하면 멈춥니다.
  • 중단점에서 오래 멈추면 에디터 화면도 멈춥니다. 모든 콜백이 메인 스레드에서 돌기 때문입니다.
  • Register 에 중단점을 걸려면 연결한 뒤 「다시 읽기(선택)」를 누릅니다.

화면 검증 시나리오 (AddShotScenario)

섹션 제목: “화면 검증 시나리오 (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)가 옵니다.

제한: 화면 검증 실행기는 에디터 개발용 빌드에만 들어 있습니다. 설치판에는 없습니다. 또 이 모드에서는 사용자 플러그인 폴더를 자동으로 읽지 않습니다. 그래서 지금 판의 설치판으로는 외부 플러그인의 시나리오를 돌릴 수 없습니다. 외부 플러그인은 단위 테스트와 손 확인을 씁니다.

확인왜
켜기 → 끄기 → 켜기메뉴 · 패널이 두 번 생기거나 남지 않는지
새 월드 · 다른 월드 열기WorldChanged 에서 상태를 비우는지
기능 한 번 → Ctrl+Z 한 번되돌리기 한 단계로 깔끔히 돌아가는지
선택 없음 · 빈 월드Selection · Bounds 가 null 일 때 예외가 없는지
큰 월드Committed · 살아 있는 글이 에디터를 끊지 않는지