664 lines
38 KiB
C#
664 lines
38 KiB
C#
|
|
using System.Collections.Generic;
|
||
|
|
using System.Linq;
|
||
|
|
|
||
|
|
// PD 지시 #771 2단계 — WL_Nature 맵 마무리(조명·MapData·볼륨·기상연출·인스턴싱) + 검증 + 캡처
|
||
|
|
// 실행 예
|
||
|
|
// 조명 이식 : unity command run_script --file AgentScripts/WL_NatureFinish.cs --entry WL_NatureFinish.Lighting --args '["Assets/LMHPOLY/Low Poly Nature Bundle/_Demo Scenes/Demo_01.unity","Assets/Res_Addr/Map/WL_Nature.prefab", 30.0, 250.0]'
|
||
|
|
// 구성 : ... --entry WL_NatureFinish.Setup --args '["Assets/Res_Addr/Map/WL_Nature.prefab"]'
|
||
|
|
// 인스턴싱 : ... --entry WL_NatureFinish.Instancing --args '["Assets/Res_Addr/Map/WL_Nature.prefab"]'
|
||
|
|
// 검증 : ... --entry WL_NatureFinish.Verify --args '["Assets/Res_Addr/Map/WL_Nature.prefab"]'
|
||
|
|
// 도달성 : ... --entry WL_NatureFinish.Reach --args '["Assets/Res_Addr/Map/WL_Nature.prefab","Assets/WL/Settings/WL_Nature_NavMesh.asset", 4.0]'
|
||
|
|
// 캡처 : ... --entry WL_NatureFinish.Shots --args '["Assets/Res_Addr/Map/WL_Nature.prefab", 100.0]'
|
||
|
|
public static class WL_NatureFinish
|
||
|
|
{
|
||
|
|
const string OUT = @"E:\NerdNavis\nn_himminji\AgentScripts\staging\WL_Nature2\out";
|
||
|
|
const string SHOT = @"E:\NerdNavis\nn_himminji\Screenshots_WL\nature";
|
||
|
|
const string kWakeSettings = "Assets/WL/Settings/StartWakeSettings.asset";
|
||
|
|
const string kPostProfile = "Assets/WL/Settings/WL_Nature_Post.asset";
|
||
|
|
|
||
|
|
// 게임 시점 카메라 실측값 (Main Camera.prefab arr_cCamData[0] · RealCamera.cs · 1단계와 동일)
|
||
|
|
const float CAM_PITCH = 65f, CAM_DIST = 3f, CAM_UP = 2f, CAM_FOV = 60f;
|
||
|
|
const int W = 1280, H = 720;
|
||
|
|
|
||
|
|
static System.Text.StringBuilder sb;
|
||
|
|
static void L(string s) { sb.AppendLine(s); }
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
// 0) 지정한 머티리얼만 Built-in → URP 변환 (Q3 · 세미콜론 구분 경로 목록)
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
public static object UpgradeMats(string pathsSemicolon)
|
||
|
|
{
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
if (UnityEngine.Application.isPlaying) return "PLAYING - 중단";
|
||
|
|
var ups = UnityEditor.Rendering.MaterialUpgrader.FetchAllUpgradersForPipeline(
|
||
|
|
typeof(UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset));
|
||
|
|
L("업그레이더 " + ups.Count + "개");
|
||
|
|
int changed = 0, kept = 0;
|
||
|
|
foreach (var raw in pathsSemicolon.Split(';'))
|
||
|
|
{
|
||
|
|
var p = raw.Trim();
|
||
|
|
if (p.Length == 0) continue;
|
||
|
|
var m = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(p);
|
||
|
|
if (m == null) { L(" [없음] " + p); continue; }
|
||
|
|
string before = m.shader == null ? "<null>" : m.shader.name;
|
||
|
|
string bColor = m.HasProperty("_Color") ? m.GetColor("_Color").ToString() : "-";
|
||
|
|
string msg = null;
|
||
|
|
try { UnityEditor.Rendering.MaterialUpgrader.Upgrade(m, ups, UnityEditor.Rendering.MaterialUpgrader.UpgradeFlags.None, ref msg); }
|
||
|
|
catch (System.Exception e) { msg = "EXCEPTION " + e.Message; }
|
||
|
|
string after = m.shader == null ? "<null>" : m.shader.name;
|
||
|
|
string aColor = m.HasProperty("_BaseColor") ? m.GetColor("_BaseColor").ToString()
|
||
|
|
: (m.HasProperty("_Color") ? m.GetColor("_Color").ToString() : "-");
|
||
|
|
if (before != after) { changed++; L(" OK " + before + " -> " + after + " color " + bColor + " -> " + aColor + " " + p); }
|
||
|
|
else { kept++; L(" -- " + before + " (변화 없음) " + p + (string.IsNullOrEmpty(msg) ? "" : " msg=" + msg)); }
|
||
|
|
}
|
||
|
|
UnityEditor.AssetDatabase.SaveAssets();
|
||
|
|
UnityEditor.AssetDatabase.Refresh();
|
||
|
|
Flush("F0_upgrade.txt");
|
||
|
|
return "changed=" + changed + " unchanged=" + kept;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
// 1) 원본 씬의 조명·안개·스카이박스·앰비언트를 MapData 로 이식
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
public static object Lighting(string srcScene, string prefabPath, float fogStart, float fogEnd)
|
||
|
|
{
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
if (UnityEngine.Application.isPlaying) return "PLAYING - 중단";
|
||
|
|
var setup = UnityEditor.SceneManagement.EditorSceneManager.GetSceneManagerSetup();
|
||
|
|
|
||
|
|
UnityEngine.Rendering.AmbientMode am; UnityEngine.Color aSky, aEq, aGr, fogColor;
|
||
|
|
float aInt, refl; UnityEngine.Material sky; bool fogOn; UnityEngine.FogMode fogMode; float fogDensity;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(srcScene, UnityEditor.SceneManagement.OpenSceneMode.Single);
|
||
|
|
am = UnityEngine.RenderSettings.ambientMode;
|
||
|
|
aSky = UnityEngine.RenderSettings.ambientSkyColor;
|
||
|
|
aEq = UnityEngine.RenderSettings.ambientEquatorColor;
|
||
|
|
aGr = UnityEngine.RenderSettings.ambientGroundColor;
|
||
|
|
aInt = UnityEngine.RenderSettings.ambientIntensity;
|
||
|
|
refl = UnityEngine.RenderSettings.reflectionIntensity;
|
||
|
|
sky = UnityEngine.RenderSettings.skybox;
|
||
|
|
fogOn = UnityEngine.RenderSettings.fog;
|
||
|
|
fogMode = UnityEngine.RenderSettings.fogMode;
|
||
|
|
fogColor = UnityEngine.RenderSettings.fogColor;
|
||
|
|
fogDensity = UnityEngine.RenderSettings.fogDensity;
|
||
|
|
L("원본 씬 실측 " + srcScene);
|
||
|
|
L(" ambientMode=" + am + " ambientLight=" + UnityEngine.RenderSettings.ambientLight
|
||
|
|
+ " sky=" + aSky + " eq=" + aEq + " gr=" + aGr + " intensity=" + aInt + " reflection=" + refl);
|
||
|
|
L(" fog=" + fogOn + " mode=" + fogMode + " color=" + fogColor
|
||
|
|
+ " start=" + UnityEngine.RenderSettings.fogStartDistance + " end=" + UnityEngine.RenderSettings.fogEndDistance
|
||
|
|
+ " density=" + fogDensity);
|
||
|
|
L(" skybox=" + (sky == null ? "(없음)" : UnityEditor.AssetDatabase.GetAssetPath(sky)));
|
||
|
|
if (am == UnityEngine.Rendering.AmbientMode.Flat) aSky = UnityEngine.RenderSettings.ambientLight; // Flat 은 ambientLight 가 SOT
|
||
|
|
ClearDirty();
|
||
|
|
}
|
||
|
|
finally { try { if (setup != null && setup.Length > 0) UnityEditor.SceneManagement.EditorSceneManager.RestoreSceneManagerSetup(setup); } catch { } }
|
||
|
|
|
||
|
|
var root = UnityEditor.PrefabUtility.LoadPrefabContents(prefabPath);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var md = root.GetComponent<MapData>();
|
||
|
|
if (md == null) { L("[오류] MapData 없음"); Flush("F1_lighting.txt"); return "no MapData"; }
|
||
|
|
md.material_skybox = sky;
|
||
|
|
md.ambientMode = am;
|
||
|
|
md.ambientSky = aSky; md.ambientEquator = aEq; md.ambientGround = aGr;
|
||
|
|
md.ambientIntensity = aInt; md.reflectionIntensity = refl;
|
||
|
|
md.updateEnvironmentFromSkybox = (am == UnityEngine.Rendering.AmbientMode.Skybox);
|
||
|
|
md.showWeaponInLobby = true; // #756 관례 — 시작 맵은 무기 표시
|
||
|
|
|
||
|
|
// 안개 (MapData 신규 필드 · 기본값 = 기존 동작(끔))
|
||
|
|
var so = new UnityEditor.SerializedObject(md);
|
||
|
|
SetB(so, "fogEnabled", fogOn);
|
||
|
|
SetI(so, "fogMode", (int)fogMode);
|
||
|
|
SetC(so, "fogColor", fogColor);
|
||
|
|
SetF(so, "fogStart", fogStart);
|
||
|
|
SetF(so, "fogEnd", fogEnd);
|
||
|
|
SetF(so, "fogDensity", fogDensity);
|
||
|
|
so.ApplyModifiedPropertiesWithoutUndo();
|
||
|
|
UnityEditor.EditorUtility.SetDirty(md);
|
||
|
|
|
||
|
|
UnityEditor.PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
||
|
|
L("MapData 갱신: ambientMode=" + am + " ambientSky=" + aSky + " skybox=" + (sky == null ? "-" : sky.name)
|
||
|
|
+ " fog=" + fogOn + "/" + fogMode + " start=" + fogStart + " end=" + fogEnd);
|
||
|
|
}
|
||
|
|
finally { UnityEditor.PrefabUtility.UnloadPrefabContents(root); }
|
||
|
|
UnityEditor.AssetDatabase.SaveAssets();
|
||
|
|
Flush("F1_lighting.txt");
|
||
|
|
return "lighting applied (fog " + fogStart + "~" + fogEnd + ")";
|
||
|
|
}
|
||
|
|
|
||
|
|
static void SetB(UnityEditor.SerializedObject so, string n, bool v) { var p = so.FindProperty(n); if (p != null) p.boolValue = v; else L(" [주의] 필드 없음 " + n); }
|
||
|
|
static void SetI(UnityEditor.SerializedObject so, string n, int v) { var p = so.FindProperty(n); if (p != null) p.intValue = v; else L(" [주의] 필드 없음 " + n); }
|
||
|
|
static void SetF(UnityEditor.SerializedObject so, string n, float v) { var p = so.FindProperty(n); if (p != null) p.floatValue = v; else L(" [주의] 필드 없음 " + n); }
|
||
|
|
static void SetC(UnityEditor.SerializedObject so, string n, UnityEngine.Color v) { var p = so.FindProperty(n); if (p != null) p.colorValue = v; else L(" [주의] 필드 없음 " + n); }
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
// 2) 구성 — MobControlMgrList · #764 필드 · StartWakeSequence · Global Volume
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
public static object Setup(string prefabPath)
|
||
|
|
{
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
if (UnityEngine.Application.isPlaying) return "PLAYING - 중단";
|
||
|
|
|
||
|
|
// 포스트 프로파일 복제 (WL_Start_Post.asset → WL_Nature_Post.asset)
|
||
|
|
if (UnityEditor.AssetDatabase.LoadMainAssetAtPath(kPostProfile) == null)
|
||
|
|
{
|
||
|
|
bool ok = UnityEditor.AssetDatabase.CopyAsset("Assets/WL/Settings/WL_Start_Post.asset", kPostProfile);
|
||
|
|
UnityEditor.AssetDatabase.SaveAssets();
|
||
|
|
L("포스트 프로파일 복제 " + (ok ? "OK" : "실패") + " → " + kPostProfile);
|
||
|
|
}
|
||
|
|
else L("포스트 프로파일 이미 존재: " + kPostProfile);
|
||
|
|
var prof = UnityEditor.AssetDatabase.LoadMainAssetAtPath(kPostProfile);
|
||
|
|
|
||
|
|
var wake = UnityEditor.AssetDatabase.LoadAssetAtPath<WL.Start.StartWakeSettings>(kWakeSettings);
|
||
|
|
if (wake == null) L("[주의] StartWakeSettings 에셋 없음: " + kWakeSettings);
|
||
|
|
|
||
|
|
var root = UnityEditor.PrefabUtility.LoadPrefabContents(prefabPath);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
// MobControlMgrList (루트)
|
||
|
|
if (root.GetComponent<MobControlMgrList>() == null) { root.AddComponent<MobControlMgrList>(); L("MobControlMgrList 추가"); }
|
||
|
|
else L("MobControlMgrList 이미 있음");
|
||
|
|
|
||
|
|
// #764 필드
|
||
|
|
var md = root.GetComponent<MapData>();
|
||
|
|
if (md == null) L("[오류] MapData 없음");
|
||
|
|
else
|
||
|
|
{
|
||
|
|
md.spawnRequireReachableFromPlayer = true;
|
||
|
|
md.spawnSnapRadius = 2f;
|
||
|
|
md.spawnSampleTries = 50;
|
||
|
|
md.mobNavSnapRadius = 2f;
|
||
|
|
UnityEditor.EditorUtility.SetDirty(md);
|
||
|
|
L("#764 필드: reachable=1 snap=2 tries=50 mobSnap=2");
|
||
|
|
}
|
||
|
|
|
||
|
|
// StartWakeSequence
|
||
|
|
var seq = root.GetComponent<WL.Start.StartWakeSequence>();
|
||
|
|
if (seq == null) { seq = root.AddComponent<WL.Start.StartWakeSequence>(); L("StartWakeSequence 추가"); }
|
||
|
|
if (wake != null)
|
||
|
|
{
|
||
|
|
var sox = new UnityEditor.SerializedObject(seq);
|
||
|
|
var p = sox.FindProperty("settings");
|
||
|
|
if (p != null) { p.objectReferenceValue = wake; sox.ApplyModifiedPropertiesWithoutUndo(); L("StartWakeSequence.settings = " + kWakeSettings); }
|
||
|
|
else L("[주의] settings 프로퍼티 없음");
|
||
|
|
}
|
||
|
|
|
||
|
|
// SceneAssets / Global Volume
|
||
|
|
var holder = root.transform.Find("SceneAssets");
|
||
|
|
if (holder == null)
|
||
|
|
{
|
||
|
|
var go = new UnityEngine.GameObject("SceneAssets");
|
||
|
|
go.transform.SetParent(root.transform, false);
|
||
|
|
holder = go.transform;
|
||
|
|
L("SceneAssets 생성");
|
||
|
|
}
|
||
|
|
var volType = System.Type.GetType("UnityEngine.Rendering.Volume, Unity.RenderPipelines.Core.Runtime");
|
||
|
|
if (volType == null) L("[주의] Volume 타입을 찾지 못했다 — 포스트 볼륨 생략");
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var vt = holder.Find("Global Volume");
|
||
|
|
UnityEngine.GameObject vgo;
|
||
|
|
if (vt == null) { vgo = new UnityEngine.GameObject("Global Volume"); vgo.transform.SetParent(holder, false); L("Global Volume 생성"); }
|
||
|
|
else vgo = vt.gameObject;
|
||
|
|
var vol = vgo.GetComponent(volType);
|
||
|
|
if (vol == null) vol = vgo.AddComponent(volType);
|
||
|
|
var vso = new UnityEditor.SerializedObject(vol);
|
||
|
|
var sp = vso.FindProperty("sharedProfile");
|
||
|
|
if (sp != null && prof != null) sp.objectReferenceValue = prof;
|
||
|
|
var ig = vso.FindProperty("isGlobal"); if (ig != null) ig.boolValue = true;
|
||
|
|
var pr = vso.FindProperty("priority"); if (pr != null) pr.floatValue = 0f;
|
||
|
|
vso.ApplyModifiedPropertiesWithoutUndo();
|
||
|
|
L("Global Volume: profile=" + (prof == null ? "(null)" : prof.name) + " isGlobal=true");
|
||
|
|
}
|
||
|
|
|
||
|
|
UnityEditor.PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
||
|
|
L("프리팹 저장: " + prefabPath);
|
||
|
|
}
|
||
|
|
finally { UnityEditor.PrefabUtility.UnloadPrefabContents(root); }
|
||
|
|
UnityEditor.AssetDatabase.SaveAssets();
|
||
|
|
Flush("F2_setup.txt");
|
||
|
|
return "setup done";
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
// 3) GPU 인스턴싱 — 맵이 쓰는 모든 머티리얼
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
public static object Instancing(string prefabPath)
|
||
|
|
{
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
if (UnityEngine.Application.isPlaying) return "PLAYING - 중단";
|
||
|
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(prefabPath);
|
||
|
|
if (go == null) return "no prefab";
|
||
|
|
var mats = new HashSet<UnityEngine.Material>();
|
||
|
|
foreach (var r in go.GetComponentsInChildren<UnityEngine.Renderer>(true))
|
||
|
|
foreach (var m in r.sharedMaterials) if (m != null) mats.Add(m);
|
||
|
|
int on = 0, already = 0;
|
||
|
|
foreach (var m in mats.OrderBy(x => x.name))
|
||
|
|
{
|
||
|
|
var p = UnityEditor.AssetDatabase.GetAssetPath(m);
|
||
|
|
if (string.IsNullOrEmpty(p) || !p.StartsWith("Assets/")) { L(" [건너뜀 · 에셋 아님] " + m.name); continue; }
|
||
|
|
if (m.enableInstancing) { already++; L(" [이미 켜짐] " + m.shader.name + " " + p); continue; }
|
||
|
|
m.enableInstancing = true;
|
||
|
|
UnityEditor.EditorUtility.SetDirty(m);
|
||
|
|
on++;
|
||
|
|
L(" [켬] " + m.shader.name + " " + p);
|
||
|
|
}
|
||
|
|
UnityEditor.AssetDatabase.SaveAssets();
|
||
|
|
L("머티리얼 " + mats.Count + "개 중 인스턴싱 신규 " + on + "개 · 기존 " + already + "개");
|
||
|
|
Flush("F3_instancing.txt");
|
||
|
|
return "materials=" + mats.Count + " enabled=" + on + " already=" + already;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
// 4) 검증 — 셰이더 전수 · 스포너 · 통계
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
public static object Verify(string prefabPath)
|
||
|
|
{
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(prefabPath);
|
||
|
|
if (go == null) return "no prefab";
|
||
|
|
|
||
|
|
// 셰이더 전수 (마젠타 판정)
|
||
|
|
var byShader = new Dictionary<string, int>();
|
||
|
|
var bad = new List<string>();
|
||
|
|
int nullSlots = 0, rend = 0;
|
||
|
|
long tris = 0, trisLod0 = 0;
|
||
|
|
foreach (var r in go.GetComponentsInChildren<UnityEngine.Renderer>(true))
|
||
|
|
{
|
||
|
|
rend++;
|
||
|
|
var ms = r.sharedMaterials;
|
||
|
|
for (int i = 0; i < ms.Length; i++)
|
||
|
|
{
|
||
|
|
if (ms[i] == null) { nullSlots++; continue; }
|
||
|
|
var sn = ms[i].shader == null ? "<null>" : ms[i].shader.name;
|
||
|
|
if (!byShader.ContainsKey(sn)) byShader[sn] = 0;
|
||
|
|
byShader[sn]++;
|
||
|
|
if (sn == "Hidden/InternalErrorShader" || sn == "Standard" || sn.StartsWith("Standard (")
|
||
|
|
|| sn.StartsWith("Legacy Shaders/") || sn.StartsWith("Mobile/"))
|
||
|
|
bad.Add(sn + " " + Path(r.transform) + " <- " + UnityEditor.AssetDatabase.GetAssetPath(ms[i]));
|
||
|
|
}
|
||
|
|
var mf = r.GetComponent<UnityEngine.MeshFilter>();
|
||
|
|
if (mf != null && mf.sharedMesh != null)
|
||
|
|
{
|
||
|
|
long t = mf.sharedMesh.triangles.Length / 3;
|
||
|
|
tris += t;
|
||
|
|
if (!r.name.Contains("LOD1") && !r.name.Contains("LOD2")) trisLod0 += t;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
L("== 렌더러 " + rend + "개 · 셰이더 분포 ==");
|
||
|
|
foreach (var kv in byShader.OrderByDescending(k => k.Value)) L(string.Format(" [{0,5}] {1}", kv.Value, kv.Key));
|
||
|
|
L("null 머티리얼 슬롯 = " + nullSlots);
|
||
|
|
L("== 마젠타 위험 슬롯 = " + bad.Count + " ==");
|
||
|
|
for (int i = 0; i < System.Math.Min(bad.Count, 40); i++) L(" " + bad[i]);
|
||
|
|
L("삼각형 합 = " + tris + " (LOD1/LOD2 제외 = " + trisLod0 + ")");
|
||
|
|
L("콜라이더 = " + go.GetComponentsInChildren<UnityEngine.Collider>(true).Length
|
||
|
|
+ " · LODGroup = " + go.GetComponentsInChildren<UnityEngine.LODGroup>(true).Length
|
||
|
|
+ " · 프리팹 인스턴스 루트 = " + CountPrefabRoots(go));
|
||
|
|
|
||
|
|
// 구성
|
||
|
|
var md = go.GetComponent<MapData>();
|
||
|
|
L("");
|
||
|
|
L("== 구성 ==");
|
||
|
|
L("MapData = " + (md == null ? "없음 🔴" : "있음"));
|
||
|
|
if (md != null)
|
||
|
|
{
|
||
|
|
L(" skybox=" + (md.material_skybox == null ? "(없음)" : md.material_skybox.name)
|
||
|
|
+ " ambientMode=" + md.ambientMode + " ambientSky=" + md.ambientSky
|
||
|
|
+ " reflection=" + md.reflectionIntensity + " showWeaponInLobby=" + md.showWeaponInLobby);
|
||
|
|
L(" #764: reachable=" + md.spawnRequireReachableFromPlayer + " snap=" + md.spawnSnapRadius
|
||
|
|
+ " tries=" + md.spawnSampleTries + " mobSnap=" + md.mobNavSnapRadius);
|
||
|
|
var so = new UnityEditor.SerializedObject(md);
|
||
|
|
var fe = so.FindProperty("fogEnabled");
|
||
|
|
if (fe != null)
|
||
|
|
L(" fog: enabled=" + fe.boolValue + " mode=" + Get(so, "fogMode") + " color=" + GetC(so, "fogColor")
|
||
|
|
+ " start=" + Get(so, "fogStart") + " end=" + Get(so, "fogEnd"));
|
||
|
|
else L(" fog: MapData 에 안개 필드가 없다 (미적용)");
|
||
|
|
if (md.m_MapZoneData != null && md.m_MapZoneData.Count > 0)
|
||
|
|
{
|
||
|
|
var z = md.m_MapZoneData[0];
|
||
|
|
L(" zone0 portal=" + (z.go_portal == null ? "(null)🔴" : z.go_portal.transform.position.ToString("F3"))
|
||
|
|
+ " CamRot_Portal=" + z.CamRot_Portal
|
||
|
|
+ " reinc=" + (z.go_reincarnation == null ? "(null)🔴" : "OK"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
L("MobControlMgrList = " + (go.GetComponent<MobControlMgrList>() == null ? "없음 🔴" : "있음"));
|
||
|
|
L("StartWakeSequence = " + (go.GetComponent<WL.Start.StartWakeSequence>() == null ? "없음 🔴" : "있음"));
|
||
|
|
L("카메라 = " + go.GetComponentsInChildren<UnityEngine.Camera>(true).Length + "대 (0 이어야 함)");
|
||
|
|
var lights = go.GetComponentsInChildren<UnityEngine.Light>(true);
|
||
|
|
L("라이트 = " + lights.Length + "개");
|
||
|
|
foreach (var lt in lights)
|
||
|
|
L(" " + Path(lt.transform) + " type=" + lt.type + " color=" + lt.color + " intensity=" + lt.intensity
|
||
|
|
+ " shadows=" + lt.shadows + " euler=" + lt.transform.rotation.eulerAngles.ToString("F2"));
|
||
|
|
|
||
|
|
// 스포너
|
||
|
|
var mgrs = go.GetComponentsInChildren<MobControlMgr>(true);
|
||
|
|
var ids = new List<int>(); int nullLv = 0;
|
||
|
|
foreach (var m in mgrs) { ids.Add(m.SpawnerID); if (m.text_lv == null) nullLv++; }
|
||
|
|
ids.Sort();
|
||
|
|
L("스포너 = " + mgrs.Length + "개 IDs=" + string.Join(",", ids.Select(x => x.ToString()).ToArray()) + " text_lv null=" + nullLv);
|
||
|
|
|
||
|
|
// 드레싱
|
||
|
|
var dress = go.transform.Find("Dressing");
|
||
|
|
if (dress != null)
|
||
|
|
{
|
||
|
|
L("");
|
||
|
|
L("== Dressing ==");
|
||
|
|
foreach (UnityEngine.Transform g in dress)
|
||
|
|
L(" " + g.name + " = " + g.childCount + "개");
|
||
|
|
L(" 총 " + CountDeep(dress) + " 오브젝트");
|
||
|
|
}
|
||
|
|
else L("Dressing 컨테이너 없음 🔴");
|
||
|
|
|
||
|
|
Flush("F4_verify.txt");
|
||
|
|
return "renderers=" + rend + " magentaRisk=" + bad.Count + " tris=" + tris + " spawners=" + mgrs.Length;
|
||
|
|
}
|
||
|
|
|
||
|
|
static string Get(UnityEditor.SerializedObject so, string n)
|
||
|
|
{
|
||
|
|
var p = so.FindProperty(n); if (p == null) return "?";
|
||
|
|
if (p.propertyType == UnityEditor.SerializedPropertyType.Float) return p.floatValue.ToString("F2");
|
||
|
|
if (p.propertyType == UnityEditor.SerializedPropertyType.Integer) return p.intValue.ToString();
|
||
|
|
if (p.propertyType == UnityEditor.SerializedPropertyType.Boolean) return p.boolValue.ToString();
|
||
|
|
return p.propertyType.ToString();
|
||
|
|
}
|
||
|
|
static string GetC(UnityEditor.SerializedObject so, string n)
|
||
|
|
{ var p = so.FindProperty(n); return p == null ? "?" : p.colorValue.ToString(); }
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
// 5) NavMesh 도달 가능 비율 + 스포너 도달성
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
public static object Reach(string prefabPath, string navAsset, float step)
|
||
|
|
{
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(prefabPath);
|
||
|
|
var nav = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.AI.NavMeshData>(navAsset);
|
||
|
|
if (go == null || nav == null) return "prefab or navmesh missing";
|
||
|
|
|
||
|
|
var portal = go.transform.Find("Portal_Start");
|
||
|
|
if (portal == null) return "no Portal_Start";
|
||
|
|
var spawn = portal.transform.position + UnityEngine.Vector3.forward;
|
||
|
|
|
||
|
|
var inst = UnityEngine.AI.NavMesh.AddNavMeshData(nav);
|
||
|
|
string res;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var tri = UnityEngine.AI.NavMesh.CalculateTriangulation();
|
||
|
|
L("NavMesh 삼각형 " + (tri.indices.Length / 3) + "개 · 정점 " + tri.vertices.Length);
|
||
|
|
if (tri.vertices.Length == 0) { Flush("F5_reach.txt"); return "empty navmesh"; }
|
||
|
|
var min = tri.vertices[0]; var max = tri.vertices[0];
|
||
|
|
for (int i = 1; i < tri.vertices.Length; i++)
|
||
|
|
{ min = UnityEngine.Vector3.Min(min, tri.vertices[i]); max = UnityEngine.Vector3.Max(max, tri.vertices[i]); }
|
||
|
|
L("NavMesh 범위 min=" + min.ToString("F1") + " max=" + max.ToString("F1")
|
||
|
|
+ " (XZ " + (max.x - min.x).ToString("F1") + " x " + (max.z - min.z).ToString("F1") + " m)");
|
||
|
|
|
||
|
|
UnityEngine.AI.NavMeshHit ph;
|
||
|
|
if (!UnityEngine.AI.NavMesh.SamplePosition(spawn, out ph, 5f, UnityEngine.AI.NavMesh.AllAreas))
|
||
|
|
{ L("🔴 시작 지점이 NavMesh 위가 아니다: " + spawn.ToString("F3")); Flush("F5_reach.txt"); return "spawn off navmesh"; }
|
||
|
|
var pc = ph.position;
|
||
|
|
L("시작 지점 " + spawn.ToString("F3") + " → NavMesh " + pc.ToString("F3")
|
||
|
|
+ " (거리 " + UnityEngine.Vector3.Distance(spawn, pc).ToString("F2") + "m)");
|
||
|
|
|
||
|
|
int onNav = 0, reach = 0;
|
||
|
|
for (float x = min.x; x <= max.x; x += step)
|
||
|
|
for (float z = min.z; z <= max.z; z += step)
|
||
|
|
{
|
||
|
|
UnityEngine.AI.NavMeshHit h;
|
||
|
|
var probe = new UnityEngine.Vector3(x, (min.y + max.y) * 0.5f, z);
|
||
|
|
if (!UnityEngine.AI.NavMesh.SamplePosition(probe, out h, (max.y - min.y) * 0.5f + step, UnityEngine.AI.NavMesh.AllAreas)) continue;
|
||
|
|
if (UnityEngine.Mathf.Abs(h.position.x - x) > step || UnityEngine.Mathf.Abs(h.position.z - z) > step) continue;
|
||
|
|
onNav++;
|
||
|
|
if (Reachable(pc, h.position)) reach++;
|
||
|
|
}
|
||
|
|
int pct = onNav == 0 ? 0 : reach * 100 / onNav;
|
||
|
|
L("격자 " + step + "m: NavMesh 위 " + onNav + "점 중 시작 지점에서 도달 가능 " + reach + "점 = " + pct + "%");
|
||
|
|
|
||
|
|
// 스포너
|
||
|
|
var mgrs = go.GetComponentsInChildren<MobControlMgr>(true);
|
||
|
|
int okS = 0;
|
||
|
|
var lines = new List<string>();
|
||
|
|
foreach (var m in mgrs.OrderBy(x => x.SpawnerID))
|
||
|
|
{
|
||
|
|
UnityEngine.AI.NavMeshHit sh;
|
||
|
|
bool on = UnityEngine.AI.NavMesh.SamplePosition(m.transform.position, out sh, 3f, UnityEngine.AI.NavMesh.AllAreas);
|
||
|
|
bool rc = on && Reachable(pc, sh.position);
|
||
|
|
if (rc) okS++;
|
||
|
|
lines.Add(string.Format(" {0,2} pos={1} onNav={2} reachable={3} PC거리={4:F1}m",
|
||
|
|
m.SpawnerID, m.transform.position.ToString("F1"), on, rc, UnityEngine.Vector3.Distance(m.transform.position, pc)));
|
||
|
|
}
|
||
|
|
L("== 스포너 도달성 " + okS + "/" + mgrs.Length + " ==");
|
||
|
|
foreach (var s in lines) L(s);
|
||
|
|
res = "reachPct=" + pct + " onNav=" + onNav + " spawnersReachable=" + okS + "/" + mgrs.Length;
|
||
|
|
}
|
||
|
|
finally { UnityEngine.AI.NavMesh.RemoveNavMeshData(inst); }
|
||
|
|
Flush("F5_reach.txt");
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool Reachable(UnityEngine.Vector3 a, UnityEngine.Vector3 b)
|
||
|
|
{
|
||
|
|
var path = new UnityEngine.AI.NavMeshPath();
|
||
|
|
if (!UnityEngine.AI.NavMesh.CalculatePath(a, b, UnityEngine.AI.NavMesh.AllAreas, path)) return false;
|
||
|
|
return path.status == UnityEngine.AI.NavMeshPathStatus.PathComplete;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
// 6) 캡처 — 게임 시점 4장 + 위에서 본 배치도 1장 (에디터 렌더)
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
public static object Shots(string prefabPath, float half)
|
||
|
|
{
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
if (UnityEngine.Application.isPlaying) return "PLAYING - 중단";
|
||
|
|
System.IO.Directory.CreateDirectory(SHOT);
|
||
|
|
var setup = UnityEditor.SceneManagement.EditorSceneManager.GetSceneManagerSetup();
|
||
|
|
var made = new List<string>();
|
||
|
|
try
|
||
|
|
{
|
||
|
|
UnityEditor.SceneManagement.EditorSceneManager.NewScene(
|
||
|
|
UnityEditor.SceneManagement.NewSceneSetup.EmptyScene, UnityEditor.SceneManagement.NewSceneMode.Single);
|
||
|
|
var src = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(prefabPath);
|
||
|
|
var root = (UnityEngine.GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(src);
|
||
|
|
root.transform.position = UnityEngine.Vector3.zero;
|
||
|
|
|
||
|
|
// 프리팹 MapData 설정을 씬 RenderSettings 에 반영 (런타임과 같은 룩)
|
||
|
|
var md = root.GetComponent<MapData>();
|
||
|
|
if (md != null)
|
||
|
|
{
|
||
|
|
UnityEngine.RenderSettings.skybox = md.material_skybox;
|
||
|
|
UnityEngine.RenderSettings.ambientMode = md.ambientMode;
|
||
|
|
if (md.ambientMode == UnityEngine.Rendering.AmbientMode.Flat) UnityEngine.RenderSettings.ambientLight = md.ambientSky;
|
||
|
|
else { UnityEngine.RenderSettings.ambientSkyColor = md.ambientSky; UnityEngine.RenderSettings.ambientEquatorColor = md.ambientEquator; UnityEngine.RenderSettings.ambientGroundColor = md.ambientGround; }
|
||
|
|
UnityEngine.RenderSettings.reflectionIntensity = md.reflectionIntensity;
|
||
|
|
var so = new UnityEditor.SerializedObject(md);
|
||
|
|
var fe = so.FindProperty("fogEnabled");
|
||
|
|
if (fe != null && fe.boolValue)
|
||
|
|
{
|
||
|
|
UnityEngine.RenderSettings.fog = true;
|
||
|
|
var fm = so.FindProperty("fogMode"); if (fm != null) UnityEngine.RenderSettings.fogMode = (UnityEngine.FogMode)fm.intValue;
|
||
|
|
var fc = so.FindProperty("fogColor"); if (fc != null) UnityEngine.RenderSettings.fogColor = fc.colorValue;
|
||
|
|
var fs = so.FindProperty("fogStart"); if (fs != null) UnityEngine.RenderSettings.fogStartDistance = fs.floatValue;
|
||
|
|
var fed = so.FindProperty("fogEnd"); if (fed != null) UnityEngine.RenderSettings.fogEndDistance = fed.floatValue;
|
||
|
|
}
|
||
|
|
else UnityEngine.RenderSettings.fog = false;
|
||
|
|
L("RenderSettings 적용: ambient=" + md.ambientMode + " fog=" + UnityEngine.RenderSettings.fog);
|
||
|
|
}
|
||
|
|
|
||
|
|
var portal = root.transform.Find("Portal_Start");
|
||
|
|
var start = portal.position + UnityEngine.Vector3.forward;
|
||
|
|
float camYaw = (md != null && md.m_MapZoneData != null && md.m_MapZoneData.Count > 0) ? md.m_MapZoneData[0].CamRot_Portal : 0f;
|
||
|
|
|
||
|
|
// (1) 시작 지점 — 실제 게임 카메라 각도
|
||
|
|
made.Add(GameShot("01_start", start, camYaw));
|
||
|
|
|
||
|
|
// (2)(3)(4) 숲 / 강가 / 바위 — Dressing 그룹 중심을 실측해 대표점 선정
|
||
|
|
var dress = root.transform.Find("Dressing");
|
||
|
|
if (dress != null)
|
||
|
|
{
|
||
|
|
var t = dress.Find("Trees"); if (t != null && t.childCount > 0) made.Add(GameShot("02_forest", GroundAt(Densest(t, 15f)), camYaw + 40f));
|
||
|
|
var r = dress.Find("Rocks"); if (r != null && r.childCount > 0) made.Add(GameShot("04_rocks", GroundAt(Densest(r, 10f)), camYaw - 60f));
|
||
|
|
}
|
||
|
|
var water = NearestWater(root, start);
|
||
|
|
if (water.HasValue) made.Add(GameShot("03_river", GroundAt(water.Value), YawTo(water.Value, start)));
|
||
|
|
|
||
|
|
// (5) 위에서 본 배치도
|
||
|
|
made.Add(TopShot("05_layout", start, half));
|
||
|
|
|
||
|
|
L("캡처 " + made.Count + "장");
|
||
|
|
foreach (var m in made) L(" " + m);
|
||
|
|
UnityEngine.Object.DestroyImmediate(root);
|
||
|
|
ClearDirty();
|
||
|
|
}
|
||
|
|
catch (System.Exception e) { L("EXCEPTION " + e); }
|
||
|
|
finally { try { if (setup != null && setup.Length > 0) UnityEditor.SceneManagement.EditorSceneManager.RestoreSceneManagerSetup(setup); } catch { } }
|
||
|
|
Flush("F6_shots.txt");
|
||
|
|
return string.Join(" | ", made.ToArray());
|
||
|
|
}
|
||
|
|
|
||
|
|
// 반경 r 안에 같은 그룹 오브젝트가 가장 많은 지점 (군락 한복판) — 촬영 대상점
|
||
|
|
static UnityEngine.Vector3 Densest(UnityEngine.Transform group, float r)
|
||
|
|
{
|
||
|
|
var pts = new List<UnityEngine.Vector3>();
|
||
|
|
foreach (UnityEngine.Transform c in group) pts.Add(c.position);
|
||
|
|
int best = 0, bestN = -1;
|
||
|
|
float r2 = r * r;
|
||
|
|
for (int i = 0; i < pts.Count; i++)
|
||
|
|
{
|
||
|
|
int n = 0;
|
||
|
|
for (int j = 0; j < pts.Count; j++)
|
||
|
|
{
|
||
|
|
float dx = pts[i].x - pts[j].x, dz = pts[i].z - pts[j].z;
|
||
|
|
if (dx * dx + dz * dz <= r2) n++;
|
||
|
|
}
|
||
|
|
if (n > bestN) { bestN = n; best = i; }
|
||
|
|
}
|
||
|
|
L(" 군락 최밀점 = " + pts[best].ToString("F1") + " (반경 " + r + "m 안 " + bestN + "개)");
|
||
|
|
// 군락 한복판이 아니라 가장자리에서 안쪽을 보도록 살짝 물러난다
|
||
|
|
return pts[best];
|
||
|
|
}
|
||
|
|
|
||
|
|
static UnityEngine.Vector3 Center(UnityEngine.Transform t)
|
||
|
|
{
|
||
|
|
var s = UnityEngine.Vector3.zero; int n = 0;
|
||
|
|
foreach (UnityEngine.Transform c in t) { s += c.position; n++; }
|
||
|
|
return n == 0 ? t.position : s / n;
|
||
|
|
}
|
||
|
|
|
||
|
|
static UnityEngine.Vector3 GroundAt(UnityEngine.Vector3 p)
|
||
|
|
{
|
||
|
|
UnityEngine.RaycastHit h;
|
||
|
|
if (UnityEngine.Physics.Raycast(new UnityEngine.Vector3(p.x, p.y + 200f, p.z), UnityEngine.Vector3.down, out h, 600f)) return h.point;
|
||
|
|
return p;
|
||
|
|
}
|
||
|
|
|
||
|
|
static float YawTo(UnityEngine.Vector3 from, UnityEngine.Vector3 to)
|
||
|
|
{
|
||
|
|
var d = to - from; d.y = 0f;
|
||
|
|
return d.sqrMagnitude < 0.01f ? 0f : UnityEngine.Quaternion.LookRotation(d).eulerAngles.y + 180f;
|
||
|
|
}
|
||
|
|
|
||
|
|
static UnityEngine.Vector3? NearestWater(UnityEngine.GameObject root, UnityEngine.Vector3 from)
|
||
|
|
{
|
||
|
|
UnityEngine.Vector3? best = null; float bd = float.MaxValue;
|
||
|
|
foreach (var r in root.GetComponentsInChildren<UnityEngine.Renderer>(true))
|
||
|
|
{
|
||
|
|
bool w = r.name.ToLowerInvariant().Contains("water");
|
||
|
|
if (!w) foreach (var m in r.sharedMaterials) { if (m != null && m.name.ToLowerInvariant().Contains("water")) { w = true; break; } }
|
||
|
|
if (!w) continue;
|
||
|
|
var c = r.bounds.ClosestPoint(from);
|
||
|
|
float d = UnityEngine.Vector3.Distance(c, from);
|
||
|
|
if (d < bd) { bd = d; best = c + (from - c).normalized * 4f; }
|
||
|
|
}
|
||
|
|
if (best.HasValue) L("최근접 수역까지 " + bd.ToString("F1") + "m → 촬영점 " + best.Value.ToString("F1"));
|
||
|
|
return best;
|
||
|
|
}
|
||
|
|
|
||
|
|
static string GameShot(string tag, UnityEngine.Vector3 target, float yaw)
|
||
|
|
{
|
||
|
|
var tmp = new UnityEngine.GameObject("~shot");
|
||
|
|
tmp.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
||
|
|
var c = tmp.AddComponent<UnityEngine.Camera>();
|
||
|
|
c.fieldOfView = CAM_FOV; c.nearClipPlane = 0.3f; c.farClipPlane = 1000f;
|
||
|
|
c.clearFlags = UnityEngine.CameraClearFlags.Skybox;
|
||
|
|
var rotV = UnityEngine.Quaternion.Euler(CAM_PITCH, yaw, 0f) * UnityEngine.Vector3.one;
|
||
|
|
c.transform.position = target + UnityEngine.Vector3.up * CAM_UP - rotV * CAM_DIST;
|
||
|
|
c.transform.LookAt(target + UnityEngine.Vector3.up);
|
||
|
|
var path = System.IO.Path.Combine(SHOT, tag + ".png");
|
||
|
|
Render(c, path, W, H);
|
||
|
|
L(tag + ": target=" + target.ToString("F1") + " campos=" + c.transform.position.ToString("F1") + " yaw=" + yaw.ToString("F0"));
|
||
|
|
UnityEngine.Object.DestroyImmediate(tmp);
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
|
||
|
|
static string TopShot(string tag, UnityEngine.Vector3 center, float half)
|
||
|
|
{
|
||
|
|
// 배치도는 안개를 끄고 찍는다 — Linear 안개(end 250 m)가 켜져 있으면 상공 카메라에서 화면이 통째로 안개색이 된다.
|
||
|
|
bool fogWas = UnityEngine.RenderSettings.fog;
|
||
|
|
UnityEngine.RenderSettings.fog = false;
|
||
|
|
var tmp = new UnityEngine.GameObject("~top");
|
||
|
|
tmp.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
||
|
|
var c = tmp.AddComponent<UnityEngine.Camera>();
|
||
|
|
c.orthographic = true; c.orthographicSize = half * 1.15f;
|
||
|
|
c.nearClipPlane = 1f; c.farClipPlane = 2000f;
|
||
|
|
c.clearFlags = UnityEngine.CameraClearFlags.SolidColor;
|
||
|
|
c.backgroundColor = new UnityEngine.Color(0.08f, 0.10f, 0.13f, 1f);
|
||
|
|
c.transform.position = new UnityEngine.Vector3(center.x, center.y + 300f, center.z);
|
||
|
|
c.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f);
|
||
|
|
var path = System.IO.Path.Combine(SHOT, tag + ".png");
|
||
|
|
Render(c, path, 1200, 1200);
|
||
|
|
L(tag + ": center=" + center.ToString("F1") + " orthoSize=" + c.orthographicSize.ToString("F0") + " (안개 일시 해제)");
|
||
|
|
UnityEngine.Object.DestroyImmediate(tmp);
|
||
|
|
UnityEngine.RenderSettings.fog = fogWas;
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void Render(UnityEngine.Camera cam, string path, int w, int h)
|
||
|
|
{
|
||
|
|
var rt = new UnityEngine.RenderTexture(w, h, 24, UnityEngine.RenderTextureFormat.ARGB32);
|
||
|
|
var prev = cam.targetTexture;
|
||
|
|
cam.targetTexture = rt;
|
||
|
|
cam.Render();
|
||
|
|
var active = 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 = active;
|
||
|
|
cam.targetTexture = prev;
|
||
|
|
System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex));
|
||
|
|
UnityEngine.Object.DestroyImmediate(tex);
|
||
|
|
rt.Release();
|
||
|
|
UnityEngine.Object.DestroyImmediate(rt);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 보조 ─────────────────────────────────────────────────────
|
||
|
|
static int CountPrefabRoots(UnityEngine.GameObject go)
|
||
|
|
{
|
||
|
|
int n = 0;
|
||
|
|
foreach (var t in go.GetComponentsInChildren<UnityEngine.Transform>(true))
|
||
|
|
if (UnityEditor.PrefabUtility.IsAnyPrefabInstanceRoot(t.gameObject)) n++;
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
static int CountDeep(UnityEngine.Transform t)
|
||
|
|
{ int n = 0; for (int i = 0; i < t.childCount; i++) n += 1 + CountDeep(t.GetChild(i)); return n; }
|
||
|
|
static string Path(UnityEngine.Transform t)
|
||
|
|
{ var s = t.name; var p = t.parent; while (p != null) { s = p.name + "/" + s; p = p.parent; } return s; }
|
||
|
|
static void ClearDirty()
|
||
|
|
{
|
||
|
|
var t = typeof(UnityEditor.SceneManagement.EditorSceneManager);
|
||
|
|
var mi = t.GetMethod("ClearSceneDirtiness",
|
||
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||
|
|
if (mi == null) return;
|
||
|
|
for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++)
|
||
|
|
mi.Invoke(null, new object[] { UnityEngine.SceneManagement.SceneManager.GetSceneAt(i) });
|
||
|
|
}
|
||
|
|
static void Flush(string name)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
System.IO.Directory.CreateDirectory(OUT);
|
||
|
|
System.IO.File.WriteAllText(System.IO.Path.Combine(OUT, name), sb.ToString(), new System.Text.UTF8Encoding(true));
|
||
|
|
}
|
||
|
|
catch { }
|
||
|
|
}
|
||
|
|
}
|