Project_WL/AgentScripts/WL814q_BuildArena.cs

478 lines
27 KiB
C#
Raw Permalink Normal View History

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