Merge branch 'wl/gameplay/WL-814q-arena-proto'

This commit is contained in:
깃 관리자 2026-09-12 21:29:22 +09:00
commit 979eb53d2a
13 changed files with 15554 additions and 0 deletions

View File

@ -0,0 +1,477 @@
// WL-814q — 시범 전투장 씬 신규 제작 (Critter Demo 만듦새 재현 · 반경 14 m)
// 데모 실측값(AgentScripts/WL814q_Probe.cs · Demo.unity YAML)을 그대로 재현한다. 원본 에셋 수정 0(참조만).
public static class WL814q_BuildArena
{
const string SceneDir = "Assets/WL/Look/Arena/Scenes";
const string TerrDir = "Assets/WL/Look/Arena/Terrain";
const string ScenePath = SceneDir + "/WL_ArenaProto.unity";
const string TerrPath = TerrDir + "/WL_ArenaTerrain.asset";
const string PfxDir = "Assets/3DPixelArtEnvironment/Prefabs/";
const string DemoDir = "Assets/3DPixelArtEnvironment/Demo/";
const string MatDir = "Assets/3DPixelArtEnvironment/Materials/";
const float TerrSize = 80f; // m (데모 200 m · 반경 14 m 아레나에 맞춰 축소)
const float TerrHeight = 12f; // m (데모 100 m)
const int HmRes = 65; // 64×64 쿼드 = 8,192 tri (데모 513 은 삼각형 예산 초과)
const int AlphaRes = 256;
const float TerrY = -1.0f; // 아레나 바닥이 월드 y=0 이 되도록
const float BaseH = 1.0f / TerrHeight;
const float Barrier = 14f; // WLStageTable barrierRadius
const float StartR = 2f; // WLStageTable startRadius
static readonly UnityEngine.Vector2 PondC = new UnityEngine.Vector2(17.5f, -10.5f);
const float PondR = 6.0f;
const float WaterY = -0.10f;
static System.Random rnd;
static UnityEngine.Terrain terrain;
static System.Text.StringBuilder log;
/// <summary>HLSL smoothstep. 🔴 Unity 의 Mathf.SmoothStep(from,to,t) 은 이것과 다르다(from~to 보간).</summary>
static float SStep(float e0, float e1, float x)
{
float t = UnityEngine.Mathf.Clamp01((x - e0) / (e1 - e0));
return t * t * (3f - 2f * t);
}
static UnityEngine.GameObject Pfx(string n)
{
return UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(PfxDir + n + ".prefab");
}
static float Rf(float a, float b) { return a + (float)rnd.NextDouble() * (b - a); }
static float Mirror() { return (rnd.NextDouble() < 0.5) ? -1f : 1f; }
static UnityEngine.GameObject Place(string prefab, UnityEngine.Transform parent, string name,
float wx, float wz, float yaw, UnityEngine.Vector3 scale, float sink, bool alignByBounds)
{
var src = Pfx(prefab);
if (src == null) { log.AppendLine("MISSING PREFAB " + prefab); return null; }
var go = (UnityEngine.GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(src, parent);
go.name = name;
go.transform.localScale = scale;
go.transform.rotation = UnityEngine.Quaternion.Euler(0f, yaw, 0f);
go.transform.position = new UnityEngine.Vector3(wx, 0f, wz);
float groundY = terrain != null
? terrain.SampleHeight(new UnityEngine.Vector3(wx, 0f, wz)) + terrain.transform.position.y : 0f;
var b = Bounds(go);
if (alignByBounds)
{
go.transform.position += new UnityEngine.Vector3(wx - b.center.x, 0f, wz - b.center.z);
b = Bounds(go);
}
go.transform.position += new UnityEngine.Vector3(0f, groundY - b.min.y - sink, 0f);
return go;
}
static UnityEngine.Bounds Bounds(UnityEngine.GameObject go)
{
var rs = go.GetComponentsInChildren<UnityEngine.Renderer>(true);
var b = new UnityEngine.Bounds(go.transform.position, UnityEngine.Vector3.zero);
bool f = true;
foreach (var r in rs) { if (f) { b = r.bounds; f = false; } else b.Encapsulate(r.bounds); }
return b;
}
static float PondAngle { get { return UnityEngine.Mathf.Atan2(PondC.y, PondC.x) * UnityEngine.Mathf.Rad2Deg; } }
static bool NearPond(float a) { return UnityEngine.Mathf.Abs(UnityEngine.Mathf.DeltaAngle(a, PondAngle)) < 15f; }
public static string Run()
{
log = new System.Text.StringBuilder();
rnd = new System.Random(814);
if (!UnityEditor.AssetDatabase.IsValidFolder("Assets/WL/Look/Arena"))
UnityEditor.AssetDatabase.CreateFolder("Assets/WL/Look", "Arena");
if (!UnityEditor.AssetDatabase.IsValidFolder(SceneDir))
UnityEditor.AssetDatabase.CreateFolder("Assets/WL/Look/Arena", "Scenes");
if (!UnityEditor.AssetDatabase.IsValidFolder(TerrDir))
UnityEditor.AssetDatabase.CreateFolder("Assets/WL/Look/Arena", "Terrain");
var scene = UnityEditor.SceneManagement.EditorSceneManager.NewScene(
UnityEditor.SceneManagement.NewSceneSetup.EmptyScene,
UnityEditor.SceneManagement.NewSceneMode.Single);
// ── 1. 지형 (데모와 같은 방식: Unity Terrain + Demo 터레인 레이어 2종 + Terrain.mat[ToonTerrain])
var td = new UnityEngine.TerrainData();
td.heightmapResolution = HmRes;
td.size = new UnityEngine.Vector3(TerrSize, TerrHeight, TerrSize);
td.alphamapResolution = AlphaRes;
td.SetDetailResolution(0, 8);
float half = TerrSize * 0.5f;
var h = new float[HmRes, HmRes];
for (int y = 0; y < HmRes; y++)
for (int x = 0; x < HmRes; x++)
{
float wx = (float)x / (HmRes - 1) * TerrSize - half;
float wz = (float)y / (HmRes - 1) * TerrSize - half;
float r = UnityEngine.Mathf.Sqrt(wx * wx + wz * wz);
// 전투장 안은 평지 + 아주 얕은 기복(툰 셰이딩 단차가 보이게 · 이동엔 영향 없음)
float ripple = (UnityEngine.Mathf.PerlinNoise(wx * 0.14f + 31.7f, wz * 0.14f + 19.3f) - 0.5f) * 0.14f;
float v = BaseH + ripple / TerrHeight;
if (r > 15.5f)
{
float t = SStep(15.5f, 38f, r);
float n = UnityEngine.Mathf.PerlinNoise(wx * 0.035f + 11.3f, wz * 0.035f + 7.1f);
v += (0.4f + n * 2.2f) * t / TerrHeight;
}
float pr = UnityEngine.Mathf.Sqrt((wx - PondC.x) * (wx - PondC.x) + (wz - PondC.y) * (wz - PondC.y));
if (pr < PondR)
v = UnityEngine.Mathf.Lerp(v, 0.15f / TerrHeight, SStep(PondR, PondR * 0.25f, pr));
h[y, x] = v;
}
var grass = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.TerrainLayer>(DemoDir + "Demo_Grass.terrainlayer");
var dirt = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.TerrainLayer>(DemoDir + "Demo_Dirt.terrainlayer");
td.terrainLayers = new UnityEngine.TerrainLayer[] { grass, dirt }; // 0=R채널(풀 초록) · 1=G채널(흙 갈색) — Terrain.mat 실측
var alpha = new float[AlphaRes, AlphaRes, 2];
for (int y = 0; y < AlphaRes; y++)
for (int x = 0; x < AlphaRes; x++)
{
float wx = (float)x / (AlphaRes - 1) * TerrSize - half;
float wz = (float)y / (AlphaRes - 1) * TerrSize - half;
float n = UnityEngine.Mathf.PerlinNoise(wx * 0.16f + 3.7f, wz * 0.16f + 5.2f);
float r = UnityEngine.Mathf.Sqrt(wx * wx + wz * wz) + (n - 0.5f) * 3.2f;
float d = 1f - SStep(11.0f, 15.0f, r); // 흙 = 전투장 바닥(반경 ~13 m)
float pr = UnityEngine.Mathf.Sqrt((wx - PondC.x) * (wx - PondC.x) + (wz - PondC.y) * (wz - PondC.y));
d = UnityEngine.Mathf.Max(d, 1f - SStep(PondR - 1.0f, PondR + 1.8f, pr)); // 물가 = 흙
alpha[y, x, 1] = UnityEngine.Mathf.Clamp01(d);
alpha[y, x, 0] = 1f - UnityEngine.Mathf.Clamp01(d);
}
// 🔴 높이·스플랫은 에셋을 만든 뒤에 써야 남는다(CreateAsset 전에 쓰면 ch1 이 0 으로 날아간다 — 실측)
UnityEditor.AssetDatabase.CreateAsset(td, TerrPath);
td.SetHeights(0, 0, h);
td.SetAlphamaps(0, 0, alpha);
UnityEditor.EditorUtility.SetDirty(td);
UnityEditor.AssetDatabase.SaveAssets();
var terrGo = UnityEngine.Terrain.CreateTerrainGameObject(td);
terrGo.name = "Terrain";
terrGo.transform.position = new UnityEngine.Vector3(-half, TerrY, -half);
terrain = terrGo.GetComponent<UnityEngine.Terrain>();
terrain.materialTemplate = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(MatDir + "Terrain.mat");
terrain.heightmapPixelError = 5f;
terrain.drawTreesAndFoliage = false;
UnityEditor.SceneManagement.EditorSceneManager.MoveGameObjectToScene(terrGo, scene);
AddTerrainInstancing(terrGo);
// ── 2. 조명 (데모 실측 그대로)
var lightRoot = new UnityEngine.GameObject("Lighting");
var dl = new UnityEngine.GameObject("Directional Light");
dl.transform.SetParent(lightRoot.transform);
var dlc = dl.AddComponent<UnityEngine.Light>();
dlc.type = UnityEngine.LightType.Directional;
dlc.color = UnityEngine.Color.white;
dlc.intensity = 1f;
dlc.shadows = UnityEngine.LightShadows.Soft;
dl.transform.position = new UnityEngine.Vector3(0f, 3f, 0f);
dl.transform.rotation = new UnityEngine.Quaternion(0.40821788f, -0.23456968f, 0.10938163f, 0.8754261f);
var warm = new UnityEngine.Color(1f, 0.9639759f, 0.63836473f, 1f);
// ── 3. 렌더 설정 (데모 실측)
UnityEngine.RenderSettings.fog = false;
UnityEngine.RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Skybox;
UnityEngine.RenderSettings.ambientSkyColor = new UnityEngine.Color(0.212f, 0.227f, 0.259f, 1f);
UnityEngine.RenderSettings.ambientEquatorColor = new UnityEngine.Color(0.114f, 0.125f, 0.133f, 1f);
UnityEngine.RenderSettings.ambientGroundColor = new UnityEngine.Color(0.047f, 0.043f, 0.035f, 1f);
UnityEngine.RenderSettings.ambientIntensity = 1f;
// 🔴 Resources.GetBuiltinResource 로는 못 찾는다(콘솔 에러) — 빌트인 extra 리소스로 받아야 한다
UnityEngine.RenderSettings.skybox =
UnityEditor.AssetDatabase.GetBuiltinExtraResource<UnityEngine.Material>("Default-Skybox.mat");
UnityEngine.RenderSettings.subtractiveShadowColor = new UnityEngine.Color(0.42f, 0.478f, 0.627f, 1f);
// ── 4. 경계(반경 14 m) — 바위 띠를 촘촘히 + 나무 숲 + 아치 2개
var ring = new UnityEngine.GameObject("Arena_Ring");
int nStone = 56;
for (int i = 0; i < nStone; i++)
{
float a = (float)i / nStone * 360f + Rf(-2.5f, 2.5f);
if (UnityEngine.Mathf.Abs(UnityEngine.Mathf.DeltaAngle(a, 0f)) < 13f) continue; // 정문
if (UnityEngine.Mathf.Abs(UnityEngine.Mathf.DeltaAngle(a, 180f)) < 11f) continue; // 후문
if (NearPond(a)) continue; // 연못 쪽은 트인다
float r = Barrier + Rf(0.1f, 1.4f);
float rad = a * UnityEngine.Mathf.Deg2Rad;
bool big = rnd.NextDouble() < 0.6;
float s = big ? Rf(1.0f, 1.8f) : Rf(1.1f, 2.0f);
// 변형: y 회전 + 비균일 스케일 + x 음수 스케일(거울) — 데모 Stone_2 실측 기법 그대로
var sc = new UnityEngine.Vector3(s * Mirror() * Rf(0.8f, 1.3f), s * Rf(0.8f, 1.6f), s * Rf(0.85f, 1.2f));
Place(big ? "Stone_2" : "Stone_1", ring.transform, (big ? "Stone_2_" : "Stone_1_") + i,
UnityEngine.Mathf.Cos(rad) * r, UnityEngine.Mathf.Sin(rad) * r, Rf(0f, 360f), sc, Rf(0.1f, 0.5f), false);
}
int nTree = 20;
for (int i = 0; i < nTree; i++)
{
float a = (float)i / nTree * 360f + Rf(-6f, 6f);
if (UnityEngine.Mathf.Abs(UnityEngine.Mathf.DeltaAngle(a, 0f)) < 18f) continue;
if (NearPond(a)) continue;
float r = Barrier + Rf(2.2f, 5.0f);
float rad = a * UnityEngine.Mathf.Deg2Rad;
float s = Rf(0.38f, 0.62f);
Place("Tree", ring.transform, "Tree_Ring_" + i,
UnityEngine.Mathf.Cos(rad) * r, UnityEngine.Mathf.Sin(rad) * r, Rf(0f, 360f),
new UnityEngine.Vector3(s * Mirror(), s * Rf(0.85f, 1.25f), s), 0.05f, false);
}
Place("Arch_Large", ring.transform, "Gate_Arch_Large", Barrier + 0.6f, 0f, -90f,
UnityEngine.Vector3.one * 0.34f, 0.05f, true);
Place("Arch_Small", ring.transform, "Gate_Arch_Small", -(Barrier + 0.4f), 0f, 90f,
UnityEngine.Vector3.one * 0.34f, 0.05f, true);
// ── 5. 전투장 바닥 (Slab 포석 + 잔돌 · 중앙 반경 2 m 는 비운다)
var floor = new UnityEngine.GameObject("Arena_Floor");
for (int i = 0; i < 14; i++)
{
float a = Rf(0f, 360f);
float r = Rf(StartR + 1.2f, 12.2f);
float rad = a * UnityEngine.Mathf.Deg2Rad;
float s = Rf(0.26f, 0.50f); // Slab 원 크기 3.37×0.91×4.41 m → 0.9~2.2 m 포석
var g = Place("Slab", floor.transform, "Slab_" + i,
UnityEngine.Mathf.Cos(rad) * r, UnityEngine.Mathf.Sin(rad) * r, Rf(0f, 360f),
new UnityEngine.Vector3(s * Rf(0.85f, 1.25f), s * Rf(0.7f, 1.1f), s * Rf(0.85f, 1.25f)), Rf(0.12f, 0.26f), false);
// 변형: z 180° 뒤집기 — 데모 Slab (2)(3) 실측 기법
if (g != null && rnd.NextDouble() < 0.35)
g.transform.rotation = UnityEngine.Quaternion.Euler(0f, g.transform.eulerAngles.y, 180f);
}
for (int i = 0; i < 26; i++) // 잔돌 (Stone.mat · 새 머티리얼 0)
{
float a = Rf(0f, 360f);
float r = Rf(StartR + 0.6f, 13.2f);
float rad = a * UnityEngine.Mathf.Deg2Rad;
float s = Rf(0.10f, 0.28f);
Place((rnd.NextDouble() < 0.5) ? "Stone_1" : "Stone_2", floor.transform, "Pebble_" + i,
UnityEngine.Mathf.Cos(rad) * r, UnityEngine.Mathf.Sin(rad) * r, Rf(0f, 360f),
new UnityEngine.Vector3(s * Mirror() * Rf(0.8f, 1.4f), s * Rf(0.5f, 1.0f), s * Rf(0.8f, 1.4f)),
Rf(0.01f, 0.06f), false);
}
// 가로등 2개 + 데모의 따뜻한 포인트 라이트 2개
float[] lpA = { 58f, 236f };
for (int i = 0; i < 2; i++)
{
float rad = lpA[i] * UnityEngine.Mathf.Deg2Rad;
float px = UnityEngine.Mathf.Cos(rad) * 12.4f, pz = UnityEngine.Mathf.Sin(rad) * 12.4f;
var lp = Place("Light_Post", floor.transform, "Light_Post_" + i, px, pz, lpA[i] + 180f,
UnityEngine.Vector3.one * 0.22f, 0f, false);
var pl = new UnityEngine.GameObject("Point Light " + i);
pl.transform.SetParent(lightRoot.transform);
pl.transform.position = new UnityEngine.Vector3(px, (lp != null ? Bounds(lp).max.y - 0.35f : 3.5f), pz);
var plc = pl.AddComponent<UnityEngine.Light>();
plc.type = UnityEngine.LightType.Point;
plc.color = warm; plc.intensity = 0.54f; plc.range = 6.14f;
plc.shadows = UnityEngine.LightShadows.None;
}
UnityEditor.SceneManagement.EditorSceneManager.MoveGameObjectToScene(lightRoot, scene);
// ── 6. 연못 (Water_Plane · 데모의 흰 거품 가장자리)
var pondRoot = new UnityEngine.GameObject("Pond");
var water = (UnityEngine.GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(Pfx("Water_Plane"), pondRoot.transform);
water.name = "Water_Plane";
water.transform.position = new UnityEngine.Vector3(PondC.x, WaterY, PondC.y);
water.transform.localScale = new UnityEngine.Vector3(PondR * 2.15f / 10f, 1f, PondR * 1.9f / 10f);
UnityEditor.SceneManagement.EditorSceneManager.MoveGameObjectToScene(pondRoot, scene);
for (int i = 0; i < 7; i++)
{
float a = Rf(0f, 360f) * UnityEngine.Mathf.Deg2Rad;
float s = Rf(0.6f, 1.3f);
Place("Stone_1", pondRoot.transform, "Pond_Stone_" + i,
PondC.x + UnityEngine.Mathf.Cos(a) * Rf(PondR * 1.0f, PondR * 1.35f),
PondC.y + UnityEngine.Mathf.Sin(a) * Rf(PondR * 1.0f, PondR * 1.35f),
Rf(0f, 360f), new UnityEngine.Vector3(s * Mirror(), s * Rf(0.7f, 1.1f), s), Rf(0.1f, 0.4f), false);
}
// ── 7. 배경 랜드마크 (원근감)
var bg = new UnityEngine.GameObject("Background");
Place("Windmill", bg.transform, "Windmill", -20f, 28f, 143f, UnityEngine.Vector3.one * 3.2f, 0.1f, false);
Place("House", bg.transform, "House_1", 25f, 23f, -142f, UnityEngine.Vector3.one * 4.6f, 0.1f, false);
Place("House", bg.transform, "House_2", 31f, 16f, -118f, UnityEngine.Vector3.one * 4.0f, 0.1f, false);
for (int i = 0; i < 14; i++)
{
float a = Rf(0f, 360f) * UnityEngine.Mathf.Deg2Rad;
float r = Rf(22f, 36f);
float s = Rf(0.5f, 0.9f);
Place("Tree", bg.transform, "Tree_BG_" + i,
UnityEngine.Mathf.Cos(a) * r, UnityEngine.Mathf.Sin(a) * r, Rf(0f, 360f),
new UnityEngine.Vector3(s * Mirror(), s * Rf(0.85f, 1.25f), s), 0.05f, false);
}
UnityEditor.SceneManagement.EditorSceneManager.MoveGameObjectToScene(bg, scene);
UnityEditor.SceneManagement.EditorSceneManager.MoveGameObjectToScene(ring, scene);
UnityEditor.SceneManagement.EditorSceneManager.MoveGameObjectToScene(floor, scene);
// ── 8. 캐릭터 (중앙 · 인게임과 같은 월드 높이 1.1912 m)
float pcScale = 0.7f * 1.5038f; // ClassConfig f_Scale × WLCharacterSwapSettings.heightCompensation
var pcSrc = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>("Assets/Res_Addr/PC/LH_M05.prefab");
var pc = (UnityEngine.GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(pcSrc);
pc.name = "PC_LH_M05";
pc.transform.localScale = UnityEngine.Vector3.one * pcScale;
pc.transform.position = UnityEngine.Vector3.zero;
pc.transform.rotation = UnityEngine.Quaternion.Euler(0f, 200f, 0f);
var comp = pc.GetComponent("WL.Character.WLPcScaleCompensator") as UnityEngine.MonoBehaviour;
if (comp != null)
{ // 씬 인스턴스 한정 오버라이드 — Play 때 스케일이 다시 곱해지지 않게(프리팹 수정 0)
var so = new UnityEditor.SerializedObject(comp);
var p = so.FindProperty("overrideCompensation");
if (p != null) { p.floatValue = 1f; so.ApplyModifiedProperties(); }
}
UnityEditor.SceneManagement.EditorSceneManager.MoveGameObjectToScene(pc, scene);
log.AppendLine(PoseIdle(pc));
var pb = Bounds(pc);
log.AppendLine(string.Format("PC scale={0:F5} worldHeight={1:F4} m (min.y={2:F4} max.y={3:F4})",
pcScale, pb.size.y, pb.min.y, pb.max.y));
// ── 9. 카메라 (인게임 도트A 와 같은 각도·거리 · CameraFramingOverride 수식 실측)
float H = 80f, D = 3f, Up = 3f, lookAtY = 1.06f;
float rr = H * UnityEngine.Mathf.Deg2Rad;
float camUp = Up + D * (UnityEngine.Mathf.Sin(rr) - UnityEngine.Mathf.Cos(rr));
float ss = UnityEngine.Mathf.Sin(rr) + UnityEngine.Mathf.Cos(rr);
float camHoriz = D * UnityEngine.Mathf.Sqrt(1f + ss * ss);
float yaw = 200f;
var dir = new UnityEngine.Vector3(UnityEngine.Mathf.Sin(yaw * UnityEngine.Mathf.Deg2Rad), 0f,
UnityEngine.Mathf.Cos(yaw * UnityEngine.Mathf.Deg2Rad));
var camGo = new UnityEngine.GameObject("Main Camera");
camGo.tag = "MainCamera";
var cam = camGo.AddComponent<UnityEngine.Camera>();
camGo.AddComponent<UnityEngine.AudioListener>();
camGo.transform.position = UnityEngine.Vector3.up * camUp - dir * camHoriz;
camGo.transform.LookAt(UnityEngine.Vector3.up * lookAtY);
cam.orthographic = true;
cam.orthographicSize = 3.4f;
cam.nearClipPlane = 0.3f;
cam.farClipPlane = 500f;
cam.clearFlags = UnityEngine.CameraClearFlags.Skybox;
UnityEditor.SceneManagement.EditorSceneManager.MoveGameObjectToScene(camGo, scene);
log.AppendLine(string.Format("Camera pos={0} euler={1} camUp={2:F4} camHoriz={3:F4} down={4:F2}deg orthoSize=3.4 · 지면 깊이={5:F2} m",
camGo.transform.position, camGo.transform.eulerAngles, camUp, camHoriz,
UnityEngine.Mathf.Atan2(camUp - lookAtY, camHoriz) * UnityEngine.Mathf.Rad2Deg,
6.8f / UnityEngine.Mathf.Sin(UnityEngine.Mathf.Atan2(camUp - lookAtY, camHoriz))));
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene, ScenePath);
UnityEditor.AssetDatabase.SaveAssets();
UnityEditor.AssetDatabase.Refresh();
log.AppendLine("SAVED " + ScenePath);
log.Append(WL814q_Stats.Measure());
return log.ToString();
}
/// <summary>데모의 지면 인스턴싱(풀·꽃·자갈)을 실측 설정 그대로 얹는다. 🔴 ExecuteAlways 가 아니라 Play 에서만 그려진다.</summary>
static void AddTerrainInstancing(UnityEngine.GameObject terrGo)
{
var t = System.Type.GetType("Environment.Instancing.TerrainInstancesBehaviour, Assembly-CSharp");
if (t == null) { log.AppendLine("TerrainInstancesBehaviour type NOT FOUND — 생략"); return; }
var c = terrGo.AddComponent(t) as UnityEngine.MonoBehaviour;
var so = new UnityEditor.SerializedObject(c);
// 데모 Demo.unity 실측값
SetLayer(so, "FirstLayer", 2.5f, new string[] { "1f298817fdd2a184480e8af5b21278bc", "dab2c84bf0b5ced4aaf0cffc4c491d55" },
new string[] { "a194cb4ce6027c246af528b83b728a15", "d50a40cb47ec86444b898d9121bbe0f7" },
new float[] { 100f, 1f }, new float[] { 0.35f, 0.35f }, new float[] { 0.04f, 0.04f });
SetLayer(so, "SecondLayer", 0.05f, new string[] { "7f1f11ac2c783374b876f8dc2bb7c7db" },
new string[] { "77d84437c100f274aad603219110357d" },
new float[] { 1f }, new float[] { 0.7f }, new float[] { 0.1f });
var pv = so.FindProperty("PositionVariance"); if (pv != null) pv.floatValue = 0.5f;
var sv = so.FindProperty("ScaleVariance"); if (sv != null) sv.floatValue = 0.2f;
so.ApplyModifiedProperties();
log.AppendLine("TerrainInstancesBehaviour 적용(데모 실측값) — 에디트 모드 캡처엔 안 나옴(ExecuteAlways 아님)");
}
static void SetLayer(UnityEditor.SerializedObject so, string layer, float density,
string[] meshGuid, string[] matGuid, float[] prob, float[] scale, float[] normOff)
{
var lp = so.FindProperty(layer);
if (lp == null) { log.AppendLine("no prop " + layer); return; }
lp.FindPropertyRelative("Density").floatValue = density;
var arr = lp.FindPropertyRelative("Settings");
arr.arraySize = meshGuid.Length;
for (int i = 0; i < meshGuid.Length; i++)
{
var e = arr.GetArrayElementAtIndex(i);
e.FindPropertyRelative("Mesh").objectReferenceValue = FirstMesh(meshGuid[i]);
e.FindPropertyRelative("Material").objectReferenceValue =
UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(
UnityEditor.AssetDatabase.GUIDToAssetPath(matGuid[i]));
e.FindPropertyRelative("Probability").floatValue = prob[i];
e.FindPropertyRelative("Scale").floatValue = scale[i];
e.FindPropertyRelative("NormalOffset").floatValue = normOff[i];
}
}
static UnityEngine.Mesh FirstMesh(string guid)
{
string p = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
foreach (var o in UnityEditor.AssetDatabase.LoadAllAssetsAtPath(p))
if (o is UnityEngine.Mesh) return (UnityEngine.Mesh)o;
return null;
}
/// <summary>T 포즈를 피한다 — 컨트롤러의 idle 클립 1프레임을 씬 인스턴스에 굽는다(프리팹 수정 0).</summary>
static string PoseIdle(UnityEngine.GameObject pc)
{
var an = pc.GetComponentInChildren<UnityEngine.Animator>(true);
if (an == null) return "PoseIdle: Animator 없음";
UnityEngine.AnimationClip pick = null;
if (an.runtimeAnimatorController != null)
foreach (var cl in an.runtimeAnimatorController.animationClips)
{
if (cl == null) continue;
string n = cl.name.ToLower();
if (n.Contains("idle") || n.Contains("stand")) { pick = cl; break; }
if (pick == null) pick = cl;
}
if (pick == null) return "PoseIdle: 클립 없음(T 포즈 유지)";
bool prev = UnityEditor.AnimationMode.InAnimationMode();
if (!prev) UnityEditor.AnimationMode.StartAnimationMode();
UnityEditor.AnimationMode.BeginSampling();
UnityEditor.AnimationMode.SampleAnimationClip(an.gameObject, pick, 0.4f);
UnityEditor.AnimationMode.EndSampling();
// 🔴 StopAnimationMode 가 트랜스폼을 되돌린다 — 샘플된 포즈를 먼저 떠 두고 나중에 다시 쓴다
var ts = pc.GetComponentsInChildren<UnityEngine.Transform>(true);
var p = new UnityEngine.Vector3[ts.Length];
var q = new UnityEngine.Quaternion[ts.Length];
var s = new UnityEngine.Vector3[ts.Length];
for (int i = 0; i < ts.Length; i++)
{ p[i] = ts[i].localPosition; q[i] = ts[i].localRotation; s[i] = ts[i].localScale; }
if (!prev) UnityEditor.AnimationMode.StopAnimationMode();
for (int i = 0; i < ts.Length; i++)
{ ts[i].localPosition = p[i]; ts[i].localRotation = q[i]; ts[i].localScale = s[i]; }
return "PoseIdle: '" + pick.name + "' @0.4s 샘플(스냅샷 재적용)";
}
}
public static class WL814q_Stats
{
public static string Measure()
{
var sb = new System.Text.StringBuilder();
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
var mats = new System.Collections.Generic.HashSet<UnityEngine.Material>();
int tris = 0, rend = 0;
foreach (var root in scene.GetRootGameObjects())
foreach (var r in root.GetComponentsInChildren<UnityEngine.Renderer>(true))
{
if (r is UnityEngine.ParticleSystemRenderer) continue;
rend++;
foreach (var m in r.sharedMaterials) if (m != null) mats.Add(m);
var mf = r.GetComponent<UnityEngine.MeshFilter>();
if (mf != null && mf.sharedMesh != null) tris += mf.sharedMesh.triangles.Length / 3;
var smr = r as UnityEngine.SkinnedMeshRenderer;
if (smr != null && smr.sharedMesh != null) tris += smr.sharedMesh.triangles.Length / 3;
}
int terrTris = 0;
var t = UnityEngine.Object.FindFirstObjectByType<UnityEngine.Terrain>();
if (t != null)
{
int q = t.terrainData.heightmapResolution - 1;
terrTris = q * q * 2;
sb.AppendLine(string.Format("Terrain hmRes={0} size={1} tris(full LOD)={2} mat={3}",
t.terrainData.heightmapResolution, t.terrainData.size, terrTris,
t.materialTemplate != null ? t.materialTemplate.name : "null"));
}
sb.AppendLine(string.Format("SCENE STATS: renderers={0} materialKinds={1}(+Terrain.mat) meshTris={2} +terrain={3} TOTAL={4}",
rend, mats.Count, tris, terrTris, tris + terrTris));
var names = new System.Collections.Generic.List<string>();
foreach (var m in mats) names.Add(m.name);
names.Sort();
sb.AppendLine("MATERIALS: " + string.Join(" · ", names.ToArray()));
return sb.ToString();
}
}

View File

@ -0,0 +1,197 @@
// WL-814q — 캡처 4장(+개요) · 1080×1920 · Screenshots_WL/WL814q/
public static class WL814q_Capture
{
const string Out = "Screenshots_WL/WL814q/";
const string ScenePath = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
const string DemoPath = "Assets/3DPixelArtEnvironment/Demo/Demo.unity";
const int W = 1080, H = 1920;
static UnityEngine.Texture2D Shot(UnityEngine.Camera cam, int w, int h)
{
var rt = new UnityEngine.RenderTexture(w, h, 24, UnityEngine.RenderTextureFormat.ARGB32,
UnityEngine.RenderTextureReadWrite.sRGB);
rt.filterMode = UnityEngine.FilterMode.Point;
rt.antiAliasing = 1;
rt.Create();
var prev = cam.targetTexture;
cam.targetTexture = rt;
cam.Render();
var oldActive = UnityEngine.RenderTexture.active;
UnityEngine.RenderTexture.active = rt;
var tex = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false);
tex.ReadPixels(new UnityEngine.Rect(0, 0, w, h), 0, 0);
tex.Apply();
UnityEngine.RenderTexture.active = oldActive;
cam.targetTexture = prev;
rt.Release();
UnityEngine.Object.DestroyImmediate(rt);
return tex;
}
/// <summary>점(Point) 확대 — 도트 격자를 그대로 키운다.</summary>
static UnityEngine.Texture2D Upscale(UnityEngine.Texture2D src, int w, int h)
{
var d = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false);
var sp = src.GetPixels32();
var dp = new UnityEngine.Color32[w * h];
for (int y = 0; y < h; y++)
{
int sy = y * src.height / h; if (sy >= src.height) sy = src.height - 1;
for (int x = 0; x < w; x++)
{
int sx = x * src.width / w; if (sx >= src.width) sx = src.width - 1;
dp[y * w + x] = sp[sy * src.width + sx];
}
}
d.SetPixels32(dp); d.Apply();
return d;
}
static void Save(UnityEngine.Texture2D t, string name)
{
var dir = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), Out);
System.IO.Directory.CreateDirectory(dir);
System.IO.File.WriteAllBytes(System.IO.Path.Combine(dir, name), UnityEngine.ImageConversion.EncodeToPNG(t));
UnityEngine.Object.DestroyImmediate(t);
}
/// <summary>도트A: pixelHeight 240 저해상도 렌더 → 점 확대.</summary>
static UnityEngine.Texture2D DotA(UnityEngine.Camera cam, float orthoSize)
{
bool o = cam.orthographic; float os = cam.orthographicSize;
cam.orthographic = true; cam.orthographicSize = orthoSize;
int ph = 240, pw = UnityEngine.Mathf.RoundToInt(240f * W / H); // 135×240
var small = Shot(cam, pw, ph);
var big = Upscale(small, W, H);
UnityEngine.Object.DestroyImmediate(small);
cam.orthographic = o; cam.orthographicSize = os;
return big;
}
static UnityEngine.Texture2D SideBySide(UnityEngine.Texture2D a, UnityEngine.Texture2D b, int gap)
{
int w = a.width + gap + b.width, h = UnityEngine.Mathf.Max(a.height, b.height);
var d = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false);
var px = new UnityEngine.Color32[w * h];
for (int i = 0; i < px.Length; i++) px[i] = new UnityEngine.Color32(18, 18, 22, 255);
d.SetPixels32(px); d.Apply();
UnityEngine.Graphics.CopyTexture(a, 0, 0, 0, 0, a.width, a.height, d, 0, 0, 0, 0);
UnityEngine.Graphics.CopyTexture(b, 0, 0, 0, 0, b.width, b.height, d, 0, 0, a.width + gap, 0);
return d;
}
public static string Run()
{
var sb = new System.Text.StringBuilder();
var scene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(ScenePath,
UnityEditor.SceneManagement.OpenSceneMode.Single);
// 🔴 Water_Plane 프리팹의 PlanarReflectionProbe 도 Camera 다 — 이름으로 고른다
UnityEngine.Camera cam = null;
foreach (var c in UnityEngine.Object.FindObjectsByType<UnityEngine.Camera>(
UnityEngine.FindObjectsSortMode.None))
if (c.gameObject.name == "Main Camera") cam = c;
if (cam == null) return "NO CAMERA";
var camPos = cam.transform.position;
var camRot = cam.transform.rotation;
// 🔴 씬을 연 직후 첫 렌더는 터레인 머티리얼/스플랫이 아직 안 붙는다 — 버리는 렌더 2장으로 예열
for (int i = 0; i < 2; i++) UnityEngine.Object.DestroyImmediate(Shot(cam, 64, 64));
// ⓐ 일반 모드 — 같은 위치·각도 · 퍼스펙티브 · 풀해상도(도트 없음)
cam.orthographic = false;
// 주시점까지 거리에서 세로 화각이 ortho 3.4 와 같아지도록 FOV 를 맞춘다(같은 구도 유지)
float dist = UnityEngine.Vector3.Distance(camPos, new UnityEngine.Vector3(0f, 1.06f, 0f));
cam.fieldOfView = 2f * UnityEngine.Mathf.Atan2(3.4f, dist) * UnityEngine.Mathf.Rad2Deg;
var a = Shot(cam, W, H);
Save(a, "a_normal_1080x1920.png");
sb.AppendLine("a_normal: perspective fov=" + cam.fieldOfView.ToString("F2") + " dist=" + dist.ToString("F3"));
cam.orthographic = true; cam.orthographicSize = 3.4f;
// ⓑ 도트A — 같은 구도 · orthoSize 3.4 · pixelHeight 240
Save(DotA(cam, 3.4f), "b_dotA_1080x1920.png");
sb.AppendLine("b_dotA: ortho 3.4 · 135x240 -> point x8");
// ⓒ 캐릭터가 보이는 눈높이 구도(도트A) — 더 가깝고 낮게
// 캐릭터를 크게 — 도트A 각도(43.61°) 유지 · orthoSize 만 줄이고 주시점을 앞으로 밀어 캐릭터를 아래쪽에
cam.transform.position = camPos; cam.transform.rotation = camRot;
var fwd = cam.transform.forward; fwd.y = 0f; fwd.Normalize();
cam.transform.position = camPos + fwd * 1.9f;
Save(DotA(cam, 2.2f), "c_dotA_eye_1080x1920.png");
sb.AppendLine("c_dotA_eye: ortho 2.0 · 135x240 -> point x8 · pos=" + cam.transform.position);
// 개요(참고) — 아레나 전체 + 연못 + 풍차 (퍼스펙티브 3/4 뷰)
cam.orthographic = false;
cam.fieldOfView = 40f;
cam.transform.position = new UnityEngine.Vector3(30f, 20f, -33f);
cam.transform.LookAt(new UnityEngine.Vector3(1f, 1.5f, -1f));
Save(Shot(cam, W, H), "e_overview_1080x1920.png");
sb.AppendLine("e_overview: perspective fov40 @" + cam.transform.position);
cam.orthographic = true; cam.orthographicSize = 3.4f;
// ⓓ 비교용 — 데모의 부품은 우리보다 5~6배 크다(House 9.2 m · Tree 13.5 m vs 캐릭터 1.19 m).
// 도트A(ortho 3.4)로 데모를 찍으면 집 벽 한 조각만 나온다 → 두 씬 모두 ortho 10 · 같은 내림각 43.61° 로 맞춘다.
const float CmpOrtho = 10f;
float kk = CmpOrtho / 3.4f; // 각도 유지 · 카메라만 뒤로(near 0.3 에 잘리지 않게)
cam.transform.position = camPos * kk; cam.transform.rotation = camRot;
cam.orthographic = true; cam.orthographicSize = CmpOrtho;
var mine = DotA(cam, CmpOrtho);
var mineHalf = Upscale(mine, W / 2, H / 2);
UnityEngine.Object.DestroyImmediate(mine);
// 텍스처를 씬 전환에서 지키기
mineHalf.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
// 데모 씬 — 같은 규격(도트A)으로. 🔴 저장하지 않는다.
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(DemoPath,
UnityEditor.SceneManagement.OpenSceneMode.Single);
UnityEngine.Camera demoCam = null;
foreach (var c in UnityEngine.Object.FindObjectsByType<UnityEngine.Camera>(
UnityEngine.FindObjectsSortMode.None))
if (c.gameObject.name == "Main Camera") demoCam = c;
UnityEngine.Texture2D demoHalf = null;
if (demoCam != null)
{
// 데모의 집 앞(House (2))을 기준점으로 — 오브젝트 바운즈에서 직접 발밑을 잡는다(지형 추정 금지)
var focus = new UnityEngine.Vector3(94f, 4f, 78f);
var hg = UnityEngine.GameObject.Find("House (2)");
if (hg == null) hg = UnityEngine.GameObject.Find("Light_Post");
if (hg != null)
{
var hr = hg.GetComponentInChildren<UnityEngine.Renderer>();
if (hr != null)
{
var hb = hr.bounds;
focus = new UnityEngine.Vector3(hb.center.x, hb.min.y, hb.center.z);
sb.AppendLine("demo focus = " + hg.name + " bounds " + hb.size + " @ " + focus);
}
}
float camUp = 5.4335f, camHoriz = 4.5911f;
float yaw = 215f;
var dir = new UnityEngine.Vector3(UnityEngine.Mathf.Sin(yaw * UnityEngine.Mathf.Deg2Rad), 0f,
UnityEngine.Mathf.Cos(yaw * UnityEngine.Mathf.Deg2Rad));
float k = CmpOrtho / 3.4f; // 같은 각도를 유지한 채 카메라만 뒤로
demoCam.transform.position = focus + UnityEngine.Vector3.up * camUp * k - dir * camHoriz * k;
demoCam.transform.LookAt(focus + UnityEngine.Vector3.up * 1.06f);
demoCam.orthographic = true; demoCam.orthographicSize = CmpOrtho;
var d = DotA(demoCam, CmpOrtho);
demoHalf = Upscale(d, W / 2, H / 2);
demoHalf.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
UnityEngine.Object.DestroyImmediate(d);
sb.AppendLine("demo shot ok @" + demoCam.transform.position);
}
else sb.AppendLine("DEMO CAMERA NOT FOUND");
if (demoHalf != null)
{
var cmp = SideBySide(demoHalf, mineHalf, 16);
Save(cmp, "d_compare_demo_vs_arena.png");
sb.AppendLine("d_compare: left=Critter Demo · right=WL_ArenaProto (both dotA 240p)");
UnityEngine.Object.DestroyImmediate(demoHalf);
}
UnityEngine.Object.DestroyImmediate(mineHalf);
// 🔴 데모 씬은 저장하지 않고 원래 씬으로 되돌린다
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(ScenePath,
UnityEditor.SceneManagement.OpenSceneMode.Single);
sb.AppendLine("OUT " + System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), Out));
return sb.ToString();
}
}

