Project_WL/AgentScripts/WL814q_BuildArena.cs

478 lines
27 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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();
}
}