2026-10-19 16:12:04 +00:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
2026-10-19 16:48:59 +00:00
|
|
|
// WLSkillTestSceneBuilder.cs — 독립 스킬 테스트 씬(Assets/WL/SkillTest/WLSkillTest.unity)을 코드로 조립·저장 (PD #947 · 2026-10-20 재지시판)
|
2026-10-19 16:12:04 +00:00
|
|
|
//
|
2026-10-19 16:48:59 +00:00
|
|
|
// ■ 들어가는 것(= 원본 StatTest 하네스 구성 + WL 치비·더미·평지) — 로그인·HUD·섬 없음
|
|
|
|
|
// Directional Light · Ground(Plane · URP Lit 재질 자산 · 하네스가 런타임 NavMesh 베이크) · Mgr(AddrResourceMgr·CoroutineMgr·TimeMgr) · EventSystem
|
|
|
|
|
// · ResWork/UIPrefabs/Title/{Tables, SoundInfo, SortOrder_5(OptionInfo·Popup), ActorInfo}.prefab
|
|
|
|
|
// · ResWork/Prefabs/Ingame/{InGameInfo(이펙트·투사체 풀), ProjectileInfo, Main Camera(RealCamera)}.prefab
|
|
|
|
|
// · PCPos/치비 PC 프리팹(WLCharacterSwapSettings.pcPrefabName · 없으면 Ai01) · MobPos/더미 몹 프리팹(Res_Addr/Mobs/WL/RPG_Bat)
|
|
|
|
|
// · Canvas + 글자 4(원본 Set_Dmglabel 이 쓰는 피해 로그) · WLSkillTestHarness(actors · labels · pcActor · dummyActor)
|
|
|
|
|
// ■ 메뉴 WL/Skill Test/Build Scene · CLI: 먼저 CloseTestScene() 또는 Title 을 연 뒤 Build()
|
2026-10-19 16:12:04 +00:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
using System.Text;
|
2026-10-19 16:48:59 +00:00
|
|
|
using TMPro;
|
2026-10-19 16:12:04 +00:00
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEditor.SceneManagement;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.EventSystems;
|
|
|
|
|
using UnityEngine.SceneManagement;
|
2026-10-19 16:48:59 +00:00
|
|
|
using UnityEngine.UI;
|
2026-10-19 16:12:04 +00:00
|
|
|
|
|
|
|
|
namespace WL.SkillTest.Editor
|
|
|
|
|
{
|
|
|
|
|
public static class WLSkillTestSceneBuilder
|
|
|
|
|
{
|
|
|
|
|
public const string Folder = "Assets/WL/SkillTest";
|
|
|
|
|
public const string ScenePath = Folder + "/WLSkillTest.unity";
|
|
|
|
|
public const string GroundMatPath = Folder + "/WLSkillTestGround.mat";
|
|
|
|
|
|
2026-10-19 16:48:59 +00:00
|
|
|
static readonly string[] TitlePrefabs = { "Tables", "SoundInfo", "SortOrder_5", "ActorInfo" };
|
2026-10-19 16:12:04 +00:00
|
|
|
const string TitlePrefabFolder = "Assets/ResWork/UIPrefabs/Title/";
|
2026-10-19 16:48:59 +00:00
|
|
|
static readonly string[] IngamePrefabs = { "InGameInfo", "ProjectileInfo", "Main Camera" };
|
|
|
|
|
const string IngamePrefabFolder = "Assets/ResWork/Prefabs/Ingame/";
|
|
|
|
|
const string PcPrefabFolder = "Assets/Res_Addr/PC/";
|
|
|
|
|
const string DummyPrefab = "Assets/Res_Addr/Mobs/WL/RPG_Bat.prefab";
|
2026-10-19 16:12:04 +00:00
|
|
|
|
|
|
|
|
[MenuItem("WL/Skill Test/Build Scene (다시 만들기)")]
|
|
|
|
|
public static void BuildMenu() { Debug.Log(Build()); }
|
|
|
|
|
|
|
|
|
|
[MenuItem("WL/Skill Test/Open Scene")]
|
|
|
|
|
public static void OpenMenu() { Debug.Log(Open()); }
|
|
|
|
|
|
|
|
|
|
public static string Open()
|
|
|
|
|
{
|
|
|
|
|
var cur = SceneManager.GetActiveScene();
|
2026-10-19 16:48:59 +00:00
|
|
|
if (cur.path == ScenePath) return "열림 " + ScenePath;
|
2026-10-19 16:12:04 +00:00
|
|
|
if (cur.isDirty) return "현재 씬 " + cur.name + " 이 저장되지 않음 — 저장 없이 다시 열거나 저장한 뒤 다시";
|
|
|
|
|
if (!System.IO.File.Exists(ScenePath)) return "씬 없음 " + ScenePath + " — Build() 먼저";
|
|
|
|
|
EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
|
|
|
|
|
return "열림 " + ScenePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool IsTestSceneOpen()
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < SceneManager.sceneCount; i++) if (SceneManager.GetSceneAt(i).path == ScenePath) return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 16:48:59 +00:00
|
|
|
/// <summary>테스트 씬이 열려 있으면 저장 없이 빈 씬으로(다시 만들기 전 · 저장 안 된 변경이 있으면 거절).</summary>
|
2026-10-19 16:12:04 +00:00
|
|
|
public static string CloseTestScene()
|
|
|
|
|
{
|
|
|
|
|
if (Application.isPlaying) return "Play 중";
|
|
|
|
|
if (!IsTestSceneOpen()) return "테스트 씬 안 열림";
|
|
|
|
|
var a = SceneManager.GetActiveScene();
|
2026-10-19 16:48:59 +00:00
|
|
|
if (a.path == ScenePath && a.isDirty) return "테스트 씬이 저장되지 않음";
|
2026-10-19 16:12:04 +00:00
|
|
|
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
|
|
|
|
return "빈 씬으로 바꿈";
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 16:48:59 +00:00
|
|
|
static string PcPrefabPath()
|
|
|
|
|
{
|
|
|
|
|
var s = WL.Character.WLCharacterSwapSettings.Instance;
|
|
|
|
|
string name = s != null && !string.IsNullOrEmpty(s.pcPrefabName) ? s.pcPrefabName : "Ai01";
|
|
|
|
|
string p = PcPrefabFolder + name + ".prefab";
|
|
|
|
|
if (AssetDatabase.LoadAssetAtPath<GameObject>(p) == null) p = PcPrefabFolder + "Ai01.prefab";
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static TextMeshProUGUI MakeLabel(Transform canvas, string name, Vector2 anchor, Vector2 pos, Vector2 size, TextAlignmentOptions align)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name, typeof(RectTransform));
|
|
|
|
|
go.transform.SetParent(canvas, false);
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchorMin = rt.anchorMax = rt.pivot = anchor; rt.anchoredPosition = pos; rt.sizeDelta = size;
|
|
|
|
|
var t = go.AddComponent<TextMeshProUGUI>();
|
|
|
|
|
t.fontSize = 16; t.alignment = align; t.color = Color.white; t.text = "";
|
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 16:12:04 +00:00
|
|
|
public static string Build()
|
|
|
|
|
{
|
|
|
|
|
if (Application.isPlaying) return "Play 중에는 만들지 않는다";
|
2026-10-19 16:48:59 +00:00
|
|
|
if (IsTestSceneOpen()) return "테스트 씬이 열려 있음 — 먼저 CloseTestScene() 또는 Title 을 연 뒤 Build()";
|
2026-10-19 16:12:04 +00:00
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
var prevActive = SceneManager.GetActiveScene();
|
2026-10-19 16:48:59 +00:00
|
|
|
if (!AssetDatabase.IsValidFolder(Folder)) AssetDatabase.CreateFolder("Assets/WL", "SkillTest");
|
2026-10-19 16:12:04 +00:00
|
|
|
|
2026-10-19 16:48:59 +00:00
|
|
|
var cfg = WLSkillTestHarness.Cfg;
|
2026-10-19 16:12:04 +00:00
|
|
|
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
|
|
|
|
|
SceneManager.SetActiveScene(scene);
|
|
|
|
|
|
|
|
|
|
var lightGo = new GameObject("Directional Light");
|
|
|
|
|
var light = lightGo.AddComponent<Light>();
|
2026-10-19 16:48:59 +00:00
|
|
|
light.type = LightType.Directional; light.intensity = 1.1f; light.shadows = LightShadows.Soft;
|
2026-10-19 16:12:04 +00:00
|
|
|
lightGo.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
|
|
|
|
|
|
|
|
|
|
var ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
|
|
|
|
|
ground.name = "Ground";
|
2026-10-19 16:48:59 +00:00
|
|
|
float size = cfg != null && cfg.groundSize > 1f ? cfg.groundSize : 100f;
|
2026-10-19 16:12:04 +00:00
|
|
|
ground.transform.localScale = new Vector3(size / 10f, 1f, size / 10f);
|
|
|
|
|
var mat = AssetDatabase.LoadAssetAtPath<Material>(GroundMatPath);
|
2026-10-19 16:48:59 +00:00
|
|
|
if (mat == null) { var sh = Shader.Find("Universal Render Pipeline/Lit"); mat = new Material(sh != null ? sh : Shader.Find("Standard")); AssetDatabase.CreateAsset(mat, GroundMatPath); }
|
2026-10-19 16:12:04 +00:00
|
|
|
var gc = cfg != null && cfg.groundColor != null && cfg.groundColor.Length >= 3 ? new Color(cfg.groundColor[0], cfg.groundColor[1], cfg.groundColor[2]) : new Color(0.45f, 0.62f, 0.35f);
|
|
|
|
|
if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", gc); else mat.color = gc;
|
|
|
|
|
EditorUtility.SetDirty(mat);
|
|
|
|
|
ground.GetComponent<Renderer>().sharedMaterial = mat;
|
2026-10-19 16:48:59 +00:00
|
|
|
GameObjectUtility.SetStaticEditorFlags(ground, StaticEditorFlags.BatchingStatic);
|
2026-10-19 16:12:04 +00:00
|
|
|
|
2026-10-19 16:48:59 +00:00
|
|
|
var mgr = new GameObject("Mgr"); mgr.AddComponent<AddrResourceMgr>(); mgr.AddComponent<CoroutineMgr>(); mgr.AddComponent<TimeMgr>();
|
|
|
|
|
var es = new GameObject("EventSystem"); es.AddComponent<EventSystem>(); es.AddComponent<StandaloneInputModule>();
|
2026-10-19 16:12:04 +00:00
|
|
|
|
|
|
|
|
foreach (var n in TitlePrefabs)
|
|
|
|
|
{
|
|
|
|
|
var p = AssetDatabase.LoadAssetAtPath<GameObject>(TitlePrefabFolder + n + ".prefab");
|
|
|
|
|
if (p == null) { sb.Append("🔴 프리팹 없음 ").Append(n).Append('\n'); continue; }
|
2026-10-19 16:48:59 +00:00
|
|
|
var go = PrefabUtility.InstantiatePrefab(p, scene) as GameObject; if (go != null) go.name = n;
|
|
|
|
|
}
|
|
|
|
|
foreach (var n in IngamePrefabs)
|
|
|
|
|
{
|
|
|
|
|
var p = AssetDatabase.LoadAssetAtPath<GameObject>(IngamePrefabFolder + n + ".prefab");
|
|
|
|
|
if (p == null) { sb.Append("🔴 프리팹 없음 ").Append(n).Append('\n'); continue; }
|
|
|
|
|
var go = PrefabUtility.InstantiatePrefab(p, scene) as GameObject; if (go != null) go.name = n;
|
2026-10-19 16:12:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-19 16:48:59 +00:00
|
|
|
// PC(치비) · 더미 — 씬에 직접(StatTest 의 PCPos/MobPos 와 같은 꼴)
|
|
|
|
|
Vector3 spawn = cfg != null && cfg.spawnPos != null && cfg.spawnPos.Length >= 3 ? new Vector3(cfg.spawnPos[0], cfg.spawnPos[1], cfg.spawnPos[2]) : Vector3.zero;
|
|
|
|
|
var pcPos = new GameObject("PCPos"); pcPos.transform.position = spawn;
|
|
|
|
|
var pcPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(PcPrefabPath());
|
|
|
|
|
Actor pcActor = null;
|
|
|
|
|
if (pcPrefab != null) { var pc = PrefabUtility.InstantiatePrefab(pcPrefab, scene) as GameObject; pc.transform.SetParent(pcPos.transform, false); pcActor = pc.GetComponent<Actor>(); }
|
|
|
|
|
else sb.Append("🔴 PC 프리팹 없음\n");
|
|
|
|
|
var mobPos = new GameObject("MobPos"); mobPos.transform.position = spawn + new Vector3(2f, 0f, -1.5f);
|
|
|
|
|
var mobPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(DummyPrefab);
|
|
|
|
|
MobActor mob = null;
|
|
|
|
|
if (mobPrefab != null) { var m = PrefabUtility.InstantiatePrefab(mobPrefab, scene) as GameObject; m.transform.SetParent(mobPos.transform, false); mob = m.GetComponent<MobActor>(); }
|
|
|
|
|
else sb.Append("🔴 더미 프리팹 없음 " + DummyPrefab + "\n");
|
|
|
|
|
if (pcActor == null) sb.Append("🔴 PC 프리팹에 Actor 없음\n");
|
|
|
|
|
if (mob == null) sb.Append("🔴 더미 프리팹에 MobActor 없음\n");
|
|
|
|
|
|
|
|
|
|
// 글자 4(원본 분기 Set_Dmglabel 이 labels[2] 에 피해를 적는다)
|
|
|
|
|
var canvasGo = new GameObject("WLSkillTestCanvas");
|
|
|
|
|
var canvas = canvasGo.AddComponent<Canvas>(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 50;
|
|
|
|
|
canvasGo.AddComponent<CanvasScaler>(); canvasGo.AddComponent<GraphicRaycaster>();
|
|
|
|
|
var labels = new TextMeshProUGUI[4];
|
|
|
|
|
labels[0] = MakeLabel(canvasGo.transform, "pcstat", new Vector2(0f, 0f), new Vector2(10f, 10f), new Vector2(360f, 220f), TextAlignmentOptions.BottomLeft);
|
|
|
|
|
labels[1] = MakeLabel(canvasGo.transform, "mobstat", new Vector2(1f, 0f), new Vector2(-10f, 10f), new Vector2(360f, 220f), TextAlignmentOptions.BottomRight);
|
|
|
|
|
labels[2] = MakeLabel(canvasGo.transform, "dmg", new Vector2(1f, 1f), new Vector2(-10f, -10f), new Vector2(420f, 300f), TextAlignmentOptions.TopRight);
|
|
|
|
|
labels[3] = MakeLabel(canvasGo.transform, "classinfo", new Vector2(0.5f, 0f), new Vector2(0f, 10f), new Vector2(600f, 40f), TextAlignmentOptions.Bottom);
|
|
|
|
|
|
|
|
|
|
var hGo = new GameObject("WLSkillTestHarness");
|
|
|
|
|
var h = hGo.AddComponent<WLSkillTestHarness>();
|
|
|
|
|
h.pcActor = pcActor; h.dummyActor = mob;
|
|
|
|
|
h.actors = new Actor[] { pcActor, mob };
|
|
|
|
|
h.labels = labels;
|
2026-10-19 16:12:04 +00:00
|
|
|
|
2026-10-19 16:48:59 +00:00
|
|
|
bool saved = EditorSceneManager.SaveScene(scene, ScenePath);
|
|
|
|
|
sb.Append(saved ? "저장 " : "🔴 저장 실패 ").Append(ScenePath).Append(" · 루트 ").Append(scene.rootCount).Append(" · PC ").Append(PcPrefabPath()).Append('\n');
|
2026-10-19 16:12:04 +00:00
|
|
|
if (prevActive.IsValid() && prevActive.isLoaded) SceneManager.SetActiveScene(prevActive);
|
|
|
|
|
EditorSceneManager.CloseScene(scene, true);
|
|
|
|
|
AssetDatabase.SaveAssets();
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|