View File

@ -0,0 +1,95 @@
// WL-814q 마감 — 전투장 바닥이 「흙」으로 읽히게 ToonTerrain 머티리얼 변형 1개를 우리 폴더에 만든다.
// 🔴 원본 Assets/3DPixelArtEnvironment/Materials/Terrain.mat 은 건드리지 않는다(복사본 생성).
// 되돌리기 = 씬 Terrain 의 materialTemplate 을 원본 Terrain.mat 으로 되돌리면 끝.
public static class WL814q_Polish
{
const string ScenePath = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
const string SrcMat = "Assets/3DPixelArtEnvironment/Materials/Terrain.mat";
const string MatDir = "Assets/WL/Look/Arena/Materials";
const string NewMat = MatDir + "/WL_ArenaTerrain.mat";
public static string Run()
{
var sb = new System.Text.StringBuilder();
if (!UnityEditor.AssetDatabase.IsValidFolder(MatDir))
UnityEditor.AssetDatabase.CreateFolder("Assets/WL/Look/Arena", "Materials");
if (UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(NewMat) == null)
{
if (!UnityEditor.AssetDatabase.CopyAsset(SrcMat, NewMat)) return "CopyAsset FAILED";
UnityEditor.AssetDatabase.Refresh();
}
var m = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(NewMat);
if (m == null) return "NEW MAT NOT FOUND";
// 데모 원본: _GreenHighlight(0.840,0.830,0.646 카키) → 렌더 결과가 풀과 거의 같은 초록(실측 152,188,119 vs 170,203,126)
// 전투장 바닥이 흙으로 읽히도록 이 채널만 Trunk.mat 계열 따뜻한 흙색으로 바꾼다. 나머지 값은 원본 그대로.
m.SetColor("_GreenHighlight", new UnityEngine.Color(0.7924528f, 0.6389284f, 0.5121039f, 0f));
m.SetColor("_GreenShadow", new UnityEngine.Color(0.3254902f, 0.2352941f, 0.1490196f, 0f));
UnityEditor.EditorUtility.SetDirty(m);
UnityEditor.AssetDatabase.SaveAssets();
sb.AppendLine("MAT " + NewMat + " _GreenHighlight=" + m.GetColor("_GreenHighlight"));
var scene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(ScenePath,
UnityEditor.SceneManagement.OpenSceneMode.Single);
var t = UnityEngine.Object.FindFirstObjectByType<UnityEngine.Terrain>();
if (t == null) return sb + "NO TERRAIN";
t.materialTemplate = m;
UnityEditor.EditorUtility.SetDirty(t);
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(scene);
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene, ScenePath);
UnityEditor.AssetDatabase.SaveAssets();
sb.AppendLine("Terrain.materialTemplate = " + t.materialTemplate.name + " (shader " + t.materialTemplate.shader.name + ")");
return sb.ToString();
}
/// <summary>실측 결과 되돌리기 — ToonTerrain 은 터레인 스플랫을 쓰지 않는다(Probe3 실측:
/// _RedHighlight 만 화면에 나오고 _GreenHighlight 는 어디에도 안 나온다). 흙 채널 변형은 의미가 없으므로
/// 데모 원본 Terrain.mat 로 되돌리고 만들었던 변형 머티리얼은 지운다 = 데모와 편차 0.</summary>
public static string Revert()
{
var scene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(ScenePath,
UnityEditor.SceneManagement.OpenSceneMode.Single);
var t = UnityEngine.Object.FindFirstObjectByType<UnityEngine.Terrain>();
if (t == null) return "NO TERRAIN";
t.materialTemplate = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(SrcMat);
UnityEditor.EditorUtility.SetDirty(t);
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(scene);
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene, ScenePath);
UnityEditor.AssetDatabase.DeleteAsset(NewMat);
UnityEditor.AssetDatabase.DeleteAsset(MatDir);
UnityEditor.AssetDatabase.SaveAssets();
UnityEditor.AssetDatabase.Refresh();
return "Terrain.materialTemplate = " + t.materialTemplate.name + " (원본) · 변형 머티리얼 삭제";
}
/// <summary>캐릭터 실측 — 무기 제외 몸 높이 · 전체 바운즈 · 바인드포즈 기준 높이.</summary>
public static string MeasurePc()
{
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(ScenePath,
UnityEditor.SceneManagement.OpenSceneMode.Single);
var pc = UnityEngine.GameObject.Find("PC_LH_M05");
if (pc == null) return "PC NOT FOUND";
var all = new UnityEngine.Bounds(); var body = new UnityEngine.Bounds();
bool fa = true, fb = true;
foreach (var r in pc.GetComponentsInChildren<UnityEngine.Renderer>(true))
{
if (fa) { all = r.bounds; fa = false; } else all.Encapsulate(r.bounds);
bool weapon = false;
foreach (var mm in r.sharedMaterials)
if (mm != null && mm.name.ToLower().Contains("sword")) weapon = true;
if (weapon) continue;
if (fb) { body = r.bounds; fb = false; } else body.Encapsulate(r.bounds);
}
// 바인드 포즈(원 프리팹) 기준 높이 — 인게임 기준값과 대조용
var src = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>("Assets/Res_Addr/PC/LH_M05.prefab");
var tmp = (UnityEngine.GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(src);
tmp.transform.position = UnityEngine.Vector3.zero;
tmp.transform.localScale = UnityEngine.Vector3.one * (0.7f * 1.5038f);
var bp = new UnityEngine.Bounds(); bool fc = true;
foreach (var r in tmp.GetComponentsInChildren<UnityEngine.Renderer>(true))
{ if (fc) { bp = r.bounds; fc = false; } else bp.Encapsulate(r.bounds); }
UnityEngine.Object.DestroyImmediate(tmp);
return string.Format(
"PC scale={0:F5} · 바인드포즈 바운즈 높이={1:F4} m · 씬(Idle 포즈) 몸(무기 제외)={2:F4} m · 전체(무기 포함)={3:F4} m · 발끝 y={4:F4}",
0.7f * 1.5038f, bp.size.y, body.size.y, all.size.y, body.min.y);
}
}

