#if UNITY_EDITOR || DEVELOPMENT_BUILD using Unity.Pipeline.Commands; using UnityEngine; using UnityEngine.SceneManagement; using WL.Player; namespace WL.Diagnostics { /// /// WL 전용 Pipeline 커스텀 명령 — Unity CLI 에서 unity command wl_* 로 호출한다. /// 에디터·개발 빌드 전용(릴리즈 빌드에서는 컴파일되지 않음). 등록 코드는 없다: /// static 메서드에 [CliCommand] 만 붙이면 리컴파일 후 자동으로 명령 목록에 뜬다. /// 인자는 [CliArg("이름", "설명")] — CLI 에서 --이름 값 으로 넘긴다. /// 규칙: 파괴적 동작은 confirm/dry_run 인자로 게이트 · 게임 데이터 값은 상수로 두지 않는다(C45 — 설정 에셋을 읽는다). /// 문서: Library/PackageCache/com.unity.pipeline@*/Documentation~/creating-commands.md /// internal static class WLPipelineCommands { [CliCommand("wl_info", "WL 런타임 요약: 씬 · Play 상태 · timeScale · FPS · 플레이어(이름/위치/HP/상태) · 적 수(생존/전체)", Tags = new[] { "wl" })] public static object Info() { var scene = SceneManager.GetActiveScene(); var switcher = Object.FindFirstObjectByType(); var pc = switcher != null ? switcher.Current : Object.FindFirstObjectByType(); int alive = 0; foreach (var e in Enemy.All) if (e != null && e.IsAlive) alive++; return new { scene = scene.name, isPlaying = Application.isPlaying, timeScale = Time.timeScale, fps = Application.isPlaying && Time.unscaledDeltaTime > 0f ? 1f / Time.unscaledDeltaTime : 0f, player = pc == null ? null : new { name = switcher != null ? switcher.CurrentDisplayName : pc.name, index = switcher != null ? switcher.CurrentIndex : -1, position = Vec(pc.transform.position), hp = pc.CurrentHp, maxHp = pc.MaxHp, wakeState = pc.CurrentWakeState.ToString(), actState = pc.CurrentActState.ToString(), speed = pc.CurrentSpeed, target = pc.CurrentTarget != null ? pc.CurrentTarget.name : null }, enemies = new { alive, total = Enemy.All.Count } }; } [CliCommand("wl_timescale", "Time.timeScale 변경 (0 = 일시정지 · 1 = 정상). 에디터 서버에서 Play 중 바로 적용된다 — 런타임 서버 없이도 동작", Tags = new[] { "wl" })] public static object SetTimeScale( [CliArg("scale", "배속 (0 ~ 100)", Required = true)] float scale) { if (scale < 0f || scale > 100f) throw new System.ArgumentException("scale 은 0~100 범위여야 합니다: " + scale); var before = Time.timeScale; Time.timeScale = scale; return new { before, after = Time.timeScale, isPlaying = Application.isPlaying }; } [CliCommand("wl_teleport_player", "현재 플레이어를 월드 좌표로 이동 (CharacterController 를 잠시 꺼서 안전하게). Play 모드에서만 동작", Tags = new[] { "wl" })] public static object TeleportPlayer( [CliArg("x", "월드 X", Required = true)] float x, [CliArg("y", "월드 Y", Required = true)] float y, [CliArg("z", "월드 Z", Required = true)] float z) { if (!Application.isPlaying) throw new System.InvalidOperationException("Play 모드에서만 동작합니다 (editor_play 후 재시도)"); var switcher = Object.FindFirstObjectByType(); var pc = switcher != null ? switcher.Current : Object.FindFirstObjectByType(); if (pc == null) throw new System.InvalidOperationException("씬에 PlayerController 가 없습니다"); var cc = pc.GetComponent(); var from = pc.transform.position; if (cc != null) cc.enabled = false; pc.transform.position = new Vector3(x, y, z); if (cc != null) cc.enabled = true; return new { player = pc.name, from = Vec(from), to = Vec(pc.transform.position) }; } // Vector3 를 그대로 반환하면 JSON 직렬화가 normalized 자기참조로 실패하므로 x/y/z 로 풀어 준다. private static object Vec(Vector3 v) => new { x = v.x, y = v.y, z = v.z }; } } #endif