View File

@ -0,0 +1,78 @@
public static class WL814q_Probe
{
static string V(UnityEngine.Vector3 v)
{
return string.Format("({0:F3},{1:F3},{2:F3})", v.x, v.y, v.z);
}
public static string Run()
{
var sb = new System.Text.StringBuilder();
string[] names = { "Arch_Large", "Arch_Small", "House", "Light_Post", "Slab", "Stone_1", "Stone_2", "Tree", "Water_Plane", "Windmill" };
sb.AppendLine("prefab | worldSize(x,y,z) m @scale1 | center | tris | mats | renderers | colliders");
foreach (var n in names)
{
string path = "Assets/3DPixelArtEnvironment/Prefabs/" + n + ".prefab";
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(path);
if (go == null) { sb.AppendLine(n + " | NOT FOUND"); continue; }
var inst = (UnityEngine.GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(go);
inst.transform.position = UnityEngine.Vector3.zero;
inst.transform.rotation = UnityEngine.Quaternion.identity;
var rs = inst.GetComponentsInChildren<UnityEngine.Renderer>(true);
var b = new UnityEngine.Bounds(inst.transform.position, UnityEngine.Vector3.zero);
bool first = true;
int tris = 0;
var mats = new System.Collections.Generic.HashSet<string>();
foreach (var r in rs)
{
if (first) { b = r.bounds; first = false; } else b.Encapsulate(r.bounds);
foreach (var m in r.sharedMaterials) if (m != null) mats.Add(m.name);
var mf = r.GetComponent<UnityEngine.MeshFilter>();
if (mf != null && mf.sharedMesh != null) tris += mf.sharedMesh.triangles.Length / 3;
}
var cols = inst.GetComponentsInChildren<UnityEngine.Collider>(true);
string ctxt = "";
foreach (var c in cols) ctxt += c.GetType().Name + ",";
sb.AppendLine(string.Format("{0} | {1} | c={2} | tris={3} | mats={4} | rend={5} | col=[{6}]",
n, V(b.size), V(b.center), tris, string.Join("/", System.Linq.Enumerable.ToArray(mats)), rs.Length, ctxt));
UnityEngine.Object.DestroyImmediate(inst);
}
// Terrain data
var td = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.TerrainData>("Assets/3DPixelArtEnvironment/Demo/Demo_Terrain.asset");
if (td != null)
sb.AppendLine(string.Format("TerrainData size={0} heightmapRes={1} layers={2} detailW={3}",
V(td.size), td.heightmapResolution, td.terrainLayers.Length, td.detailWidth));
// Character prefab
var pc = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>("Assets/Res_Addr/PC/LH_M05.prefab");
if (pc != null)
{
var pi = (UnityEngine.GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(pc);
pi.transform.position = UnityEngine.Vector3.zero;
var rs = pi.GetComponentsInChildren<UnityEngine.Renderer>(true);
var b = new UnityEngine.Bounds(UnityEngine.Vector3.zero, UnityEngine.Vector3.zero);
bool f = true; int tris = 0;
foreach (var r in rs)
{
if (f) { b = r.bounds; f = false; } else b.Encapsulate(r.bounds);
var smr = r as UnityEngine.SkinnedMeshRenderer;
if (smr != null && smr.sharedMesh != null) tris += smr.sharedMesh.triangles.Length / 3;
var mf = r.GetComponent<UnityEngine.MeshFilter>();
if (mf != null && mf.sharedMesh != null) tris += mf.sharedMesh.triangles.Length / 3;
}
sb.AppendLine(string.Format("LH_M05 prefabScale={0} worldSize={1} min.y={2:F4} max.y={3:F4} tris={4} rend={5}",
V(pi.transform.localScale), V(b.size), b.min.y, b.max.y, tris, rs.Length));
UnityEngine.Object.DestroyImmediate(pi);
}
else sb.AppendLine("LH_M05 NOT FOUND");
// Stage table
var st = UnityEditor.AssetDatabase.FindAssets("WLStageTable");
foreach (var g in st) sb.AppendLine("StageTable: " + UnityEditor.AssetDatabase.GUIDToAssetPath(g));
var ls = UnityEditor.AssetDatabase.FindAssets("WLLookModeSettings");
foreach (var g in ls) sb.AppendLine("LookModeSettings: " + UnityEditor.AssetDatabase.GUIDToAssetPath(g));
return sb.ToString();
}
}

View File

@ -0,0 +1,56 @@
public static class WL814q_Probe2
{
public static string Run()
{
var sb = new System.Text.StringBuilder();
string[] paths = {
"Assets/WL/Look/Arena/Terrain/WL_ArenaTerrain.asset",
"Assets/3DPixelArtEnvironment/Demo/Demo_Terrain.asset"
};
foreach (var p in paths)
{
var td = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.TerrainData>(p);
if (td == null) { sb.AppendLine(p + " NOT FOUND"); continue; }
sb.AppendLine(p + " layers=" + td.terrainLayers.Length + " alphaRes=" + td.alphamapResolution
+ " alphaTex=" + td.alphamapTextureCount);
for (int i = 0; i < td.terrainLayers.Length; i++)
sb.AppendLine(" layer[" + i + "] = " + (td.terrainLayers[i] != null ? td.terrainLayers[i].name : "null"));
var a = td.GetAlphamaps(0, 0, td.alphamapWidth, td.alphamapHeight);
int n = a.GetLength(2);
var mx = new float[n]; var sum = new float[n];
for (int y = 0; y < a.GetLength(0); y++)
for (int x = 0; x < a.GetLength(1); x++)
for (int c = 0; c < n; c++) { if (a[y, x, c] > mx[c]) mx[c] = a[y, x, c]; sum[c] += a[y, x, c]; }
for (int c = 0; c < n; c++)
sb.AppendLine(string.Format(" ch{0} max={1:F3} mean={2:F3}", c, mx[c], sum[c] / (a.GetLength(0) * a.GetLength(1))));
// 중앙(아레나 안) 샘플
if (p.Contains("Arena"))
{
int cx = td.alphamapWidth / 2, cy = td.alphamapHeight / 2;
sb.AppendLine(string.Format(" center px: ch0={0:F3} ch1={1:F3}", a[cy, cx, 0], a[cy, cx, 1]));
}
}
var m = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(
"Assets/3DPixelArtEnvironment/Materials/Terrain.mat");
if (m != null && m.shader != null)
{
sb.AppendLine("Terrain.mat shader=" + m.shader.name + " props=" + m.shader.GetPropertyCount());
for (int i = 0; i < m.shader.GetPropertyCount(); i++)
{
string pn = m.shader.GetPropertyName(i);
if (pn.Contains("Control") || pn.Contains("Splat") || pn.Contains("Terrain") || pn.Contains("Layer")
|| pn.Contains("Highlight") || pn.Contains("Shadow"))
sb.AppendLine(" " + pn + " : " + m.shader.GetPropertyType(i));
}
}
// 우리 터레인 GO 상태
var t = UnityEngine.Object.FindFirstObjectByType<UnityEngine.Terrain>();
if (t != null)
{
sb.AppendLine("scene Terrain mat=" + (t.materialTemplate != null ? t.materialTemplate.name : "null")
+ " shader=" + (t.materialTemplate != null && t.materialTemplate.shader != null ? t.materialTemplate.shader.name : "?")
+ " drawInstanced=" + t.drawInstanced + " basemapDist=" + t.basemapDistance);
}
return sb.ToString();
}
}

View File

@ -0,0 +1,61 @@
// ToonTerrain 이 터레인 스플랫(_Control) 을 실제로 쓰는지 최종 판정.
// R=마젠타 · G=시안 으로 동시에 바꾼 뒤 아레나 카메라(각도 43.61°)와 탑다운 둘 다 찍는다.
public static class WL814q_Probe3
{
const string ScenePath = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
const string NewMat = "Assets/WL/Look/Arena/Materials/WL_ArenaTerrain.mat";
static UnityEngine.Texture2D Shot(UnityEngine.Camera cam, int w, int h)
{
var rt = new UnityEngine.RenderTexture(w, h, 24, UnityEngine.RenderTextureFormat.ARGB32,
UnityEngine.RenderTextureReadWrite.sRGB);
rt.Create(); cam.targetTexture = rt; cam.Render();
var old = UnityEngine.RenderTexture.active; UnityEngine.RenderTexture.active = rt;
var tex = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false);
tex.ReadPixels(new UnityEngine.Rect(0, 0, w, h), 0, 0); tex.Apply();
UnityEngine.RenderTexture.active = old; cam.targetTexture = null;
rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
return tex;
}
static string Px(UnityEngine.Texture2D t, int x, int y)
{
var c = t.GetPixel(x, y);
return string.Format("({0},{1},{2})", (int)(c.r * 255), (int)(c.g * 255), (int)(c.b * 255));
}
public static string Run()
{
var sb = new System.Text.StringBuilder();
var m = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(NewMat);
var red = m.GetColor("_RedHighlight"); var grn = m.GetColor("_GreenHighlight");
m.SetColor("_RedHighlight", new UnityEngine.Color(1f, 0f, 1f, 0f)); // 마젠타
m.SetColor("_GreenHighlight", new UnityEngine.Color(0f, 1f, 1f, 0f)); // 시안
UnityEditor.EditorUtility.SetDirty(m); UnityEditor.AssetDatabase.SaveAssets();
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(ScenePath,
UnityEditor.SceneManagement.OpenSceneMode.Single);
UnityEngine.Camera cam = null;
foreach (var c in UnityEngine.Object.FindObjectsByType<UnityEngine.Camera>(UnityEngine.FindObjectsSortMode.None))
if (c.gameObject.name == "Main Camera") cam = c;
for (int i = 0; i < 3; i++) UnityEngine.Object.DestroyImmediate(Shot(cam, 64, 64));
var a = Shot(cam, 540, 960); // 아레나 도트A 각도 · 화면 전부가 반경 5 m 안쪽(스플랫상 흙 1.0)
sb.AppendLine("arena-cam(ortho3.4) 지면: " + Px(a, 120, 620) + " " + Px(a, 430, 200) + " " + Px(a, 270, 350));
UnityEngine.Object.DestroyImmediate(a);
cam.orthographic = true; cam.orthographicSize = 22f;
cam.transform.position = new UnityEngine.Vector3(0f, 30f, 0f);
cam.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f);
var b = Shot(cam, 540, 960);
// (270,480)=중앙(캐릭터가 있으니 살짝 비켜서 샘플) · 안쪽 r≈4 m · 바깥 r≈20 m
sb.AppendLine("top-down 중앙옆(r~2m): " + Px(b, 300, 480) + " r~5m: " + Px(b, 340, 560)
+ " r~20m: " + Px(b, 60, 100) + " " + Px(b, 500, 880));
UnityEngine.Object.DestroyImmediate(b);
m.SetColor("_RedHighlight", red); m.SetColor("_GreenHighlight", grn);
UnityEditor.EditorUtility.SetDirty(m); UnityEditor.AssetDatabase.SaveAssets();
sb.AppendLine("restored R=" + m.GetColor("_RedHighlight") + " G=" + m.GetColor("_GreenHighlight"));
return sb.ToString();
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 618fa1ecbb3878749a4f77406b382564
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 201987a476308434cb6f30de76035b0d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: be57fe49225e2ce4a880f566860efa9a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7b3bbee3e0de6134cbc83b04f9292692
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0429ecb77ea323f43aac1e8485a924f2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 15600000
userData:
assetBundleName:
assetBundleVariant: