[WL-815c] 중간: StageBarrier(정적 Set/Clear/IsInside) + 3스타일 그림 + 카브 옵션 + 프로브 43/43 (#815)
- Assets/WL/Combat/Stage/Barrier/ 신규 3파일 + Resources SO - 워프백 = 813w3/813t2 와 같은 원본 Actor.Set_Warp · NavMesh 스냅 · 카메라 리드 스냅 - 보이기 3스타일: ⓐ Hovl Marker 3 Zone Loop 스케일 ⓑ 울타리·바위 프롭 링 ⓒ 불꽃 링(총량 상한) - 캡처는 다음 커밋
This commit is contained in:
parent
fc88c7d30a
commit
89e4d06d99
|
|
@ -0,0 +1,62 @@
|
||||||
|
// WL-815c 설정 에셋 생성 — Assets/WL/Combat/Stage/Barrier/Resources/WL/WLStageBarrierSettings.asset
|
||||||
|
// unity command run_script --file AgentScripts/WL815c_MakeAsset.cs --entry WL815c_MakeAsset.Run
|
||||||
|
|
||||||
|
public static class WL815c_MakeAsset
|
||||||
|
{
|
||||||
|
const string kDir = "Assets/WL/Combat/Stage/Barrier/Resources/WL";
|
||||||
|
const string kPath = kDir + "/WLStageBarrierSettings.asset";
|
||||||
|
|
||||||
|
public static string Run()
|
||||||
|
{
|
||||||
|
System.IO.Directory.CreateDirectory(kDir);
|
||||||
|
UnityEditor.AssetDatabase.Refresh();
|
||||||
|
|
||||||
|
var so = UnityEditor.AssetDatabase.LoadAssetAtPath<WL.Combat.Stage.WLStageBarrierSettings>(kPath);
|
||||||
|
bool made = false;
|
||||||
|
if (so == null)
|
||||||
|
{
|
||||||
|
so = UnityEngine.ScriptableObject.CreateInstance<WL.Combat.Stage.WLStageBarrierSettings>();
|
||||||
|
UnityEditor.AssetDatabase.CreateAsset(so, kPath);
|
||||||
|
made = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ⓐ 링 = Hovl Marker 3 Zone Loop (기준서 §D-3 1순위 · 실측 시각 반경 1.10 m)
|
||||||
|
so.ringPrefab = Load("Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 3 Zone Loop.prefab");
|
||||||
|
so.ringPrefabVisualRadius = 1.1f;
|
||||||
|
|
||||||
|
// ── ⓑ 프롭 = 울타리(LowPolyFantasyVillage Fence_01 실측 2.38 × 1.02 m · 120 tri) + 바위 2종
|
||||||
|
so.propPrefabs = new UnityEngine.GameObject[]
|
||||||
|
{
|
||||||
|
Load("Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/Fence_01.prefab"),
|
||||||
|
Load("Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/Fence_01.prefab"),
|
||||||
|
Load("Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_crk_m_01.prefab"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── ⓒ 불꽃 = LMHPOLY Fire_Particles (실측 3 PS · 정상상태 125 파티클)
|
||||||
|
so.firePrefabs = new UnityEngine.GameObject[]
|
||||||
|
{
|
||||||
|
Load("Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/ParticleSystem/Fire_Particles.prefab"),
|
||||||
|
};
|
||||||
|
|
||||||
|
UnityEditor.EditorUtility.SetDirty(so);
|
||||||
|
UnityEditor.AssetDatabase.SaveAssets();
|
||||||
|
UnityEditor.AssetDatabase.Refresh();
|
||||||
|
|
||||||
|
var chk = UnityEditor.AssetDatabase.LoadAssetAtPath<WL.Combat.Stage.WLStageBarrierSettings>(kPath);
|
||||||
|
var res = UnityEngine.Resources.Load<WL.Combat.Stage.WLStageBarrierSettings>(WL.Combat.Stage.WLStageBarrierSettings.ResourcesPath);
|
||||||
|
return (made ? "생성" : "갱신") + " " + kPath
|
||||||
|
+ " · 에셋로드 " + (chk != null)
|
||||||
|
+ " · Resources.Load " + (res != null)
|
||||||
|
+ " · ring " + (chk != null && chk.ringPrefab != null ? chk.ringPrefab.name : "null")
|
||||||
|
+ " · props " + (chk != null && chk.propPrefabs != null ? chk.propPrefabs.Length : 0)
|
||||||
|
+ " · fires " + (chk != null && chk.firePrefabs != null ? chk.firePrefabs.Length : 0)
|
||||||
|
+ " · style " + (chk != null ? chk.style.ToString() : "?");
|
||||||
|
}
|
||||||
|
|
||||||
|
static UnityEngine.GameObject Load(string p)
|
||||||
|
{
|
||||||
|
var g = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(p);
|
||||||
|
if (g == null) UnityEngine.Debug.Log("[WL815c] 프리팹 없음 " + p);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
WL-815c 스테이지 장벽 프로브 · 2026-09-11 00:51:58
|
||||||
|
앵커 = 기준서 §D-1 존1 (115.0, 0.4, -22.0) 반경 14 m
|
||||||
|
|
||||||
|
① 설정 에셋(C45 — 값은 전부 SO)
|
||||||
|
PASS 1-1 에셋 로드 — Resources/WL/WLStageBarrierSettings
|
||||||
|
PASS 1-2 막기 값 — leaveMargin 2 · grace 0.5 s · 쿨 1 s · inset 1.5 m · 샘플 4/20 m
|
||||||
|
PASS 1-3 몹 값 — 몹여유 3 · 유예 1 s · 쿨 2 s
|
||||||
|
PASS 1-4 카브 기본 off — carveNavMesh 0 · arc 4 m · 상한 32 · 두께 1.5 · 높이 3
|
||||||
|
PASS 1-5 스타일 — style=MagicRing · 입장 0.6 s · 클리어 0.6 s
|
||||||
|
PASS 1-6 ⓐ 링 프리팹 — Marker 3 Zone Loop · 실측 시각반경 1.1 m · 겹 2
|
||||||
|
PASS 1-7 ⓑ 프롭 프리팹 — Fence_01, Fence_01, Rock_Block_crk_m_01 · 간격 2.5 m · 상한 64
|
||||||
|
PASS 1-8 ⓒ 불꽃 프리팹 — Fire_Particles · 간격 3 m · 상한 48 · 파티클 총량 1200
|
||||||
|
|
||||||
|
② 임시 NavMesh 베이크(에디트 모드에서 실 워프 지점을 재기 위해)
|
||||||
|
PASS 2-1 NavMesh 베이크 — 정점 64 · 앵커 스냅 (115.00, 0.48, -22.00)
|
||||||
|
|
||||||
|
③ Set / Clear 왕복 (오브젝트 0 · 풀 반납)
|
||||||
|
PASS 3-1 초기 — Active=false · IsInside(어디든)=true → True
|
||||||
|
PASS 3-2 Set — Active=true · 중심 (115.0, 0.4, -22.0) 반경 14 · 그림 인스턴스 2 · Instantiate 누계 2
|
||||||
|
PASS 3-3 IsInside — 중심=안 · 13.9 m=안 · 14.1 m=밖 · 높이 무관(y+50 → True)
|
||||||
|
PASS 3-4a Clear 직후 = 페이드 중 — Active=false · 페이드 중 활성 2 · 진행도 1.00
|
||||||
|
PASS 3-4 페이드 끝 → 활성 0 · 풀 반납 — 활성 0 · 풀 2(= Instantiate 누계 2) · Destroy 0
|
||||||
|
PASS 3-5 재Set → 풀 재사용 — Instantiate 2 → 2(증가 0) · 활성 2
|
||||||
|
PASS 3-6 입장/클리어 연출 — 스케일 t=0 0.00 → t=.5 6.23 → t=1 12.45 (appear 0.6 s / fade 0.6 s)
|
||||||
|
PASS 3-7 반경 0 → Clear — Set(r=0) → Active=False
|
||||||
|
|
||||||
|
④ 되돌릴 지점 계산(순수 함수 · NearestInsidePoint)
|
||||||
|
(145.0, 0.4, -22.0) (중심 30.0 m) → (127.50, 0.40, -22.00) (중심 12.50 m · 방위 유지 Y)
|
||||||
|
(115.0, 0.4, -47.0) (중심 25.0 m) → (115.00, 0.40, -34.50) (중심 12.50 m · 방위 유지 Y)
|
||||||
|
(75.0, 3.4, 18.0) (중심 56.6 m) → (106.16, 3.40, -13.16) (중심 12.50 m · 방위 유지 Y)
|
||||||
|
(129.5, 0.4, -22.0) (중심 14.5 m) → (127.50, 0.40, -22.00) (중심 12.50 m · 방위 유지 Y)
|
||||||
|
PASS 4-1 안쪽 12.5 m · 방위 유지 — 4/4 · inset 1.5 m
|
||||||
|
PASS 4-2 정중앙 예외 — 중심 그대로 (115.00, 0.40, -22.00)(0 나누기 없음)
|
||||||
|
|
||||||
|
⑤ 실 워프(원본 Actor.Set_Warp · NavMesh 스냅)
|
||||||
|
PASS 5-1 스냅 지점 = 안쪽 · NavMesh 위 — 밖 35 m → (127.50, 0.48, -22.00) · 중심 12.50 m(< 14) · NavMesh 위 True
|
||||||
|
PASS 5-2 NavMesh 없으면 워프 안 함 — 먼 좌표(9000,0,9000) → false = 워프 포기(지형 뚫기 방지)
|
||||||
|
PASS 5-3 PC 실 워프 — 밖 30.0 m → 중심 12.50 m · PlayerWarps 1 · (145.0, 0.4, -22.0) → (127.5, 0.5, -22.0)
|
||||||
|
PASS 5-4 안에 있으면 무동작 — 중심에서 ForceCheck → 워프 안 함
|
||||||
|
PASS 5-5 몹 복귀 — 밖 28 m → 중심 12.50 m · MobWarps 1
|
||||||
|
PASS 5-6 유예 안에서는 안 되돌린다 — grace 999 s · 틱 ×2 → 워프 0 (OutsideSeen 0)
|
||||||
|
|
||||||
|
⑥ 스타일 전환 3회 (PD 가 값 하나로 바꾼다)
|
||||||
|
PASS 6-1 MagicRing — 활성 2 · 렌더러 8 · 머티리얼 1 · tri 0 · PS 8 · 빌드 1.4 ms · 링 스케일 12.45(반경 14 ÷ 실측 1.1)
|
||||||
|
PASS 6-2 Props — 활성 35 · 렌더러 35 · 머티리얼 2 · tri 4530 · PS 0 · 빌드 1.3 ms · 프롭 35개 · 실간격 2.51 m
|
||||||
|
PASS 6-3 Fire — 활성 29 · 렌더러 87 · 머티리얼 3 · tri 0 · PS 87 · 빌드 7.6 ms · 불꽃 29개 · 방출률 ×0.33 · 파티클 추정 1200(개당 125)
|
||||||
|
PASS 6-4 전환 누계 — StyleSwitches 3 · Instantiate 누계 66
|
||||||
|
PASS 6-5 None → 그림 0 — 활성 그림 0 · 판정은 그대로 Active=True
|
||||||
|
|
||||||
|
⑦ NavMesh 카브 on/off (옵션 · 기준서 §D-2 ②)
|
||||||
|
PASS 7-1 off(기본) → 카브 0 — carveNavMesh 0 → 오브젝트 0
|
||||||
|
PASS 7-2 on → 세그먼트 생성 — 둘레 88.0 m ÷ arc 4 m = 22(상한 32) · 오브젝트 22 · 생성 0.41 ms · Set 전체 2.32 ms
|
||||||
|
PASS 7-3 세그먼트 기하 — carving=true 22 · Box 22 · 상자 4.60 × 두께 1.50 m > 이웃 간격 3.98 m(이론 4.00 · 겹침 15 %) · 원 이탈 최대 0.0000 m
|
||||||
|
NavMesh 삼각형 32 → 32 (에디트 모드는 carve 가 즉시 반영되지 않을 수 있다 — 실 반영·재베이크 ms 는 Play 확인)
|
||||||
|
PASS 7-4 Clear → 카브 회수 — 오브젝트 0
|
||||||
|
|
||||||
|
⑧ GC (프레임당 할당 0)
|
||||||
|
PASS 8-1 반경 안 · 틱 ×200 — 할당 0 B (틱마다 0.0 B) · 계측 = Profiler.GetMonoUsedSizeLong
|
||||||
|
대조군(byte[65536] ×200 = 12.8 MB) 13926400 B · sink True
|
||||||
|
PASS 8-2 폴링 동작 — Scans 2 · Checks 0 → 1 (몹 1기 · 안쪽이라 워프 0)
|
||||||
|
PASS 8-3 몹 1기 · 틱 ×200 — 할당 0 B
|
||||||
|
|
||||||
|
⑨ C8 롤백 진리표
|
||||||
|
PASS 9-1 enabled=0 → Set 무동작 — Active=False · Instantiate 0 (오브젝트 0)
|
||||||
|
PASS 9-2 RuntimeDisabled=1 → Set 무동작 — Active=False · Instantiate 0
|
||||||
|
PASS 9-3 에셋 없음 → Set 무동작 — Active=False · FeatureEnabled=False
|
||||||
|
PASS 9-4 blockEnabled=0 → 판정 0 · 그림은 남는다 — Checks 0 · 그림 2
|
||||||
|
PASS 9-5 showVisual=0 → 그림 0 · 판정은 산다 — 그림 0 · Active=True
|
||||||
|
|
||||||
|
⑩ 성능 표 (반경 14 m · 스타일별)
|
||||||
|
| 스타일 | 인스턴스 | 렌더러 | 머티리얼 | 삼각형 | 파티클시스템 | 정상상태 파티클 | 빌드 ms |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| MagicRing | 2 | 8 | 1 | 0 | 8 | 8 | 0.9 |
|
||||||
|
| Props | 35 | 35 | 2 | 4530 | 0 | 0 | 1.2 |
|
||||||
|
| Fire | 29 | 87 | 3 | 0 | 87 | 1200 | 8.2 |
|
||||||
|
PASS 10-1 성능 표 — 위 표 참조(에디트 모드 측정 · 실 드로우콜은 캡처의 UnityStats)
|
||||||
|
반경별 프롭 개수(간격 2.5 m · 상한 64)
|
||||||
|
r 8 m(둘레 50.3 m) → 프롭 20 · 실간격 2.51 m · 카브 세그(on 일 때) 13
|
||||||
|
r 14 m(둘레 88.0 m) → 프롭 35 · 실간격 2.51 m · 카브 세그(on 일 때) 22
|
||||||
|
r 20 m(둘레 125.7 m) → 프롭 50 · 실간격 2.51 m · 카브 세그(on 일 때) 32
|
||||||
|
r 30 m(둘레 188.5 m) → 프롭 64 · 실간격 2.95 m · 카브 세그(on 일 때) 32
|
||||||
|
r 40 m(둘레 251.3 m) → 프롭 64 · 실간격 3.93 m · 카브 세그(on 일 때) 32
|
||||||
|
|
||||||
|
RESULT PASS · 43 PASS · 0 FAIL
|
||||||
|
|
@ -0,0 +1,636 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// WL815c_Probe.cs — 스테이지 장벽 검증(에디트 모드 · Play 0 · 로그인 0)
|
||||||
|
// unity command run_script --file AgentScripts/WL815c_Probe.cs --entry WL815c_Probe.RunAll
|
||||||
|
// 산출물: AgentScripts/WL815c_PROBE.txt
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.AI;
|
||||||
|
using UnityEngine.Profiling;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using WL.Combat.Stage;
|
||||||
|
|
||||||
|
public static class WL815c_Probe
|
||||||
|
{
|
||||||
|
static StringBuilder s_sb;
|
||||||
|
static int s_pass, s_fail;
|
||||||
|
|
||||||
|
// 기준서 §D-1 존1 앵커
|
||||||
|
static readonly Vector3 kAnchor = new Vector3(115f, 0.4f, -22f);
|
||||||
|
const float kRadius = 14f;
|
||||||
|
|
||||||
|
static void L(string s) { s_sb.AppendLine(s); }
|
||||||
|
static void Chk(string name, bool ok, string detail)
|
||||||
|
{
|
||||||
|
if (ok) s_pass++; else s_fail++;
|
||||||
|
L((ok ? " PASS " : " FAIL ") + name + " — " + detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string RunAll()
|
||||||
|
{
|
||||||
|
s_sb = new StringBuilder(); s_pass = 0; s_fail = 0;
|
||||||
|
L("WL-815c 스테이지 장벽 프로브 · " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
L(" 앵커 = 기준서 §D-1 존1 " + kAnchor.ToString("F1") + " 반경 " + kRadius + " m");
|
||||||
|
|
||||||
|
var keepSo = WLStageBarrierSettings.Instance;
|
||||||
|
Scene temp = default(Scene);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
temp = EditorSceneManager.NewScene(
|
||||||
|
NewSceneSetup.DefaultGameObjects,
|
||||||
|
NewSceneMode.Single);
|
||||||
|
|
||||||
|
Sec1_Settings();
|
||||||
|
var ground = MakeGround();
|
||||||
|
bool navOk = BakeNav(ground);
|
||||||
|
Sec2_SetClear();
|
||||||
|
Sec3_WarpMath();
|
||||||
|
Sec4_WarpReal(navOk);
|
||||||
|
Sec5_Styles();
|
||||||
|
Sec6_Carve();
|
||||||
|
Sec7_GC();
|
||||||
|
Sec8_C8();
|
||||||
|
Sec9_Cost();
|
||||||
|
}
|
||||||
|
catch (Exception ex) { s_fail++; L(" FAIL 예외 — " + ex.GetType().Name + " " + ex.Message + "\n" + ex.StackTrace); }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { StageBarrier.HardResetForProbe(); } catch { }
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(keepSo);
|
||||||
|
WLStageBarrierSettings.RuntimeDisabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL") + " · " + s_pass + " PASS · " + s_fail + " FAIL");
|
||||||
|
File.WriteAllText("AgentScripts/WL815c_PROBE.txt", s_sb.ToString());
|
||||||
|
return (s_fail == 0 ? "PASS" : "FAIL") + " " + s_pass + "/" + (s_pass + s_fail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec1_Settings()
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("① 설정 에셋(C45 — 값은 전부 SO)");
|
||||||
|
var c = WLStageBarrierSettings.Instance;
|
||||||
|
Chk("1-1 에셋 로드", c != null, c != null ? "Resources/WL/WLStageBarrierSettings" : "null");
|
||||||
|
if (c == null) return;
|
||||||
|
Chk("1-2 막기 값", c.leaveMargin > 0 && c.graceSeconds >= 0,
|
||||||
|
"leaveMargin " + c.leaveMargin + " · grace " + c.graceSeconds + " s · 쿨 " + c.warpCooldownSeconds +
|
||||||
|
" s · inset " + c.warpInsetMeters + " m · 샘플 " + c.warpNavSampleRadius + "/" + c.warpNavSampleRadiusFar + " m");
|
||||||
|
Chk("1-3 몹 값", c.blockMobs, "몹여유 " + c.mobLeaveMargin + " · 유예 " + c.mobGraceSeconds + " s · 쿨 " + c.mobWarpCooldownSeconds + " s");
|
||||||
|
Chk("1-4 카브 기본 off", !c.carveNavMesh, "carveNavMesh " + (c.carveNavMesh ? 1 : 0) + " · arc " + c.carveSegmentArcMeters +
|
||||||
|
" m · 상한 " + c.maxCarveSegments + " · 두께 " + c.carveThickness + " · 높이 " + c.carveHeight);
|
||||||
|
Chk("1-5 스타일", true, "style=" + c.style + " · 입장 " + c.appearSeconds + " s · 클리어 " + c.fadeSeconds + " s");
|
||||||
|
Chk("1-6 ⓐ 링 프리팹", c.ringPrefab != null, (c.ringPrefab != null ? c.ringPrefab.name : "null") +
|
||||||
|
" · 실측 시각반경 " + c.ringPrefabVisualRadius + " m · 겹 " + (1 + c.ringExtraRings));
|
||||||
|
Chk("1-7 ⓑ 프롭 프리팹", c.propPrefabs != null && c.propPrefabs.Length > 0,
|
||||||
|
Names(c.propPrefabs) + " · 간격 " + c.propSpacing + " m · 상한 " + c.maxProps);
|
||||||
|
Chk("1-8 ⓒ 불꽃 프리팹", c.firePrefabs != null && c.firePrefabs.Length > 0,
|
||||||
|
Names(c.firePrefabs) + " · 간격 " + c.fireSpacing + " m · 상한 " + c.maxFires + " · 파티클 총량 " + c.maxTotalParticles);
|
||||||
|
}
|
||||||
|
|
||||||
|
static string Names(GameObject[] a)
|
||||||
|
{
|
||||||
|
if (a == null || a.Length == 0) return "(없음)";
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < a.Length; i++) { if (i > 0) sb.Append(", "); sb.Append(a[i] != null ? a[i].name : "null"); }
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static GameObject MakeGround()
|
||||||
|
{
|
||||||
|
var g = GameObject.CreatePrimitive(PrimitiveType.Plane);
|
||||||
|
g.name = "[WL815c] Ground";
|
||||||
|
g.transform.position = new Vector3(kAnchor.x, kAnchor.y, kAnchor.z);
|
||||||
|
g.transform.localScale = Vector3.one * 12f; // 120 × 120 m
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool BakeNav(GameObject ground)
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("② 임시 NavMesh 베이크(에디트 모드에서 실 워프 지점을 재기 위해)");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var surf = ground.AddComponent<Unity.AI.Navigation.NavMeshSurface>();
|
||||||
|
surf.collectObjects = Unity.AI.Navigation.CollectObjects.All;
|
||||||
|
surf.BuildNavMesh();
|
||||||
|
var tri = NavMesh.CalculateTriangulation();
|
||||||
|
NavMeshHit h;
|
||||||
|
bool hit = NavMesh.SamplePosition(kAnchor, out h, 5f, NavMesh.AllAreas);
|
||||||
|
Chk("2-1 NavMesh 베이크", tri.vertices != null && tri.vertices.Length > 0 && hit,
|
||||||
|
"정점 " + (tri.vertices != null ? tri.vertices.Length : 0) + " · 앵커 스냅 " + (hit ? h.position.ToString("F2") : "실패"));
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Chk("2-1 NavMesh 베이크", false, "미확인 — " + ex.Message); return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec2_SetClear()
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("③ Set / Clear 왕복 (오브젝트 0 · 풀 반납)");
|
||||||
|
var B = typeof(StageBarrier);
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
|
||||||
|
Chk("3-1 초기", !StageBarrier.Active, "Active=false · IsInside(어디든)=true → " +
|
||||||
|
StageBarrier.IsInside(new Vector3(999f, 0f, 999f)));
|
||||||
|
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
int inst1 = StageBarrierVisual.Instantiates;
|
||||||
|
int live1 = StageBarrierVisual.LiveCount;
|
||||||
|
Chk("3-2 Set", StageBarrier.Active && live1 > 0,
|
||||||
|
"Active=true · 중심 " + StageBarrier.Center.ToString("F1") + " 반경 " + StageBarrier.Radius +
|
||||||
|
" · 그림 인스턴스 " + live1 + " · Instantiate 누계 " + inst1);
|
||||||
|
|
||||||
|
Chk("3-3 IsInside", StageBarrier.IsInside(kAnchor)
|
||||||
|
&& StageBarrier.IsInside(kAnchor + new Vector3(13.9f, 0f, 0f))
|
||||||
|
&& !StageBarrier.IsInside(kAnchor + new Vector3(14.1f, 0f, 0f)),
|
||||||
|
"중심=안 · 13.9 m=안 · 14.1 m=밖 · 높이 무관(y+50 → " +
|
||||||
|
StageBarrier.IsInside(kAnchor + new Vector3(0f, 50f, 0f)) + ")");
|
||||||
|
|
||||||
|
// 클리어 → 페이드 도중에는 아직 살아 있어야 한다(연출)
|
||||||
|
StageBarrier.Clear();
|
||||||
|
StageBarrier.ForceTick();
|
||||||
|
int liveMid = StageBarrierVisual.LiveCount;
|
||||||
|
Chk("3-4a Clear 직후 = 페이드 중", !StageBarrier.Active && liveMid > 0,
|
||||||
|
"Active=false · 페이드 중 활성 " + liveMid + " · 진행도 " + StageBarrierVisual.Phase.ToString("F2"));
|
||||||
|
|
||||||
|
// 페이드 시계를 끝으로 → 실제 종료 경로(ReturnAll)를 탄다
|
||||||
|
StageBarrierVisual.FinishPhaseForProbe();
|
||||||
|
StageBarrier.ForceTick();
|
||||||
|
int live2 = StageBarrierVisual.LiveCount;
|
||||||
|
int pooled = StageBarrierVisual.PooledInstances;
|
||||||
|
Chk("3-4 페이드 끝 → 활성 0 · 풀 반납", !StageBarrier.Active && live2 == 0 && pooled == inst1,
|
||||||
|
"활성 " + live2 + " · 풀 " + pooled + "(= Instantiate 누계 " + inst1 + ") · Destroy 0");
|
||||||
|
|
||||||
|
// 두 번째 Set — 풀에서 꺼내 쓰므로 Instantiate 가 늘지 않아야 한다
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
int inst2 = StageBarrierVisual.Instantiates;
|
||||||
|
Chk("3-5 재Set → 풀 재사용", inst2 == inst1 && StageBarrierVisual.LiveCount == live1,
|
||||||
|
"Instantiate " + inst1 + " → " + inst2 + "(증가 " + (inst2 - inst1) + ") · 활성 " + StageBarrierVisual.LiveCount);
|
||||||
|
|
||||||
|
// 입장 연출
|
||||||
|
StageBarrierVisual.ForcePhase(0f);
|
||||||
|
float s0 = FirstScale();
|
||||||
|
StageBarrierVisual.ForcePhase(0.5f);
|
||||||
|
float s5 = FirstScale();
|
||||||
|
StageBarrierVisual.ForcePhase(1f);
|
||||||
|
float s1 = FirstScale();
|
||||||
|
Chk("3-6 입장/클리어 연출", s0 < s5 && s5 < s1, "스케일 t=0 " + s0.ToString("F2") + " → t=.5 " + s5.ToString("F2") +
|
||||||
|
" → t=1 " + s1.ToString("F2") + " (appear " + WLStageBarrierSettings.Instance.appearSeconds +
|
||||||
|
" s / fade " + WLStageBarrierSettings.Instance.fadeSeconds + " s)");
|
||||||
|
|
||||||
|
Chk("3-7 반경 0 → Clear", true, ProbeZero());
|
||||||
|
_ = B;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string ProbeZero()
|
||||||
|
{
|
||||||
|
StageBarrier.Set(kAnchor, 0f);
|
||||||
|
string r = "Set(r=0) → Active=" + StageBarrier.Active;
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float FirstScale()
|
||||||
|
{
|
||||||
|
var root = StageBarrierVisual.Root;
|
||||||
|
if (root == null || root.childCount == 0) return -1f;
|
||||||
|
for (int i = 0; i < root.childCount; i++)
|
||||||
|
if (root.GetChild(i).gameObject.activeSelf) return root.GetChild(i).localScale.x;
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec3_WarpMath()
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("④ 되돌릴 지점 계산(순수 함수 · NearestInsidePoint)");
|
||||||
|
var c = WLStageBarrierSettings.Instance;
|
||||||
|
float inset = c.warpInsetMeters;
|
||||||
|
float want = kRadius - inset;
|
||||||
|
|
||||||
|
Vector3[] outs = {
|
||||||
|
kAnchor + new Vector3(30f, 0f, 0f),
|
||||||
|
kAnchor + new Vector3(0f, 0f, -25f),
|
||||||
|
kAnchor + new Vector3(-40f, 3f, 40f),
|
||||||
|
kAnchor + new Vector3(14.5f, 0f, 0f),
|
||||||
|
};
|
||||||
|
bool all = true;
|
||||||
|
for (int i = 0; i < outs.Length; i++)
|
||||||
|
{
|
||||||
|
var p = StageBarrier.NearestInsidePoint(c, kAnchor, kRadius, outs[i]);
|
||||||
|
float d = new Vector2(p.x - kAnchor.x, p.z - kAnchor.z).magnitude;
|
||||||
|
bool ok = Mathf.Abs(d - want) < 0.01f;
|
||||||
|
// 방향이 유지되는가(가장 가까운 안쪽 지점 = 같은 방위각)
|
||||||
|
var dirIn = new Vector2(p.x - kAnchor.x, p.z - kAnchor.z).normalized;
|
||||||
|
var dirOut = new Vector2(outs[i].x - kAnchor.x, outs[i].z - kAnchor.z).normalized;
|
||||||
|
bool sameDir = Vector2.Dot(dirIn, dirOut) > 0.999f;
|
||||||
|
all = all && ok && sameDir;
|
||||||
|
L(" " + outs[i].ToString("F1") + " (중심 " +
|
||||||
|
new Vector2(outs[i].x - kAnchor.x, outs[i].z - kAnchor.z).magnitude.ToString("F1") + " m) → " +
|
||||||
|
p.ToString("F2") + " (중심 " + d.ToString("F2") + " m · 방위 유지 " + (sameDir ? "Y" : "N") + ")");
|
||||||
|
}
|
||||||
|
Chk("4-1 안쪽 " + want + " m · 방위 유지", all, "4/4 · inset " + inset + " m");
|
||||||
|
|
||||||
|
var mid = StageBarrier.NearestInsidePoint(c, kAnchor, kRadius, kAnchor);
|
||||||
|
Chk("4-2 정중앙 예외", Mathf.Approximately(mid.x, kAnchor.x) && Mathf.Approximately(mid.z, kAnchor.z),
|
||||||
|
"중심 그대로 " + mid.ToString("F2") + "(0 나누기 없음)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec4_WarpReal(bool navOk)
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("⑤ 실 워프(원본 Actor.Set_Warp · NavMesh 스냅)");
|
||||||
|
var c = WLStageBarrierSettings.Instance;
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
StageBarrier.ResetDiagnostics();
|
||||||
|
|
||||||
|
if (!navOk) { Chk("5-0 NavMesh", false, "미확인 — 베이크 실패라 실 워프 지점은 재지 못했다"); }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Vector3 t;
|
||||||
|
bool ok = StageBarrier.ResolveWarpTarget(c, kAnchor, kRadius, kAnchor + new Vector3(35f, 0f, 0f), out t);
|
||||||
|
float d = new Vector2(t.x - kAnchor.x, t.z - kAnchor.z).magnitude;
|
||||||
|
NavMeshHit h;
|
||||||
|
bool onMesh = NavMesh.SamplePosition(t, out h, 0.3f, NavMesh.AllAreas);
|
||||||
|
Chk("5-1 스냅 지점 = 안쪽 · NavMesh 위", ok && d < kRadius && onMesh,
|
||||||
|
"밖 35 m → " + t.ToString("F2") + " · 중심 " + d.ToString("F2") + " m(< " + kRadius + ") · NavMesh 위 " + onMesh);
|
||||||
|
|
||||||
|
// NavMesh 가 아예 없는 방향(평면 밖 200 m)
|
||||||
|
Vector3 t2;
|
||||||
|
bool ok2 = StageBarrier.ResolveWarpTarget(c, new Vector3(9000f, 0f, 9000f), kRadius,
|
||||||
|
new Vector3(9100f, 0f, 9000f), out t2);
|
||||||
|
Chk("5-2 NavMesh 없으면 워프 안 함", !ok2, "먼 좌표(9000,0,9000) → " + (ok2 ? "워프함(FAIL)" : "false = 워프 포기(지형 뚫기 방지)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 실 PC 프리팹으로 왕복
|
||||||
|
var pcPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Res_Addr/PC/Ai01.prefab");
|
||||||
|
if (pcPrefab == null) { Chk("5-3 PC 프리팹", false, "미확인 — Ai01.prefab 없음"); }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pc = UnityEngine.Object.Instantiate(pcPrefab);
|
||||||
|
pc.transform.position = kAnchor + new Vector3(30f, 0f, 0f);
|
||||||
|
var actor = pc.GetComponent<Actor>();
|
||||||
|
if (actor == null) Chk("5-3 PCActor", false, "미확인 — Actor 컴포넌트 없음");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 🔴 에디트 모드는 Awake 가 돌지 않아 Actor.m_NavMeshAgent(비직렬화 protected)가 null 이다.
|
||||||
|
// Set_Warp 이 그것을 쓰므로 **프로브가** Awake 가 넣었을 값을 직접 심는다(게임 코드 무관 · 스캐폴딩).
|
||||||
|
SeedAgent(actor, pc);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Vector3 before = actor.Get_position();
|
||||||
|
bool warped = StageBarrier.ForceCheck(actor, true);
|
||||||
|
Vector3 after = actor.Get_position();
|
||||||
|
float dAfter = new Vector2(after.x - kAnchor.x, after.z - kAnchor.z).magnitude;
|
||||||
|
Chk("5-3 PC 실 워프", warped && dAfter < kRadius,
|
||||||
|
"밖 " + new Vector2(before.x - kAnchor.x, before.z - kAnchor.z).magnitude.ToString("F1") +
|
||||||
|
" m → 중심 " + dAfter.ToString("F2") + " m · PlayerWarps " + StageBarrier.PlayerWarps +
|
||||||
|
" · " + StageBarrier.LastWarpFrom.ToString("F1") + " → " + StageBarrier.LastWarpTo.ToString("F1"));
|
||||||
|
|
||||||
|
// 안에 있으면 워프하지 않는다
|
||||||
|
actor.transform.position = kAnchor;
|
||||||
|
bool w2 = StageBarrier.ForceCheck(actor, true);
|
||||||
|
Chk("5-4 안에 있으면 무동작", !w2, "중심에서 ForceCheck → 워프 " + (w2 ? "함(FAIL)" : "안 함"));
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Chk("5-3 PC 실 워프", false, "미확인 — " + ex.GetType().Name + " " + ex.Message); }
|
||||||
|
}
|
||||||
|
UnityEngine.Object.DestroyImmediate(pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 몹
|
||||||
|
var mobPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Res_Addr/Mobs/Mob/Batty_A.prefab");
|
||||||
|
if (mobPrefab == null) { Chk("5-5 몹 프리팹", false, "미확인 — Batty_A.prefab 없음"); }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var mob = UnityEngine.Object.Instantiate(mobPrefab);
|
||||||
|
mob.transform.position = kAnchor + new Vector3(0f, 0f, 28f);
|
||||||
|
var ma = mob.GetComponent<Actor>();
|
||||||
|
if (ma == null) Chk("5-5 MobActor", false, "미확인 — Actor 없음");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SeedAgent(ma, mob);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool warped = StageBarrier.ForceCheck(ma, false);
|
||||||
|
float dAfter = new Vector2(ma.Get_position().x - kAnchor.x, ma.Get_position().z - kAnchor.z).magnitude;
|
||||||
|
Chk("5-5 몹 복귀", warped && dAfter < kRadius,
|
||||||
|
"밖 28 m → 중심 " + dAfter.ToString("F2") + " m · MobWarps " + StageBarrier.MobWarps);
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Chk("5-5 몹 복귀", false, "미확인 — " + ex.GetType().Name + " " + ex.Message); }
|
||||||
|
}
|
||||||
|
UnityEngine.Object.DestroyImmediate(mob);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 유예 · 쿨다운 진리표(가짜 시각 없이 — 유예를 0 으로 둔 임시 SO)
|
||||||
|
var tmp = Clone(c);
|
||||||
|
tmp.graceSeconds = 999f;
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(tmp);
|
||||||
|
StageBarrier.ResetDiagnostics();
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
var g = new GameObject("probe_dummy");
|
||||||
|
g.transform.position = kAnchor + new Vector3(40f, 0f, 0f);
|
||||||
|
StageBarrier.ForceTick();
|
||||||
|
StageBarrier.ForceTick();
|
||||||
|
Chk("5-6 유예 안에서는 안 되돌린다", StageBarrier.PlayerWarps == 0 && StageBarrier.MobWarps == 0,
|
||||||
|
"grace 999 s · 틱 ×2 → 워프 0 (OutsideSeen " + StageBarrier.OutsideSeen + ")");
|
||||||
|
UnityEngine.Object.DestroyImmediate(g);
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec5_Styles()
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("⑥ 스타일 전환 3회 (PD 가 값 하나로 바꾼다)");
|
||||||
|
var c = WLStageBarrierSettings.Instance;
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(c);
|
||||||
|
|
||||||
|
var styles = new StageBarrierStyle[] {
|
||||||
|
StageBarrierStyle.MagicRing,
|
||||||
|
StageBarrierStyle.Props,
|
||||||
|
StageBarrierStyle.Fire,
|
||||||
|
};
|
||||||
|
for (int i = 0; i < styles.Length; i++)
|
||||||
|
{
|
||||||
|
StageBarrierVisual.StyleOverride = styles[i];
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
StageBarrierVisual.ForcePhase(1f);
|
||||||
|
int rend, mats, ps; long tri;
|
||||||
|
StageBarrierVisual.MeasureCost(out rend, out mats, out tri, out ps);
|
||||||
|
string extra = "";
|
||||||
|
if (styles[i] == StageBarrierStyle.MagicRing)
|
||||||
|
extra = " · 링 스케일 " + StageBarrierVisual.RingScale.ToString("F2") + "(반경 " + kRadius + " ÷ 실측 " + c.ringPrefabVisualRadius + ")";
|
||||||
|
else if (styles[i] == StageBarrierStyle.Props)
|
||||||
|
extra = " · 프롭 " + StageBarrierVisual.PropCount + "개 · 실간격 " + StageBarrierVisual.PropSpacingUsed.ToString("F2") + " m";
|
||||||
|
else
|
||||||
|
extra = " · 불꽃 " + StageBarrierVisual.FireCount + "개 · 방출률 ×" + StageBarrierVisual.FireRateMul.ToString("F2") +
|
||||||
|
" · 파티클 추정 " + StageBarrierVisual.FireParticleEstimate + "(개당 " + StageBarrierVisual.FirePerInstanceEstimate + ")";
|
||||||
|
Chk("6-" + (i + 1) + " " + styles[i], StageBarrierVisual.CurrentStyle == styles[i] && StageBarrierVisual.LiveCount > 0,
|
||||||
|
"활성 " + StageBarrierVisual.LiveCount + " · 렌더러 " + rend + " · 머티리얼 " + mats + " · tri " + tri +
|
||||||
|
" · PS " + ps + " · 빌드 " + StageBarrierVisual.LastBuildMs.ToString("F1") + " ms" + extra);
|
||||||
|
}
|
||||||
|
Chk("6-4 전환 누계", StageBarrierVisual.StyleSwitches >= 2,
|
||||||
|
"StyleSwitches " + StageBarrierVisual.StyleSwitches + " · Instantiate 누계 " + StageBarrierVisual.Instantiates);
|
||||||
|
|
||||||
|
// None 으로 되돌리기
|
||||||
|
StageBarrierVisual.StyleOverride = StageBarrierStyle.None;
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
Chk("6-5 None → 그림 0", StageBarrierVisual.LiveCount == 0 && StageBarrier.Active,
|
||||||
|
"활성 그림 " + StageBarrierVisual.LiveCount + " · 판정은 그대로 Active=" + StageBarrier.Active);
|
||||||
|
StageBarrierVisual.StyleOverride = (StageBarrierStyle)(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec6_Carve()
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("⑦ NavMesh 카브 on/off (옵션 · 기준서 §D-2 ②)");
|
||||||
|
var c = WLStageBarrierSettings.Instance;
|
||||||
|
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(c);
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
Chk("7-1 off(기본) → 카브 0", StageBarrier.CarveObjectCount == 0,
|
||||||
|
"carveNavMesh " + (c.carveNavMesh ? 1 : 0) + " → 오브젝트 " + StageBarrier.CarveObjectCount);
|
||||||
|
|
||||||
|
var tmp = Clone(c);
|
||||||
|
tmp.carveNavMesh = true;
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(tmp);
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(tmp);
|
||||||
|
|
||||||
|
int want = StageBarrier.CarveSegmentCount(tmp, kRadius);
|
||||||
|
float circ = 2f * Mathf.PI * kRadius;
|
||||||
|
|
||||||
|
var before = NavMesh.CalculateTriangulation();
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
sw.Stop();
|
||||||
|
Chk("7-2 on → 세그먼트 생성", StageBarrier.CarveObjectCount == want && want > 0,
|
||||||
|
"둘레 " + circ.ToString("F1") + " m ÷ arc " + tmp.carveSegmentArcMeters + " m = " + want +
|
||||||
|
"(상한 " + tmp.maxCarveSegments + ") · 오브젝트 " + StageBarrier.CarveObjectCount +
|
||||||
|
" · 생성 " + StageBarrier.LastCarveBuildMs.ToString("F2") + " ms · Set 전체 " + sw.Elapsed.TotalMilliseconds.ToString("F2") + " ms");
|
||||||
|
|
||||||
|
// 세그먼트 기하 확인 — 카브 오브젝트는 HideAndDontSave 라 FindObjectsByType 에 안 잡힌다(내부 목록으로 읽는다)
|
||||||
|
int carving = 0, boxShape = 0; float gap = -1f, sizeX = 0f, sizeZ = 0f, ringDev = 0f;
|
||||||
|
Vector3 p0 = Vector3.zero, p1 = Vector3.zero;
|
||||||
|
for (int i = 0; i < StageBarrier.CarveObjectCount; i++)
|
||||||
|
{
|
||||||
|
var ob = StageBarrier.CarveObstacleAt(i);
|
||||||
|
if (ob == null) continue;
|
||||||
|
if (ob.carving) carving++;
|
||||||
|
if (ob.shape == NavMeshObstacleShape.Box) boxShape++;
|
||||||
|
if (i == 0) { p0 = ob.transform.position; sizeX = ob.size.x; sizeZ = ob.size.z; }
|
||||||
|
if (i == 1) p1 = ob.transform.position;
|
||||||
|
ringDev = Mathf.Max(ringDev, Mathf.Abs(
|
||||||
|
new Vector2(ob.transform.position.x - kAnchor.x, ob.transform.position.z - kAnchor.z).magnitude - kRadius));
|
||||||
|
}
|
||||||
|
gap = Vector3.Distance(p0, p1);
|
||||||
|
float step = circ / Mathf.Max(1, want);
|
||||||
|
Chk("7-3 세그먼트 기하", carving == want && boxShape == want && sizeX > gap && ringDev < 0.01f,
|
||||||
|
"carving=true " + carving + " · Box " + boxShape + " · 상자 " + sizeX.ToString("F2") + " × 두께 " + sizeZ.ToString("F2") +
|
||||||
|
" m > 이웃 간격 " + gap.ToString("F2") + " m(이론 " + step.ToString("F2") + " · 겹침 " + (tmp.carveOverlap * 100f).ToString("F0") +
|
||||||
|
" %) · 원 이탈 최대 " + ringDev.ToString("F4") + " m");
|
||||||
|
|
||||||
|
// 재베이크(카브 반영) 비용 — 에디트 모드에서 실제로 반영되는지
|
||||||
|
var after = NavMesh.CalculateTriangulation();
|
||||||
|
L(" NavMesh 삼각형 " + (before.indices != null ? before.indices.Length / 3 : 0) + " → " + (after.indices != null ? after.indices.Length / 3 : 0) +
|
||||||
|
" (에디트 모드는 carve 가 즉시 반영되지 않을 수 있다 — 실 반영·재베이크 ms 는 Play 확인)");
|
||||||
|
|
||||||
|
StageBarrier.Clear();
|
||||||
|
Chk("7-4 Clear → 카브 회수", StageBarrier.CarveObjectCount == 0, "오브젝트 " + StageBarrier.CarveObjectCount);
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(c);
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec7_GC()
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("⑧ GC (프레임당 할당 0)");
|
||||||
|
var c = WLStageBarrierSettings.Instance;
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
StageBarrierVisual.ForcePhase(1f);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) StageBarrier.ProbeTick(); // 워밍
|
||||||
|
long a = Profiler.GetMonoUsedSizeLong();
|
||||||
|
for (int i = 0; i < 200; i++) StageBarrier.ProbeTick();
|
||||||
|
long b = Profiler.GetMonoUsedSizeLong();
|
||||||
|
Chk("8-1 반경 안 · 틱 ×200", b - a == 0, "할당 " + (b - a) + " B (틱마다 " + ((b - a) / 200f).ToString("F1") + " B) · 계측 = Profiler.GetMonoUsedSizeLong");
|
||||||
|
|
||||||
|
// 대조군 — 확실히 할당되는 배열 200개(계측기가 살아 있는지 확인)
|
||||||
|
long c0 = Profiler.GetMonoUsedSizeLong();
|
||||||
|
object sink = null;
|
||||||
|
for (int i = 0; i < 200; i++) sink = new byte[65536];
|
||||||
|
long c1 = Profiler.GetMonoUsedSizeLong();
|
||||||
|
L(" 대조군(byte[65536] ×200 = 12.8 MB) " + (c1 - c0) + " B · sink " + (sink != null));
|
||||||
|
|
||||||
|
// 폴링 검사가 실제로 도는지 — 씬에 몹을 하나 놓고 재수집·판정이 도는지 본다
|
||||||
|
var mobPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Res_Addr/Mobs/Mob/Batty_A.prefab");
|
||||||
|
GameObject probeMob = null;
|
||||||
|
if (mobPrefab != null)
|
||||||
|
{
|
||||||
|
probeMob = UnityEngine.Object.Instantiate(mobPrefab);
|
||||||
|
probeMob.transform.position = kAnchor; // 안쪽 = 워프 대상 아님
|
||||||
|
}
|
||||||
|
int chk0 = StageBarrier.Checks;
|
||||||
|
StageBarrier.ForceTick();
|
||||||
|
Chk("8-2 폴링 동작", StageBarrier.Scans > 0 && (probeMob == null || StageBarrier.Checks > chk0),
|
||||||
|
"Scans " + StageBarrier.Scans + " · Checks " + chk0 + " → " + StageBarrier.Checks +
|
||||||
|
" (몹 " + (probeMob != null ? 1 : 0) + "기 · 안쪽이라 워프 " + (StageBarrier.MobWarps) + ")");
|
||||||
|
|
||||||
|
// 몹이 있는 상태에서 다시 GC 측정(실전에 가까운 조건)
|
||||||
|
for (int i = 0; i < 10; i++) StageBarrier.ProbeTick();
|
||||||
|
long d0 = Profiler.GetMonoUsedSizeLong();
|
||||||
|
for (int i = 0; i < 200; i++) StageBarrier.ProbeTick();
|
||||||
|
long d1 = Profiler.GetMonoUsedSizeLong();
|
||||||
|
Chk("8-3 몹 1기 · 틱 ×200", d1 - d0 == 0, "할당 " + (d1 - d0) + " B");
|
||||||
|
if (probeMob != null) UnityEngine.Object.DestroyImmediate(probeMob);
|
||||||
|
_ = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec8_C8()
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("⑨ C8 롤백 진리표");
|
||||||
|
var real = WLStageBarrierSettings.Instance;
|
||||||
|
|
||||||
|
// enabled = 0
|
||||||
|
var off = Clone(real); off.enabled = false;
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(off);
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
Chk("9-1 enabled=0 → Set 무동작", !StageBarrier.Active && StageBarrierVisual.Instantiates == 0,
|
||||||
|
"Active=" + StageBarrier.Active + " · Instantiate " + StageBarrierVisual.Instantiates + " (오브젝트 0)");
|
||||||
|
|
||||||
|
// RuntimeDisabled
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(real);
|
||||||
|
WLStageBarrierSettings.RuntimeDisabled = true;
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
Chk("9-2 RuntimeDisabled=1 → Set 무동작", !StageBarrier.Active,
|
||||||
|
"Active=" + StageBarrier.Active + " · Instantiate " + StageBarrierVisual.Instantiates);
|
||||||
|
WLStageBarrierSettings.RuntimeDisabled = false;
|
||||||
|
|
||||||
|
// 에셋 없음
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(null);
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
Chk("9-3 에셋 없음 → Set 무동작", !StageBarrier.Active && !StageBarrier.FeatureEnabled,
|
||||||
|
"Active=" + StageBarrier.Active + " · FeatureEnabled=" + StageBarrier.FeatureEnabled);
|
||||||
|
|
||||||
|
// blockEnabled = 0 → 그림만
|
||||||
|
var noBlock = Clone(real); noBlock.blockEnabled = false;
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(noBlock);
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
var d = new GameObject("probe_far"); d.transform.position = kAnchor + new Vector3(60f, 0f, 0f);
|
||||||
|
for (int i = 0; i < 5; i++) StageBarrier.ForceTick();
|
||||||
|
Chk("9-4 blockEnabled=0 → 판정 0 · 그림은 남는다",
|
||||||
|
StageBarrier.Checks == 0 && StageBarrierVisual.LiveCount > 0,
|
||||||
|
"Checks " + StageBarrier.Checks + " · 그림 " + StageBarrierVisual.LiveCount);
|
||||||
|
UnityEngine.Object.DestroyImmediate(d);
|
||||||
|
|
||||||
|
// showVisual = 0 → 판정만
|
||||||
|
var noVis = Clone(real); noVis.showVisual = false;
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(noVis);
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
Chk("9-5 showVisual=0 → 그림 0 · 판정은 산다",
|
||||||
|
StageBarrierVisual.LiveCount == 0 && StageBarrier.Active,
|
||||||
|
"그림 " + StageBarrierVisual.LiveCount + " · Active=" + StageBarrier.Active);
|
||||||
|
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(real);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static void Sec9_Cost()
|
||||||
|
{
|
||||||
|
L("");
|
||||||
|
L("⑩ 성능 표 (반경 14 m · 스타일별)");
|
||||||
|
var c = WLStageBarrierSettings.Instance;
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(c);
|
||||||
|
|
||||||
|
L(" | 스타일 | 인스턴스 | 렌더러 | 머티리얼 | 삼각형 | 파티클시스템 | 정상상태 파티클 | 빌드 ms |");
|
||||||
|
L(" |---|---|---|---|---|---|---|---|");
|
||||||
|
var styles = new StageBarrierStyle[] {
|
||||||
|
StageBarrierStyle.MagicRing,
|
||||||
|
StageBarrierStyle.Props,
|
||||||
|
StageBarrierStyle.Fire,
|
||||||
|
};
|
||||||
|
for (int i = 0; i < styles.Length; i++)
|
||||||
|
{
|
||||||
|
StageBarrierVisual.StyleOverride = styles[i];
|
||||||
|
StageBarrier.Set(kAnchor, kRadius);
|
||||||
|
StageBarrierVisual.ForcePhase(1f);
|
||||||
|
int rend, mats, ps; long tri;
|
||||||
|
StageBarrierVisual.MeasureCost(out rend, out mats, out tri, out ps);
|
||||||
|
int est = styles[i] == StageBarrierStyle.Fire ? StageBarrierVisual.FireParticleEstimate
|
||||||
|
: (styles[i] == StageBarrierStyle.MagicRing ? StageBarrierVisual.LiveCount * 4 : 0);
|
||||||
|
L(" | " + styles[i] + " | " + StageBarrierVisual.LiveCount + " | " + rend + " | " + mats +
|
||||||
|
" | " + tri + " | " + ps + " | " + est + " | " + StageBarrierVisual.LastBuildMs.ToString("F1") + " |");
|
||||||
|
}
|
||||||
|
StageBarrierVisual.StyleOverride = (StageBarrierStyle)(-1);
|
||||||
|
Chk("10-1 성능 표", true, "위 표 참조(에디트 모드 측정 · 실 드로우콜은 캡처의 UnityStats)");
|
||||||
|
|
||||||
|
// 반경별 개수
|
||||||
|
L(" 반경별 프롭 개수(간격 " + c.propSpacing + " m · 상한 " + c.maxProps + ")");
|
||||||
|
float[] rs = { 8f, 14f, 20f, 30f, 40f };
|
||||||
|
StageBarrierVisual.StyleOverride = StageBarrierStyle.Props;
|
||||||
|
for (int i = 0; i < rs.Length; i++)
|
||||||
|
{
|
||||||
|
StageBarrier.Set(kAnchor, rs[i]);
|
||||||
|
L(" r " + rs[i] + " m(둘레 " + (2f * Mathf.PI * rs[i]).ToString("F1") + " m) → 프롭 " +
|
||||||
|
StageBarrierVisual.PropCount + " · 실간격 " + StageBarrierVisual.PropSpacingUsed.ToString("F2") +
|
||||||
|
" m · 카브 세그(on 일 때) " + StageBarrier.CarveSegmentCount(c, rs[i]));
|
||||||
|
}
|
||||||
|
StageBarrierVisual.StyleOverride = (StageBarrierStyle)(-1);
|
||||||
|
StageBarrier.HardResetForProbe();
|
||||||
|
WLStageBarrierSettings.SetInstanceForProbe(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 에디트 모드 스캐폴딩 — Awake 가 넣었을 `Actor.m_NavMeshAgent`(비직렬화 protected)를 심는다.
|
||||||
|
/// 게임 코드에는 리플렉션이 0 이고, 이 프로브에서만 쓴다.
|
||||||
|
/// </summary>
|
||||||
|
static string SeedAgent(Actor a, GameObject go)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var agent = go.GetComponent<UnityEngine.AI.NavMeshAgent>();
|
||||||
|
if (agent == null) agent = go.GetComponentInChildren<UnityEngine.AI.NavMeshAgent>(true);
|
||||||
|
if (agent == null) return "NavMeshAgent 없음";
|
||||||
|
var fi = typeof(Actor).GetField("m_NavMeshAgent",
|
||||||
|
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
||||||
|
if (fi == null) return "필드 없음";
|
||||||
|
fi.SetValue(a, agent);
|
||||||
|
return "seeded";
|
||||||
|
}
|
||||||
|
catch (Exception ex) { return ex.Message; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
static WLStageBarrierSettings Clone(WLStageBarrierSettings src)
|
||||||
|
{
|
||||||
|
var t = UnityEngine.Object.Instantiate(src);
|
||||||
|
t.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
WL-815c 장벽 보이기 후보 실측 · 2026-09-11 00:34:25
|
||||||
|
|
||||||
|
── ⓐ 링 후보 (Hovl) ─────────────────────────────────────────────
|
||||||
|
Marker 3 Zone Loop
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 3 Zone Loop.prefab
|
||||||
|
렌더러 4(메시 0 · 파티클 4) · 머티리얼 1 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 4 · maxParticles 합 4000 · rateOverTime 합 0.0/s · 최대수명 1.00 s · 정상상태 추정 0 · duration 1.00 · loop Y · shape radius 0.00
|
||||||
|
Marker 5 Circle Loop
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 5 Circle Loop.prefab
|
||||||
|
렌더러 4(메시 0 · 파티클 4) · 머티리얼 1 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 4 · maxParticles 합 4000 · rateOverTime 합 0.0/s · 최대수명 2.00 s · 정상상태 추정 0 · duration 1.00 · loop Y · shape radius 0.00
|
||||||
|
Marker 7 Danger zone Loop
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 7 Danger zone Loop.prefab
|
||||||
|
렌더러 2(메시 0 · 파티클 2) · 머티리얼 1 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 2 · maxParticles 합 2000 · rateOverTime 합 0.0/s · 최대수명 1.00 s · 정상상태 추정 0 · duration 1.00 · loop Y · shape radius 0.00
|
||||||
|
Marker 8 Safe zone Loop
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 8 Safe zone Loop.prefab
|
||||||
|
렌더러 2(메시 0 · 파티클 2) · 머티리얼 1 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 2 · maxParticles 합 2000 · rateOverTime 합 0.0/s · 최대수명 1.00 s · 정상상태 추정 0 · duration 1.00 · loop Y · shape radius 0.00
|
||||||
|
Marker 4 Pillar Loop
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 4 Pillar Loop.prefab
|
||||||
|
렌더러 4(메시 0 · 파티클 4) · 머티리얼 1 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 4 · maxParticles 합 4000 · rateOverTime 합 0.0/s · 최대수명 2.00 s · 정상상태 추정 0 · duration 1.00 · loop Y · shape radius 0.00
|
||||||
|
Magic circle 1 loop
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Magic circles/Prefabs/Loop version/Magic circle 1 loop.prefab
|
||||||
|
렌더러 11(메시 0 · 파티클 11) · 머티리얼 9 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 11 · maxParticles 합 11000 · rateOverTime 합 142.0/s · 최대수명 2.00 s · 정상상태 추정 284 · duration 1.00 · loop Y · shape radius 1.10
|
||||||
|
Magic circle 3 loop
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Magic circles/Prefabs/Loop version/Magic circle 3 loop.prefab
|
||||||
|
렌더러 7(메시 0 · 파티클 7) · 머티리얼 7 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 7 · maxParticles 합 7000 · rateOverTime 합 0.0/s · 최대수명 1.25 s · 정상상태 추정 0 · duration 1.00 · loop Y · shape radius 2.00
|
||||||
|
Magic circle 5 loop
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Magic circles/Prefabs/Loop version/Magic circle 5 loop.prefab
|
||||||
|
렌더러 7(메시 0 · 파티클 7) · 머티리얼 7 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 7 · maxParticles 합 7000 · rateOverTime 합 0.0/s · 최대수명 2.00 s · 정상상태 추정 0 · duration 1.00 · loop Y · shape radius 0.50
|
||||||
|
|
||||||
|
── ⓑ 프롭 후보 (울타리 · 바위 · 기둥) ───────────────────────────
|
||||||
|
Fence_01
|
||||||
|
경로 Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/Fence_01.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 120
|
||||||
|
바운즈 크기 (2.38, 1.02, 0.27) · 중심 (0.00, 0.50, 0.00) · 최대반경(XZ) 1.19 m
|
||||||
|
Fence_02
|
||||||
|
경로 Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/Fence_02.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 100
|
||||||
|
바운즈 크기 (2.20, 1.02, 0.27) · 중심 (-0.96, 0.51, 0.00) · 최대반경(XZ) 1.10 m
|
||||||
|
FenceBig_01
|
||||||
|
경로 Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/FenceBig_01.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 324
|
||||||
|
바운즈 크기 (2.26, 3.41, 0.32) · 중심 (0.01, 1.70, -0.01) · 최대반경(XZ) 1.13 m
|
||||||
|
FenceBig_02
|
||||||
|
경로 Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/FenceBig_02.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 324
|
||||||
|
바운즈 크기 (2.26, 3.41, 0.32) · 중심 (0.01, 1.69, -0.01) · 최대반경(XZ) 1.13 m
|
||||||
|
FenceBig_01
|
||||||
|
경로 Assets/LowPolyFantasyArena/Prefabs/FenceBig_01.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 42
|
||||||
|
바운즈 크기 (0.48, 6.39, 0.51) · 중심 (-0.01, 3.19, 0.00) · 최대반경(XZ) 0.25 m
|
||||||
|
FenceBig_02
|
||||||
|
경로 Assets/LowPolyFantasyArena/Prefabs/FenceBig_02.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 20
|
||||||
|
바운즈 크기 (0.37, 4.81, 0.38) · 중심 (0.00, 2.40, 0.02) · 최대반경(XZ) 0.19 m
|
||||||
|
fence_1
|
||||||
|
경로 Assets/ResWork/Aligned Games/Polygonal Woodlands Nature Asset Package/Models/Fence1/fence_1.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 120
|
||||||
|
바운즈 크기 (5.01, 2.50, 0.45) · 중심 (-0.01, 1.25, -0.06) · 최대반경(XZ) 2.51 m
|
||||||
|
fence_2
|
||||||
|
경로 Assets/ResWork/Aligned Games/Polygonal Woodlands Nature Asset Package/Models/Fence2/fence_2.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 120
|
||||||
|
바운즈 크기 (5.01, 2.50, 0.45) · 중심 (0.00, 1.25, -0.06) · 최대반경(XZ) 2.50 m
|
||||||
|
Stone_1
|
||||||
|
경로 Assets/3DPixelArtEnvironment/Prefabs/Stone_1.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 36
|
||||||
|
바운즈 크기 (1.29, 1.62, 1.50) · 중심 (0.00, 0.00, 0.12) · 최대반경(XZ) 0.75 m
|
||||||
|
Stone_2
|
||||||
|
경로 Assets/3DPixelArtEnvironment/Prefabs/Stone_2.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 44
|
||||||
|
바운즈 크기 (2.42, 2.62, 2.64) · 중심 (-0.07, 0.04, 0.04) · 최대반경(XZ) 1.32 m
|
||||||
|
Slab
|
||||||
|
경로 Assets/3DPixelArtEnvironment/Prefabs/Slab.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 80
|
||||||
|
바운즈 크기 (3.37, 0.91, 4.41) · 중심 (0.37, -0.15, 0.73) · 최대반경(XZ) 2.20 m
|
||||||
|
Arch_Small
|
||||||
|
경로 Assets/3DPixelArtEnvironment/Prefabs/Arch_Small.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 261
|
||||||
|
바운즈 크기 (13.58, 14.70, 11.91) · 중심 (2.09, 7.36, -12.48) · 최대반경(XZ) 6.79 m
|
||||||
|
Light_Post
|
||||||
|
경로 Assets/3DPixelArtEnvironment/Prefabs/Light_Post.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 68
|
||||||
|
바운즈 크기 (3.67, 18.24, 7.27) · 중심 (0.00, 8.12, -1.51) · 최대반경(XZ) 3.64 m
|
||||||
|
|
||||||
|
── LMHPOLY 바위 (자연 번들) ────────────────────────────────────
|
||||||
|
Cloud_01
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Clouds/Cloud_01.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 100
|
||||||
|
바운즈 크기 (18.31, 8.76, 8.70) · 중심 (0.64, 0.34, -0.45) · 최대반경(XZ) 9.16 m
|
||||||
|
Cloud_02
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Clouds/Cloud_02.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 100
|
||||||
|
바운즈 크기 (14.14, 6.68, 7.58) · 중심 (0.12, 0.44, -0.32) · 최대반경(XZ) 7.07 m
|
||||||
|
Cloud_03
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Clouds/Cloud_03.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 100
|
||||||
|
바운즈 크기 (31.30, 12.15, 11.05) · 중심 (-1.67, 1.71, -0.57) · 최대반경(XZ) 15.65 m
|
||||||
|
Ground_m_01
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Ground/Ground_m_01.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 3872
|
||||||
|
바운즈 크기 (100.00, 0.86, 100.00) · 중심 (-50.00, -0.37, 50.00) · 최대반경(XZ) 50.00 m
|
||||||
|
Ground_m_02
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Ground/Ground_m_02.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 3872
|
||||||
|
바운즈 크기 (100.00, 9.82, 100.00) · 중심 (-50.00, 2.67, 50.00) · 최대반경(XZ) 50.00 m
|
||||||
|
Ground_m_03
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Ground/Ground_m_03.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 3872
|
||||||
|
바운즈 크기 (100.00, 8.21, 100.00) · 중심 (-50.00, -2.01, 50.00) · 최대반경(XZ) 50.00 m
|
||||||
|
Ground_m_04
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Ground/Ground_m_04.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 3872
|
||||||
|
바운즈 크기 (100.00, 7.24, 100.00) · 중심 (-50.00, -3.57, 50.00) · 최대반경(XZ) 50.00 m
|
||||||
|
Ground_m_05
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Ground/Ground_m_05.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 3872
|
||||||
|
바운즈 크기 (100.00, 29.22, 100.00) · 중심 (-50.00, 14.58, 50.00) · 최대반경(XZ) 50.00 m
|
||||||
|
Ground_m_06
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Ground/Ground_m_06.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 3872
|
||||||
|
바운즈 크기 (100.00, 4.88, 100.00) · 중심 (-50.00, 2.28, 50.00) · 최대반경(XZ) 50.00 m
|
||||||
|
Ground_m_07
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/Ground/Ground_m_07.prefab
|
||||||
|
렌더러 1(메시 1 · 파티클 0) · 머티리얼 1 · 삼각형 3872
|
||||||
|
바운즈 크기 (100.00, 27.42, 100.00) · 중심 (-50.00, 9.70, 50.00) · 최대반경(XZ) 50.00 m
|
||||||
|
(Rocks 폴더 프리팹 총 1455개)
|
||||||
|
|
||||||
|
── ⓒ 불꽃 후보 ────────────────────────────────────────────────
|
||||||
|
Fire_Particles
|
||||||
|
경로 Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/ParticleSystem/Fire_Particles.prefab
|
||||||
|
렌더러 3(메시 0 · 파티클 3) · 머티리얼 3 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 3 · maxParticles 합 3000 · rateOverTime 합 45.0/s · 최대수명 8.00 s · 정상상태 추정 360 · duration 5.00 · loop Y · shape radius 0.46
|
||||||
|
Fire buff
|
||||||
|
경로 Assets/ResWork/VFX/Hovl Studio/Epic Toon VFX 2/Prefabs/Fire buff/Fire buff.prefab
|
||||||
|
렌더러 19(메시 0 · 파티클 19) · 머티리얼 12 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 19 · maxParticles 합 19000 · rateOverTime 합 0.0/s · 최대수명 2.20 s · 정상상태 추정 0 · duration 3.00 · loop Y · shape radius 5.00
|
||||||
|
SK_Flame
|
||||||
|
경로 Assets/ResWork/ModularDungeon/Prefabs/SK_Flame.prefab
|
||||||
|
렌더러 1(메시 0 · 파티클 0) · 머티리얼 1 · 삼각형 0
|
||||||
|
바운즈 크기 (0.01, 0.03, 0.01) · 중심 (0.00, 0.01, 0.00) · 최대반경(XZ) 0.01 m
|
||||||
|
FX_Flameflow_floor
|
||||||
|
경로 Assets/ResWork/EricWang/GameVFX Collection Vol3/Prefabs/FX_Flameflow_floor.prefab
|
||||||
|
렌더러 14(메시 0 · 파티클 14) · 머티리얼 11 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 14 · maxParticles 합 11004 · rateOverTime 합 365.0/s · 최대수명 5.90 s · 정상상태 추정 2154 · duration 5.50 · loop Y · shape radius 6.60
|
||||||
|
PS_Cast_Fire
|
||||||
|
경로 Assets/ResWork/VFX/StylizedFantasyFxPack/Prefab/PS_Cast_Fire.prefab
|
||||||
|
렌더러 6(메시 0 · 파티클 6) · 머티리얼 6 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 6 · maxParticles 합 7 · rateOverTime 합 0.0/s · 최대수명 5.00 s · 정상상태 추정 0 · duration 1.00 · loop N · shape radius 0.05
|
||||||
|
Effect_Hit_Mob_HellfireFlames
|
||||||
|
경로 Assets/Res_Addr/Effect/Effect_Hit_Mob_HellfireFlames.prefab
|
||||||
|
렌더러 8(메시 0 · 파티클 8) · 머티리얼 6 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 8 · maxParticles 합 2013 · rateOverTime 합 115.0/s · 최대수명 0.70 s · 정상상태 추정 81 · duration 0.30 · loop N · shape radius 0.20
|
||||||
|
|
||||||
|
── 불꽃 추가 탐색(이름에 Fire/Flame · 파티클 보유 · 루프) ───────
|
||||||
|
Slash Projectile VFX Fire
|
||||||
|
경로 Assets/ResWork/VFX/Free Slash VFX/Prefabs/Projectiles/Slash Projectile VFX Fire.prefab
|
||||||
|
렌더러 13(메시 0 · 파티클 13) · 머티리얼 8 · 삼각형 0
|
||||||
|
바운즈: 없음(메시 0)
|
||||||
|
파티클시스템 13 · maxParticles 합 13000 · rateOverTime 합 910.0/s · 최대수명 2.00 s · 정상상태 추정 1820 · duration 1.00 · loop Y · shape radius 1.40
|
||||||
|
vfx_Projectile_ArrowFire
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_1/Prefabs/Projectiles/vfx_Projectile_ArrowFire.prefab
|
||||||
|
렌더러 6(메시 0 · 파티클 5) · 머티리얼 3 · 삼각형 0
|
||||||
|
바운즈 크기 (0.15, 0.15, 0.15) · 중심 (0.00, 0.00, 0.40) · 최대반경(XZ) 0.08 m
|
||||||
|
파티클시스템 5 · maxParticles 합 132 · rateOverTime 합 20.0/s · 최대수명 1.00 s · 정상상태 추정 20 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball01Blue
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_1/Prefabs/Projectiles/vfx_Projectile_Fireball01Blue.prefab
|
||||||
|
렌더러 7(메시 0 · 파티클 6) · 머티리얼 5 · 삼각형 0
|
||||||
|
바운즈 크기 (1.00, 1.00, 1.00) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 0.50 m
|
||||||
|
파티클시스템 6 · maxParticles 합 1019 · rateOverTime 합 51.0/s · 최대수명 1.00 s · 정상상태 추정 51 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball01Orange
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_1/Prefabs/Projectiles/vfx_Projectile_Fireball01Orange.prefab
|
||||||
|
렌더러 7(메시 0 · 파티클 6) · 머티리얼 5 · 삼각형 0
|
||||||
|
바운즈 크기 (1.00, 1.00, 1.00) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 0.50 m
|
||||||
|
파티클시스템 6 · maxParticles 합 1015 · rateOverTime 합 53.0/s · 최대수명 1.00 s · 정상상태 추정 53 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball02Green
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_1/Prefabs/Projectiles/vfx_Projectile_Fireball02Green.prefab
|
||||||
|
렌더러 6(메시 0 · 파티클 5) · 머티리얼 3 · 삼각형 0
|
||||||
|
바운즈 크기 (2.00, 2.00, 2.00) · 중심 (0.00, 0.00, 0.60) · 최대반경(XZ) 1.00 m
|
||||||
|
파티클시스템 5 · maxParticles 합 2008 · rateOverTime 합 80.0/s · 최대수명 1.00 s · 정상상태 추정 80 · duration 1.00 · loop Y · shape radius 0.70
|
||||||
|
vfx_Projectile_Fireball02Orange
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_1/Prefabs/Projectiles/vfx_Projectile_Fireball02Orange.prefab
|
||||||
|
렌더러 6(메시 0 · 파티클 5) · 머티리얼 3 · 삼각형 0
|
||||||
|
바운즈 크기 (2.00, 2.00, 2.00) · 중심 (0.00, 0.00, 0.60) · 최대반경(XZ) 1.00 m
|
||||||
|
파티클시스템 5 · maxParticles 합 2008 · rateOverTime 합 80.0/s · 최대수명 1.00 s · 정상상태 추정 80 · duration 1.00 · loop Y · shape radius 0.70
|
||||||
|
vfx_Projectile_SwordFire
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_1/Prefabs/Projectiles/vfx_Projectile_SwordFire.prefab
|
||||||
|
렌더러 7(메시 0 · 파티클 6) · 머티리얼 3 · 삼각형 0
|
||||||
|
바운즈 크기 (0.20, 0.20, 0.20) · 중심 (0.00, 0.00, 0.90) · 최대반경(XZ) 0.10 m
|
||||||
|
파티클시스템 6 · maxParticles 합 207 · rateOverTime 합 27.0/s · 최대수명 1.00 s · 정상상태 추정 27 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball03_Blue
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_2/Prefabs/Projectiles/vfx_Projectile_Fireball03_Blue.prefab
|
||||||
|
렌더러 12(메시 0 · 파티클 11) · 머티리얼 6 · 삼각형 0
|
||||||
|
바운즈 크기 (3.00, 3.00, 3.00) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 1.50 m
|
||||||
|
파티클시스템 11 · maxParticles 합 3135 · rateOverTime 합 150.0/s · 최대수명 1.00 s · 정상상태 추정 150 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball03_Green
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_2/Prefabs/Projectiles/vfx_Projectile_Fireball03_Green.prefab
|
||||||
|
렌더러 12(메시 0 · 파티클 11) · 머티리얼 6 · 삼각형 0
|
||||||
|
바운즈 크기 (3.00, 3.00, 3.00) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 1.50 m
|
||||||
|
파티클시스템 11 · maxParticles 합 3135 · rateOverTime 합 150.0/s · 최대수명 1.00 s · 정상상태 추정 150 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball03_Orange
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_2/Prefabs/Projectiles/vfx_Projectile_Fireball03_Orange.prefab
|
||||||
|
렌더러 12(메시 0 · 파티클 11) · 머티리얼 6 · 삼각형 0
|
||||||
|
바운즈 크기 (3.00, 3.00, 3.00) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 1.50 m
|
||||||
|
파티클시스템 11 · maxParticles 합 3132 · rateOverTime 합 150.0/s · 최대수명 1.00 s · 정상상태 추정 150 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball04_Blue
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_2/Prefabs/Projectiles/vfx_Projectile_Fireball04_Blue.prefab
|
||||||
|
렌더러 11(메시 0 · 파티클 10) · 머티리얼 4 · 삼각형 0
|
||||||
|
바운즈 크기 (1.30, 1.30, 1.30) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 0.65 m
|
||||||
|
파티클시스템 10 · maxParticles 합 3408 · rateOverTime 합 189.0/s · 최대수명 1.10 s · 정상상태 추정 208 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball04_Green
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_2/Prefabs/Projectiles/vfx_Projectile_Fireball04_Green.prefab
|
||||||
|
렌더러 11(메시 0 · 파티클 10) · 머티리얼 4 · 삼각형 0
|
||||||
|
바운즈 크기 (1.30, 1.30, 1.30) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 0.65 m
|
||||||
|
파티클시스템 10 · maxParticles 합 3408 · rateOverTime 합 189.0/s · 최대수명 1.10 s · 정상상태 추정 208 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball04_Orange
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_2/Prefabs/Projectiles/vfx_Projectile_Fireball04_Orange.prefab
|
||||||
|
렌더러 11(메시 0 · 파티클 10) · 머티리얼 4 · 삼각형 0
|
||||||
|
바운즈 크기 (1.30, 1.30, 1.30) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 0.65 m
|
||||||
|
파티클시스템 10 · maxParticles 합 3408 · rateOverTime 합 189.0/s · 최대수명 1.10 s · 정상상태 추정 208 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
vfx_Projectile_Fireball05_Blue
|
||||||
|
경로 Assets/ResWork/VFX/GabrielAguiarProductions/UniqueProjectilesVol_2/Prefabs/Projectiles/vfx_Projectile_Fireball05_Blue.prefab
|
||||||
|
렌더러 11(메시 0 · 파티클 10) · 머티리얼 4 · 삼각형 0
|
||||||
|
바운즈 크기 (2.45, 2.45, 2.45) · 중심 (0.00, 0.00, 0.00) · 최대반경(XZ) 1.23 m
|
||||||
|
파티클시스템 10 · maxParticles 합 4283 · rateOverTime 합 175.0/s · 최대수명 1.00 s · 정상상태 추정 175 · duration 1.00 · loop Y · shape radius 1.00
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
WL-815c 2차 실측 · 2026-09-11 00:35:30
|
||||||
|
|
||||||
|
── 링 프리팹: Simulate 2 s 후 실제 시각 반경(XZ) ──────────────
|
||||||
|
Marker 3 Zone Loop
|
||||||
|
Simulate(2 s) 살아있는 파티클 3 · 시각 반경(XZ) 1.10 m · 높이 0.00~0.01 m · 렌더러 바운즈 반경 2.13 m
|
||||||
|
├ Marker 3 Zone Loop(Clone) : max 1000 · rate 0.0/s · 버스트 1종 총 1 · life 1.00 s · size 1.00 · shape off · scale (1.00, 1.00, 1.00) · 지금 1
|
||||||
|
├ Flash : max 1000 · rate 0.0/s · 버스트 1종 총 1 · life 1.00 s · size 2.00 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 1
|
||||||
|
├ Runes : max 1000 · rate 0.0/s · 버스트 1종 총 1 · life 1.00 s · size 2.20 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 1
|
||||||
|
├ ShockWave : max 1000 · rate 0.0/s · 버스트 1종 총 1 · life 0.30 s · size 3.00 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 0
|
||||||
|
Marker 5 Circle Loop
|
||||||
|
Simulate(2 s) 살아있는 파티클 8 · 시각 반경(XZ) 1.02 m · 높이 0.00~0.01 m · 렌더러 바운즈 반경 1.45 m
|
||||||
|
├ Marker 5 Circle Loop(Clone) : max 1000 · rate 0.0/s · 버스트 1종 총 2 · life 1.00 s · size 1.00 · shape off · scale (1.00, 1.00, 1.00) · 지금 2
|
||||||
|
├ Flash : max 1000 · rate 0.0/s · 버스트 1종 총 1 · life 2.00 s · size 2.00 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 2
|
||||||
|
├ Runes : max 1000 · rate 0.0/s · 버스트 1종 총 1 · life 2.00 s · size 2.04 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 2
|
||||||
|
├ ShockWave : max 1000 · rate 0.0/s · 버스트 1종 총 2 · life 1.00 s · size 2.00 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 2
|
||||||
|
Marker 7 Danger zone Loop
|
||||||
|
Simulate(2 s) 살아있는 파티클 4 · 시각 반경(XZ) 2.50 m · 높이 0.00~0.01 m · 렌더러 바운즈 반경 3.55 m
|
||||||
|
├ Marker 7 Danger zone Loop(Clone) : max 1000 · rate 0.0/s · 버스트 1종 총 2 · life 1.00 s · size 5.00 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 2
|
||||||
|
├ ShockWave : max 1000 · rate 0.0/s · 버스트 1종 총 2 · life 1.00 s · size 5.00 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 2
|
||||||
|
Marker 8 Safe zone Loop
|
||||||
|
Simulate(2 s) 살아있는 파티클 4 · 시각 반경(XZ) 2.50 m · 높이 0.00~0.01 m · 렌더러 바운즈 반경 3.55 m
|
||||||
|
├ Marker 8 Safe zone Loop(Clone) : max 1000 · rate 0.0/s · 버스트 1종 총 2 · life 1.00 s · size 5.00 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 2
|
||||||
|
├ ShockWave : max 1000 · rate 0.0/s · 버스트 1종 총 2 · life 1.00 s · size 5.00 · shape Hemisphere r0.00 · scale (1.00, 1.00, 1.00) · 지금 2
|
||||||
|
|
||||||
|
── 불꽃 후보: 파티클 시스템별 상세 ────────────────────────────
|
||||||
|
Fire_Particles · PS 3
|
||||||
|
├ FireRed : rate 20.0/s × life 1.50 s = 30 (max 1000) · size 0.50 · loop Y · 정지공간 Local
|
||||||
|
├ FireYellow : rate 15.0/s × life 1.00 s = 15 (max 1000) · size 0.50 · loop Y · 정지공간 Local
|
||||||
|
├ Smoke : rate 10.0/s × life 8.00 s = 80 (max 1000) · size 1.00 · loop Y · 정지공간 Local
|
||||||
|
⇒ 인스턴스 1개 정상상태 추정 125 파티클
|
||||||
|
Fire buff · PS 19
|
||||||
|
├ Fire buff(Clone) : rate 0.0/s × life 2.00 s = 0 (max 1000) · size 5.00 · loop Y · 정지공간 Local
|
||||||
|
├ SparksStart : rate 0.0/s × life 0.15 s = 0 (max 1000) · size 0.10 · loop Y · 정지공간 Local
|
||||||
|
├ SparksExpl : rate 0.0/s × life 0.50 s = 0 (max 1000) · size 0.30 · loop Y · 정지공간 Local
|
||||||
|
├ Sparks : rate 0.0/s × life 0.50 s = 0 (max 1000) · size 0.20 · loop Y · 정지공간 Local
|
||||||
|
├ SparksEnd : rate 0.0/s × life 0.40 s = 0 (max 1000) · size 0.20 · loop Y · 정지공간 Local
|
||||||
|
├ StartRays : rate 0.0/s × life 0.20 s = 0 (max 1000) · size 0.40 · loop Y · 정지공간 Local
|
||||||
|
├ StartFlash : rate 0.0/s × life 0.15 s = 0 (max 1000) · size 8.00 · loop Y · 정지공간 Local
|
||||||
|
├ StartShockWave : rate 0.0/s × life 0.15 s = 0 (max 1000) · size 8.00 · loop Y · 정지공간 Local
|
||||||
|
├ ShockWave : rate 0.0/s × life 0.14 s = 0 (max 1000) · size 14.00 · loop Y · 정지공간 Local
|
||||||
|
├ ShockWaves : rate 0.0/s × life 0.35 s = 0 (max 1000) · size 14.00 · loop Y · 정지공간 Local
|
||||||
|
├ GroundWaves : rate 0.0/s × life 0.50 s = 0 (max 1000) · size 4.00 · loop Y · 정지공간 Local
|
||||||
|
├ GroundWaves2 : rate 0.0/s × life 0.60 s = 0 (max 1000) · size 3.50 · loop Y · 정지공간 Local
|
||||||
|
├ MainTrails : rate 0.0/s × life 1.90 s = 0 (max 1000) · size 1.00 · loop Y · 정지공간 Local
|
||||||
|
├ Ray : rate 0.0/s × life 2.00 s = 0 (max 1000) · size 1.00 · loop Y · 정지공간 Local
|
||||||
|
├ Twist : rate 0.0/s × life 2.00 s = 0 (max 1000) · size 0.65 · loop Y · 정지공간 Local
|
||||||
|
├ FireUp : rate 0.0/s × life 0.50 s = 0 (max 1000) · size 2.00 · loop Y · 정지공간 Local
|
||||||
|
├ FireCore : rate 0.0/s × life 2.20 s = 0 (max 1000) · size 1.40 · loop Y · 정지공간 Local
|
||||||
|
├ EndSmoke : rate 0.0/s × life 0.50 s = 0 (max 1000) · size 1.50 · loop Y · 정지공간 Local
|
||||||
|
├ Light : rate 0.0/s × life 2.00 s = 0 (max 1000) · size 1.00 · loop Y · 정지공간 Local
|
||||||
|
⇒ 인스턴스 1개 정상상태 추정 0 파티클
|
||||||
|
FX_Flameflow_floor · PS 14
|
||||||
|
├ FX_Flameflow_floor(Clone) : rate 10.0/s × life 5.00 s = 50 (max 1000) · size 1.00 · loop Y · 정지공간 Local
|
||||||
|
├ Pos : rate 0.0/s × life 0.05 s = 0 (max 1000) · size 0.37 · loop N · 정지공간 Local
|
||||||
|
├ glow_b : rate 10.0/s × life 5.90 s = 59 (max 1000) · size 26.00 · loop N · 정지공간 Local
|
||||||
|
├ fire : rate 0.0/s × life 5.80 s = 0 (max 1000) · size 9.00 · loop N · 정지공간 Local
|
||||||
|
├ glow : rate 0.0/s × life 5.90 s = 0 (max 1000) · size 18.00 · loop N · 정지공간 Local
|
||||||
|
├ Rune : rate 100.0/s × life 5.80 s = 1 (max 1) · size 23.00 · loop N · 정지공간 Local
|
||||||
|
├ Dust : rate 15.0/s × life 2.30 s = 35 (max 1000) · size 5.00 · loop N · 정지공간 World
|
||||||
|
├ bao : rate 100.0/s × life 0.30 s = 2 (max 2) · size 22.00 · loop N · 정지공간 Local
|
||||||
|
├ bao1 : rate 0.0/s × life 0.60 s = 0 (max 1000) · size 13.00 · loop N · 정지공간 Local
|
||||||
|
├ bao2 : rate 10.0/s × life 0.50 s = 1 (max 1) · size 7.00 · loop N · 정지공간 Local
|
||||||
|
├ Gradient_ap : rate 0.0/s × life 0.60 s = 0 (max 1000) · size 27.00 · loop N · 정지공간 Local
|
||||||
|
├ startwave_R : rate 0.0/s × life 5.80 s = 0 (max 1000) · size 12.00 · loop N · 정지공간 Local
|
||||||
|
├ startwave_Y : rate 0.0/s × life 5.80 s = 0 (max 1000) · size 12.00 · loop N · 정지공간 Local
|
||||||
|
├ dust_ring : rate 120.0/s × life 2.30 s = 276 (max 1000) · size 4.00 · loop N · 정지공간 World
|
||||||
|
⇒ 인스턴스 1개 정상상태 추정 424 파티클
|
||||||
|
|
||||||
|
── LMHPOLY 바위(비 LOD · Rock 계열) ───────────────────────────
|
||||||
|
Rock_Block_crk_m_01 · Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_crk_m_01.prefab
|
||||||
|
크기 (1.49, 0.74, 1.47) · tri 150 · 렌더러 1
|
||||||
|
Rock_Block_crk_m_02 · Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_crk_m_02.prefab
|
||||||
|
크기 (1.33, 0.86, 1.40) · tri 134 · 렌더러 1
|
||||||
|
Rock_Block_crk_m_03 · Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_crk_m_03.prefab
|
||||||
|
크기 (1.94, 0.78, 0.92) · tri 62 · 렌더러 1
|
||||||
|
Rock_Block_crk_m_04 · Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_crk_m_04.prefab
|
||||||
|
크기 (1.26, 0.70, 1.31) · tri 152 · 렌더러 1
|
||||||
|
Rock_Block_crk_m_05 · Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_crk_m_05.prefab
|
||||||
|
크기 (1.48, 0.70, 0.79) · tri 42 · 렌더러 1
|
||||||
|
Rock_Block_m_01 · Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_m_01.prefab
|
||||||
|
크기 (1.07, 0.58, 0.58) · tri 12 · 렌더러 1
|
||||||
|
Rock_Block_m_02 · Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_m_02.prefab
|
||||||
|
크기 (1.13, 0.63, 0.63) · tri 44 · 렌더러 1
|
||||||
|
Rock_Block_m_03 · Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Rock Assets/Prefabs/With_Box_Colliders/1 Color/Block_Rocks/Rock_Block_m_03.prefab
|
||||||
|
크기 (1.20, 0.60, 0.64) · tri 44 · 렌더러 1
|
||||||
|
|
||||||
|
── 울타리 프롭 pivot·회전 확인(둘레 배치 적합성) ───────────────
|
||||||
|
Fence_01 : pivot→바운즈중심 (0.00, 0.50, 0.00) · 크기 (2.38, 1.02, 0.27) · 긴축 X 2.38 · 바닥 y -0.01
|
||||||
|
FenceBig_01 : pivot→바운즈중심 (0.01, 1.70, -0.01) · 크기 (2.26, 3.41, 0.32) · 긴축 X 2.26 · 바닥 y -0.01
|
||||||
|
Stone_1 : pivot→바운즈중심 (0.00, 0.00, 0.12) · 크기 (1.29, 1.62, 1.50) · 긴축 Z 1.50 · 바닥 y -0.81
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
// WL-815c 보유 에셋 실측 — 장벽 「보이기」 후보 (임시 · 커밋 대상)
|
||||||
|
// unity command run_script --file AgentScripts/WL815c_Survey.cs --entry WL815c_Survey.Run --project-path E:\NerdNavis\WL_wt\gameplay2
|
||||||
|
|
||||||
|
public static class WL815c_Survey
|
||||||
|
{
|
||||||
|
static System.Text.StringBuilder s_sb;
|
||||||
|
|
||||||
|
static void L(string s) { s_sb.AppendLine(s); UnityEngine.Debug.Log(s); }
|
||||||
|
|
||||||
|
public static string Run()
|
||||||
|
{
|
||||||
|
s_sb = new System.Text.StringBuilder();
|
||||||
|
L("WL-815c 장벽 보이기 후보 실측 · " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── ⓐ 링 후보 (Hovl) ─────────────────────────────────────────────");
|
||||||
|
string[] rings = {
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 3 Zone Loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 5 Circle Loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 7 Danger zone Loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 8 Safe zone Loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 4 Pillar Loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Magic circles/Prefabs/Loop version/Magic circle 1 loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Magic circles/Prefabs/Loop version/Magic circle 3 loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Magic circles/Prefabs/Loop version/Magic circle 5 loop.prefab",
|
||||||
|
};
|
||||||
|
for (int i = 0; i < rings.Length; i++) Measure(rings[i]);
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── ⓑ 프롭 후보 (울타리 · 바위 · 기둥) ───────────────────────────");
|
||||||
|
string[] props = {
|
||||||
|
"Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/Fence_01.prefab",
|
||||||
|
"Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/Fence_02.prefab",
|
||||||
|
"Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/FenceBig_01.prefab",
|
||||||
|
"Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/FenceBig_02.prefab",
|
||||||
|
"Assets/LowPolyFantasyArena/Prefabs/FenceBig_01.prefab",
|
||||||
|
"Assets/LowPolyFantasyArena/Prefabs/FenceBig_02.prefab",
|
||||||
|
"Assets/ResWork/Aligned Games/Polygonal Woodlands Nature Asset Package/Models/Fence1/fence_1.prefab",
|
||||||
|
"Assets/ResWork/Aligned Games/Polygonal Woodlands Nature Asset Package/Models/Fence2/fence_2.prefab",
|
||||||
|
"Assets/3DPixelArtEnvironment/Prefabs/Stone_1.prefab",
|
||||||
|
"Assets/3DPixelArtEnvironment/Prefabs/Stone_2.prefab",
|
||||||
|
"Assets/3DPixelArtEnvironment/Prefabs/Slab.prefab",
|
||||||
|
"Assets/3DPixelArtEnvironment/Prefabs/Arch_Small.prefab",
|
||||||
|
"Assets/3DPixelArtEnvironment/Prefabs/Light_Post.prefab",
|
||||||
|
};
|
||||||
|
for (int i = 0; i < props.Length; i++) Measure(props[i]);
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── LMHPOLY 바위 (자연 번들) ────────────────────────────────────");
|
||||||
|
var rockGuids = UnityEditor.AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/LMHPOLY/Low Poly Nature Bundle/Rocks" });
|
||||||
|
int shown = 0;
|
||||||
|
for (int i = 0; i < rockGuids.Length && shown < 10; i++)
|
||||||
|
{
|
||||||
|
string p = UnityEditor.AssetDatabase.GUIDToAssetPath(rockGuids[i]);
|
||||||
|
if (p.Contains("/LOD/") || p.Contains("Fireplace") || p.Contains("ParticleSystem")) continue;
|
||||||
|
if (!p.Contains("Rock")) continue;
|
||||||
|
Measure(p); shown++;
|
||||||
|
}
|
||||||
|
L(" (Rocks 폴더 프리팹 총 " + rockGuids.Length + "개)");
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── ⓒ 불꽃 후보 ────────────────────────────────────────────────");
|
||||||
|
string[] fires = {
|
||||||
|
"Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/ParticleSystem/Fire_Particles.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Epic Toon VFX 2/Prefabs/Fire buff/Fire buff.prefab",
|
||||||
|
"Assets/ResWork/ModularDungeon/Prefabs/SK_Flame.prefab",
|
||||||
|
"Assets/ResWork/EricWang/GameVFX Collection Vol3/Prefabs/FX_Flameflow_floor.prefab",
|
||||||
|
"Assets/ResWork/VFX/StylizedFantasyFxPack/Prefab/PS_Cast_Fire.prefab",
|
||||||
|
"Assets/Res_Addr/Effect/Effect_Hit_Mob_HellfireFlames.prefab",
|
||||||
|
};
|
||||||
|
for (int i = 0; i < fires.Length; i++) Measure(fires[i]);
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── 불꽃 추가 탐색(이름에 Fire/Flame · 파티클 보유 · 루프) ───────");
|
||||||
|
var guids = UnityEditor.AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/ResWork/VFX" });
|
||||||
|
int hits = 0;
|
||||||
|
for (int i = 0; i < guids.Length && hits < 14; i++)
|
||||||
|
{
|
||||||
|
string p = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
|
||||||
|
string n = System.IO.Path.GetFileNameWithoutExtension(p).ToLowerInvariant();
|
||||||
|
if (!(n.Contains("fire") || n.Contains("flame") || n.Contains("torch") || n.Contains("burn"))) continue;
|
||||||
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(p);
|
||||||
|
if (go == null) continue;
|
||||||
|
var pss = go.GetComponentsInChildren<UnityEngine.ParticleSystem>(true);
|
||||||
|
if (pss.Length == 0) continue;
|
||||||
|
bool loop = false;
|
||||||
|
for (int k = 0; k < pss.Length; k++) if (pss[k].main.loop) loop = true;
|
||||||
|
if (!loop) continue;
|
||||||
|
Measure(p); hits++;
|
||||||
|
}
|
||||||
|
|
||||||
|
string outFile = "AgentScripts/WL815c_SURVEY.txt";
|
||||||
|
System.IO.File.WriteAllText(outFile, s_sb.ToString());
|
||||||
|
return "survey done → " + outFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Measure(string path)
|
||||||
|
{
|
||||||
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(path);
|
||||||
|
if (go == null) { L(" [없음] " + path); return; }
|
||||||
|
|
||||||
|
var inst = UnityEngine.Object.Instantiate(go);
|
||||||
|
inst.transform.position = UnityEngine.Vector3.zero;
|
||||||
|
inst.transform.rotation = UnityEngine.Quaternion.identity;
|
||||||
|
inst.transform.localScale = UnityEngine.Vector3.one;
|
||||||
|
|
||||||
|
var rends = inst.GetComponentsInChildren<UnityEngine.Renderer>(true);
|
||||||
|
bool has = false;
|
||||||
|
UnityEngine.Bounds b = new UnityEngine.Bounds(UnityEngine.Vector3.zero, UnityEngine.Vector3.zero);
|
||||||
|
int meshRend = 0, partRend = 0;
|
||||||
|
long tris = 0;
|
||||||
|
for (int i = 0; i < rends.Length; i++)
|
||||||
|
{
|
||||||
|
var mf = rends[i].GetComponent<UnityEngine.MeshFilter>();
|
||||||
|
if (mf != null && mf.sharedMesh != null)
|
||||||
|
{
|
||||||
|
meshRend++;
|
||||||
|
tris += mf.sharedMesh.triangles.Length / 3;
|
||||||
|
if (!has) { b = rends[i].bounds; has = true; } else b.Encapsulate(rends[i].bounds);
|
||||||
|
}
|
||||||
|
else if (rends[i] is UnityEngine.ParticleSystemRenderer) partRend++;
|
||||||
|
else if (!has) { b = rends[i].bounds; has = true; } else b.Encapsulate(rends[i].bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pss = inst.GetComponentsInChildren<UnityEngine.ParticleSystem>(true);
|
||||||
|
int maxParticles = 0; float rate = 0f, shapeR = 0f, dur = 0f; bool loop = false;
|
||||||
|
for (int i = 0; i < pss.Length; i++)
|
||||||
|
{
|
||||||
|
var m = pss[i].main;
|
||||||
|
maxParticles += m.maxParticles;
|
||||||
|
dur = UnityEngine.Mathf.Max(dur, m.duration);
|
||||||
|
if (m.loop) loop = true;
|
||||||
|
var e = pss[i].emission;
|
||||||
|
if (e.enabled) rate += e.rateOverTime.constantMax;
|
||||||
|
var sh = pss[i].shape;
|
||||||
|
if (sh.enabled) shapeR = UnityEngine.Mathf.Max(shapeR, sh.radius * pss[i].transform.lossyScale.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 실 파티클 수 추정 = maxParticles 상한, 실효 = rate × lifetime
|
||||||
|
float lifeSum = 0f;
|
||||||
|
for (int i = 0; i < pss.Length; i++) lifeSum = UnityEngine.Mathf.Max(lifeSum, pss[i].main.startLifetime.constantMax);
|
||||||
|
|
||||||
|
int mats = 0;
|
||||||
|
var seen = new System.Collections.Generic.HashSet<int>();
|
||||||
|
for (int i = 0; i < rends.Length; i++)
|
||||||
|
{
|
||||||
|
var ms = rends[i].sharedMaterials;
|
||||||
|
for (int k = 0; k < ms.Length; k++) if (ms[k] != null && seen.Add(ms[k].GetInstanceID())) mats++;
|
||||||
|
}
|
||||||
|
|
||||||
|
L(" " + System.IO.Path.GetFileNameWithoutExtension(path));
|
||||||
|
L(" 경로 " + path);
|
||||||
|
L(" 렌더러 " + rends.Length + "(메시 " + meshRend + " · 파티클 " + partRend + ") · 머티리얼 " + mats + " · 삼각형 " + tris);
|
||||||
|
if (has) L(" 바운즈 크기 " + b.size.ToString("F2") + " · 중심 " + b.center.ToString("F2") + " · 최대반경(XZ) " +
|
||||||
|
(UnityEngine.Mathf.Max(b.extents.x, b.extents.z)).ToString("F2") + " m");
|
||||||
|
else L(" 바운즈: 없음(메시 0)");
|
||||||
|
if (pss.Length > 0)
|
||||||
|
L(" 파티클시스템 " + pss.Length + " · maxParticles 합 " + maxParticles + " · rateOverTime 합 " + rate.ToString("F1") +
|
||||||
|
"/s · 최대수명 " + lifeSum.ToString("F2") + " s · 정상상태 추정 " + (rate * lifeSum).ToString("F0") +
|
||||||
|
" · duration " + dur.ToString("F2") + " · loop " + (loop ? "Y" : "N") + " · shape radius " + shapeR.ToString("F2"));
|
||||||
|
|
||||||
|
UnityEngine.Object.DestroyImmediate(inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
// WL-815c 2차 실측 — 링 실제 시각 반경(Simulate 후 바운즈) · 불꽃 파티클 시스템별 상세 · 바위 프롭
|
||||||
|
// unity command run_script --file AgentScripts/WL815c_Survey2.cs --entry WL815c_Survey2.Run
|
||||||
|
|
||||||
|
public static class WL815c_Survey2
|
||||||
|
{
|
||||||
|
static System.Text.StringBuilder s_sb;
|
||||||
|
static void L(string s) { s_sb.AppendLine(s); UnityEngine.Debug.Log(s); }
|
||||||
|
|
||||||
|
public static string Run()
|
||||||
|
{
|
||||||
|
s_sb = new System.Text.StringBuilder();
|
||||||
|
L("WL-815c 2차 실측 · " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── 링 프리팹: Simulate 2 s 후 실제 시각 반경(XZ) ──────────────");
|
||||||
|
string[] rings = {
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 3 Zone Loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 5 Circle Loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 7 Danger zone Loop.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Map track markers VFX/Prefabs/Marker 8 Safe zone Loop.prefab",
|
||||||
|
};
|
||||||
|
for (int i = 0; i < rings.Length; i++) SimRadius(rings[i]);
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── 불꽃 후보: 파티클 시스템별 상세 ────────────────────────────");
|
||||||
|
string[] fires = {
|
||||||
|
"Assets/LMHPOLY/Low Poly Nature Bundle/Rocks/Bonus Assets/Prefabs/ParticleSystem/Fire_Particles.prefab",
|
||||||
|
"Assets/ResWork/VFX/Hovl Studio/Epic Toon VFX 2/Prefabs/Fire buff/Fire buff.prefab",
|
||||||
|
"Assets/ResWork/EricWang/GameVFX Collection Vol3/Prefabs/FX_Flameflow_floor.prefab",
|
||||||
|
};
|
||||||
|
for (int i = 0; i < fires.Length; i++) Detail(fires[i]);
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── LMHPOLY 바위(비 LOD · Rock 계열) ───────────────────────────");
|
||||||
|
var guids = UnityEditor.AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/LMHPOLY/Low Poly Nature Bundle/Rocks" });
|
||||||
|
int shown = 0;
|
||||||
|
for (int i = 0; i < guids.Length && shown < 8; i++)
|
||||||
|
{
|
||||||
|
string p = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
|
||||||
|
string n = System.IO.Path.GetFileNameWithoutExtension(p);
|
||||||
|
if (p.Contains("/LOD/") || n.EndsWith("_LOD")) continue;
|
||||||
|
if (!(n.StartsWith("Rock") || n.StartsWith("Stone") || n.StartsWith("Boulder"))) continue;
|
||||||
|
Brief(p); shown++;
|
||||||
|
}
|
||||||
|
if (shown == 0)
|
||||||
|
{
|
||||||
|
L(" Rock/Stone/Boulder 접두 프리팹 0 — 폴더 구조 표본:");
|
||||||
|
for (int i = 0; i < guids.Length && i < 12; i++)
|
||||||
|
L(" " + UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
L("");
|
||||||
|
L("── 울타리 프롭 pivot·회전 확인(둘레 배치 적합성) ───────────────");
|
||||||
|
string[] fences = {
|
||||||
|
"Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/Fence_01.prefab",
|
||||||
|
"Assets/ResWork/LowPolyFantasyVillage_URP/Prefabs/Props/FenceBig_01.prefab",
|
||||||
|
"Assets/3DPixelArtEnvironment/Prefabs/Stone_1.prefab",
|
||||||
|
};
|
||||||
|
for (int i = 0; i < fences.Length; i++) Pivot(fences[i]);
|
||||||
|
|
||||||
|
string outFile = "AgentScripts/WL815c_SURVEY2.txt";
|
||||||
|
System.IO.File.WriteAllText(outFile, s_sb.ToString());
|
||||||
|
return "survey2 done → " + outFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SimRadius(string path)
|
||||||
|
{
|
||||||
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(path);
|
||||||
|
if (go == null) { L(" [없음] " + path); return; }
|
||||||
|
var inst = UnityEngine.Object.Instantiate(go);
|
||||||
|
inst.transform.position = UnityEngine.Vector3.zero;
|
||||||
|
|
||||||
|
var pss = inst.GetComponentsInChildren<UnityEngine.ParticleSystem>(true);
|
||||||
|
for (int i = 0; i < pss.Length; i++) { pss[i].Clear(true); pss[i].Simulate(2.0f, true, true, false); }
|
||||||
|
|
||||||
|
float maxR = 0f, maxY = 0f, minY = 0f;
|
||||||
|
int alive = 0;
|
||||||
|
for (int i = 0; i < pss.Length; i++)
|
||||||
|
{
|
||||||
|
int c = pss[i].particleCount;
|
||||||
|
alive += c;
|
||||||
|
if (c == 0) continue;
|
||||||
|
var buf = new UnityEngine.ParticleSystem.Particle[c];
|
||||||
|
int got = pss[i].GetParticles(buf);
|
||||||
|
bool world = pss[i].main.simulationSpace == UnityEngine.ParticleSystemSimulationSpace.World;
|
||||||
|
for (int k = 0; k < got; k++)
|
||||||
|
{
|
||||||
|
var p = buf[k].position;
|
||||||
|
if (!world) p = pss[i].transform.TransformPoint(p);
|
||||||
|
float r = new UnityEngine.Vector2(p.x, p.z).magnitude;
|
||||||
|
float sz = buf[k].GetCurrentSize(pss[i]) * 0.5f;
|
||||||
|
if (r + sz > maxR) maxR = r + sz;
|
||||||
|
if (p.y > maxY) maxY = p.y;
|
||||||
|
if (p.y < minY) minY = p.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 파티클이 0이면 메시/빌보드 크기로 대신 잰다
|
||||||
|
float rendR = 0f;
|
||||||
|
var rends = inst.GetComponentsInChildren<UnityEngine.Renderer>(true);
|
||||||
|
for (int i = 0; i < rends.Length; i++)
|
||||||
|
{
|
||||||
|
var b = rends[i].bounds;
|
||||||
|
rendR = UnityEngine.Mathf.Max(rendR, UnityEngine.Mathf.Max(b.extents.x, b.extents.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
L(" " + System.IO.Path.GetFileNameWithoutExtension(path));
|
||||||
|
L(" Simulate(2 s) 살아있는 파티클 " + alive + " · 시각 반경(XZ) " + maxR.ToString("F2") +
|
||||||
|
" m · 높이 " + minY.ToString("F2") + "~" + maxY.ToString("F2") + " m · 렌더러 바운즈 반경 " + rendR.ToString("F2") + " m");
|
||||||
|
for (int i = 0; i < pss.Length; i++)
|
||||||
|
{
|
||||||
|
var m = pss[i].main;
|
||||||
|
var sh = pss[i].shape;
|
||||||
|
var e = pss[i].emission;
|
||||||
|
string bursts = "";
|
||||||
|
if (e.burstCount > 0)
|
||||||
|
{
|
||||||
|
var arr = new UnityEngine.ParticleSystem.Burst[e.burstCount];
|
||||||
|
e.GetBursts(arr);
|
||||||
|
float tot = 0f; for (int k = 0; k < arr.Length; k++) tot += arr[k].count.constantMax * UnityEngine.Mathf.Max(1, arr[k].cycleCount);
|
||||||
|
bursts = " · 버스트 " + e.burstCount + "종 총 " + tot.ToString("F0");
|
||||||
|
}
|
||||||
|
L(" ├ " + pss[i].name + " : max " + m.maxParticles + " · rate " + (e.enabled ? e.rateOverTime.constantMax : 0f).ToString("F1") +
|
||||||
|
"/s" + bursts + " · life " + m.startLifetime.constantMax.ToString("F2") + " s · size " + m.startSize.constantMax.ToString("F2") +
|
||||||
|
" · shape " + (sh.enabled ? sh.shapeType.ToString() + " r" + sh.radius.ToString("F2") : "off") +
|
||||||
|
" · scale " + pss[i].transform.lossyScale.ToString("F2") + " · 지금 " + pss[i].particleCount);
|
||||||
|
}
|
||||||
|
UnityEngine.Object.DestroyImmediate(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Detail(string path)
|
||||||
|
{
|
||||||
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(path);
|
||||||
|
if (go == null) { L(" [없음] " + path); return; }
|
||||||
|
var inst = UnityEngine.Object.Instantiate(go);
|
||||||
|
var pss = inst.GetComponentsInChildren<UnityEngine.ParticleSystem>(true);
|
||||||
|
float steady = 0f;
|
||||||
|
L(" " + System.IO.Path.GetFileNameWithoutExtension(path) + " · PS " + pss.Length);
|
||||||
|
for (int i = 0; i < pss.Length; i++)
|
||||||
|
{
|
||||||
|
var m = pss[i].main; var e = pss[i].emission;
|
||||||
|
float r = e.enabled ? e.rateOverTime.constantMax : 0f;
|
||||||
|
float life = m.startLifetime.constantMax;
|
||||||
|
float st = UnityEngine.Mathf.Min(r * life, m.maxParticles);
|
||||||
|
steady += st;
|
||||||
|
L(" ├ " + pss[i].name + " : rate " + r.ToString("F1") + "/s × life " + life.ToString("F2") + " s = " + st.ToString("F0") +
|
||||||
|
" (max " + m.maxParticles + ") · size " + m.startSize.constantMax.ToString("F2") + " · loop " + (m.loop ? "Y" : "N") +
|
||||||
|
" · 정지공간 " + m.simulationSpace);
|
||||||
|
}
|
||||||
|
L(" ⇒ 인스턴스 1개 정상상태 추정 " + steady.ToString("F0") + " 파티클");
|
||||||
|
UnityEngine.Object.DestroyImmediate(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Brief(string path)
|
||||||
|
{
|
||||||
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(path);
|
||||||
|
if (go == null) return;
|
||||||
|
var inst = UnityEngine.Object.Instantiate(go);
|
||||||
|
var rends = inst.GetComponentsInChildren<UnityEngine.Renderer>(true);
|
||||||
|
long tris = 0; bool has = false; UnityEngine.Bounds b = default(UnityEngine.Bounds);
|
||||||
|
for (int i = 0; i < rends.Length; i++)
|
||||||
|
{
|
||||||
|
var mf = rends[i].GetComponent<UnityEngine.MeshFilter>();
|
||||||
|
if (mf != null && mf.sharedMesh != null) tris += mf.sharedMesh.triangles.Length / 3;
|
||||||
|
if (!has) { b = rends[i].bounds; has = true; } else b.Encapsulate(rends[i].bounds);
|
||||||
|
}
|
||||||
|
L(" " + System.IO.Path.GetFileNameWithoutExtension(path) + " · " + path);
|
||||||
|
L(" 크기 " + b.size.ToString("F2") + " · tri " + tris + " · 렌더러 " + rends.Length);
|
||||||
|
UnityEngine.Object.DestroyImmediate(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Pivot(string path)
|
||||||
|
{
|
||||||
|
var go = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(path);
|
||||||
|
if (go == null) return;
|
||||||
|
var inst = UnityEngine.Object.Instantiate(go);
|
||||||
|
inst.transform.position = UnityEngine.Vector3.zero;
|
||||||
|
inst.transform.rotation = UnityEngine.Quaternion.identity;
|
||||||
|
var rends = inst.GetComponentsInChildren<UnityEngine.Renderer>(true);
|
||||||
|
bool has = false; UnityEngine.Bounds b = default(UnityEngine.Bounds);
|
||||||
|
for (int i = 0; i < rends.Length; i++) { if (!has) { b = rends[i].bounds; has = true; } else b.Encapsulate(rends[i].bounds); }
|
||||||
|
L(" " + System.IO.Path.GetFileNameWithoutExtension(path) + " : pivot→바운즈중심 " + b.center.ToString("F2") +
|
||||||
|
" · 크기 " + b.size.ToString("F2") + " · 긴축 " + (b.size.x >= b.size.z ? "X " + b.size.x.ToString("F2") : "Z " + b.size.z.ToString("F2")) +
|
||||||
|
" · 바닥 y " + (b.center.y - b.extents.y).ToString("F2"));
|
||||||
|
UnityEngine.Object.DestroyImmediate(inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 58ad4c9f64381564b887b17b3ca5b314
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b64e1a9aad572f04a8a23c913bd5dfc7
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b4d10d4d325b7ac41a2aa48028290759
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 327b74d22ec69414ca28e7b3fd4c8d1a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 3c3dc9bd8de148744b88ce512ce0f09a, type: 3}
|
||||||
|
m_Name: WLStageBarrierSettings
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::WL.Combat.Stage.WLStageBarrierSettings
|
||||||
|
enabled: 1
|
||||||
|
verboseLog: 0
|
||||||
|
checkSeconds: 0.2
|
||||||
|
rescanSeconds: 3
|
||||||
|
blockEnabled: 1
|
||||||
|
leaveMargin: 2
|
||||||
|
graceSeconds: 0.5
|
||||||
|
warpCooldownSeconds: 1
|
||||||
|
warpInsetMeters: 1.5
|
||||||
|
warpNavSampleRadius: 4
|
||||||
|
warpNavSampleRadiusFar: 20
|
||||||
|
blockPlayer: 1
|
||||||
|
blockMobs: 1
|
||||||
|
mobLeaveMargin: 3
|
||||||
|
mobGraceSeconds: 1
|
||||||
|
mobWarpCooldownSeconds: 2
|
||||||
|
warpSnapsCameraLead: 1
|
||||||
|
warpResetsPath: 1
|
||||||
|
carveNavMesh: 0
|
||||||
|
carveSegmentArcMeters: 4
|
||||||
|
maxCarveSegments: 32
|
||||||
|
carveThickness: 1.5
|
||||||
|
carveHeight: 3
|
||||||
|
carveOverlap: 0.15
|
||||||
|
carveStaticOnly: 1
|
||||||
|
style: 1
|
||||||
|
showVisual: 1
|
||||||
|
appearSeconds: 0.6
|
||||||
|
fadeSeconds: 0.6
|
||||||
|
visualYOffset: 0.05
|
||||||
|
visualRadiusScale: 1
|
||||||
|
ringPrefab: {fileID: 1370640339480484, guid: a1b9488b1b1338d4585b154f4b86703e, type: 3}
|
||||||
|
ringPrefabVisualRadius: 1.1
|
||||||
|
ringColor: {r: 1, g: 0.35, b: 0.15, a: 1}
|
||||||
|
ringTintEnabled: 1
|
||||||
|
ringYScale: 1
|
||||||
|
ringExtraRings: 1
|
||||||
|
ringExtraSpacing: 0.6
|
||||||
|
propPrefabs:
|
||||||
|
- {fileID: 3287405711304420922, guid: 84026706d57fb9e43b9716c8b1510e98, type: 3}
|
||||||
|
- {fileID: 3287405711304420922, guid: 84026706d57fb9e43b9716c8b1510e98, type: 3}
|
||||||
|
- {fileID: 118804, guid: 3e0cf4dec1c9ddb4b9832582972317f6, type: 3}
|
||||||
|
propSpacing: 2.5
|
||||||
|
maxProps: 64
|
||||||
|
propScale: 1
|
||||||
|
propAlignToTangent: 1
|
||||||
|
propYawJitter: 6
|
||||||
|
propRadialJitter: 0.2
|
||||||
|
propGroundSnap: 1
|
||||||
|
propRayUp: 50
|
||||||
|
propRayLength: 200
|
||||||
|
propStripColliders: 1
|
||||||
|
propSeed: 815
|
||||||
|
firePrefabs:
|
||||||
|
- {fileID: 131402, guid: 5608cd25f71830e4aaa770d88ff9ebe2, type: 3}
|
||||||
|
fireSpacing: 3
|
||||||
|
maxFires: 48
|
||||||
|
fireScale: 1
|
||||||
|
maxTotalParticles: 1200
|
||||||
|
fireBudgetMode: 0
|
||||||
|
fireTintEnabled: 0
|
||||||
|
fireColor: {r: 1, g: 0.45, b: 0.1, a: 1}
|
||||||
|
fireGroundSnap: 1
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 25f5d7207dc33a54e8a14b8628b6eae6
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,482 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// StageBarrier.cs — 스테이지 「고정 영역(장벽)」 · 정적 API
|
||||||
|
//
|
||||||
|
// PD 지시 #815 「스테이지에 들어가면 고정 영역 안에서만 싸운다」
|
||||||
|
// + PD 질문(2026-09-11) 「구역을 어떻게 막는가 — 울타리 오브젝트 · 불꽃 이펙트 등 대안」
|
||||||
|
// 발주서 WL-815c · SOT = 기준서 「스테이지_전환_기준서_v1.md」 §D
|
||||||
|
//
|
||||||
|
// ■ 공개 계약(815b 의 StageBarrierHook 이 이것을 부른다 — Lead 가 병합 뒤 1줄 연결)
|
||||||
|
// StageBarrier.Set(center, radius) 스테이지 입장 — 장벽을 세운다
|
||||||
|
// StageBarrier.Clear() 스테이지 클리어 — 장벽을 연다
|
||||||
|
// StageBarrier.IsInside(pos) 이 좌표가 장벽 안인가
|
||||||
|
// StageBarrier.Active 장벽이 서 있는가
|
||||||
|
// 🔴 이 파일은 StageBarrierHook 을 정의하지 않는다(815b 소유 · 이름 충돌 방지).
|
||||||
|
//
|
||||||
|
// ■ 「어떻게 막는가」 = 기준서 §D-2 실측 결론 ③ 거리 판정 + 워프백
|
||||||
|
// ① 물리 콜라이더 벽은 **불가** — PC 프리팹 Ai01 의 CapsuleCollider 가 IsTrigger 이고
|
||||||
|
// Rigidbody Constraints = 126(위치·회전 전부 고정)이라 물리로 밀 수 없다.
|
||||||
|
// 몹도 MobActor.Start 가 isKinematic 을 강제한다.
|
||||||
|
// ② NavMesh 카브는 되지만(813t2 가 NavMeshModifierVolume 로 실증) 런타임 재베이크 비용이 있어 **옵션**.
|
||||||
|
// ③ 거리 + 워프백은 **이미 돌고 있는 방식**이다 — BossArena.EvaluateLeash(arenaRadius+margin, 유예)와
|
||||||
|
// 813w3 RunGuide.WarpToArena, 813t2 TerrainGuard 가 같은 원본 API 를 쓴다.
|
||||||
|
//
|
||||||
|
// ■ 되돌리는 방법 = 원본 `Actor.Set_Warp`(Actor.cs:2111) — 813i DeathFlow · 813w3 RunGuide ·
|
||||||
|
// 813t2 TerrainGuard · 813x MobNavRecovery 와 같은 API. 이펙트·잔상 0.
|
||||||
|
// PC 는 Get_NavSnapRadius()=0 이라 Set_Warp 이 스스로 스냅하지 않는다
|
||||||
|
// → **반드시 먼저 NavMesh.SamplePosition 으로 스냅한 지점**을 넘긴다(지형 뚫기 방지 · 813w3 규칙).
|
||||||
|
// 되돌린 뒤 PC 는 카메라 이동 리드를 0 으로 스냅한다(813t2 방식 · RealCamera 무수정).
|
||||||
|
//
|
||||||
|
// ■ 원본 훅 0 — 폴링만 쓴다(813x MobNavRecovery · 813t2 TerrainGuard 선례).
|
||||||
|
// CombatEvents 를 쓰지 않는 이유: RaiseSpawned 첫 줄이 `if (!Enabled …) return;` 이라
|
||||||
|
// 코어 스위치가 꺼지면 이벤트가 0 이다(813t2 실측).
|
||||||
|
//
|
||||||
|
// ■ 그림(3스타일)은 StageBarrierVisual.cs 가 맡는다. 이 파일은 판정·워프·카브만 다룬다.
|
||||||
|
//
|
||||||
|
// ■ 값(C45) 전부 WLStageBarrierSettings. 게임 수치 상수 0.
|
||||||
|
// 🔴 Debug.LogError/LogException 금지(813z — ErrorLogHookManager 가 플레이어 모달을 띄운다).
|
||||||
|
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(Actor/MobActor 가 Assembly-CSharp 에 있다).
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.AI;
|
||||||
|
|
||||||
|
namespace WL.Combat.Stage
|
||||||
|
{
|
||||||
|
/// <summary>스테이지 고정 영역. 정적 · Set/Clear 로 세우고 연다.</summary>
|
||||||
|
public static class StageBarrier
|
||||||
|
{
|
||||||
|
static WLStageBarrierSettings Cfg { get { return WLStageBarrierSettings.Instance; } }
|
||||||
|
|
||||||
|
// ───────────────────────────────────────── 상태
|
||||||
|
static bool s_active;
|
||||||
|
static Vector3 s_center;
|
||||||
|
static float s_radius;
|
||||||
|
|
||||||
|
/// <summary>장벽이 서 있는가.</summary>
|
||||||
|
public static bool Active { get { return s_active; } }
|
||||||
|
|
||||||
|
/// <summary>장벽 중심(월드).</summary>
|
||||||
|
public static Vector3 Center { get { return s_center; } }
|
||||||
|
|
||||||
|
/// <summary>장벽 반경(m).</summary>
|
||||||
|
public static float Radius { get { return s_radius; } }
|
||||||
|
|
||||||
|
/// <summary>기능 축이 살아 있는가(에셋 · enabled · RuntimeDisabled).</summary>
|
||||||
|
public static bool FeatureEnabled { get { return WLStageBarrierSettings.Active; } }
|
||||||
|
|
||||||
|
// ───────────────────────────────────────── 진단 카운터(프로브·QA 가 읽는다)
|
||||||
|
public static int SetCount, ClearCount, Checks, Scans;
|
||||||
|
public static int PlayerWarps, MobWarps, WarpsFailedNavMesh, WarpsSkippedCooldown, OutsideSeen;
|
||||||
|
public static int CarveSegments;
|
||||||
|
public static float LastCarveBuildMs;
|
||||||
|
public static string LastLog = "";
|
||||||
|
public static Vector3 LastWarpFrom, LastWarpTo;
|
||||||
|
|
||||||
|
// ───────────────────────────────────────── 내부(GC 0 — 틱에서 새로 만들지 않는다)
|
||||||
|
static MobActor[] s_mobs = new MobActor[0];
|
||||||
|
static readonly Dictionary<int, float> s_outsideSince = new Dictionary<int, float>();
|
||||||
|
static readonly Dictionary<int, float> s_lastWarp = new Dictionary<int, float>();
|
||||||
|
static readonly List<GameObject> s_carve = new List<GameObject>();
|
||||||
|
static Transform s_carveRoot;
|
||||||
|
static float s_nextRescan, s_nextCheck;
|
||||||
|
static bool s_faulted;
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// 공개 API
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>스테이지 입장 — 중심·반경으로 장벽을 세운다. 같은 값으로 다시 불러도 안전(멱등).</summary>
|
||||||
|
public static void Set(Vector3 center, float radius)
|
||||||
|
{
|
||||||
|
var cfg = Cfg;
|
||||||
|
if (cfg == null || !cfg.enabled || WLStageBarrierSettings.RuntimeDisabled) return; // C8 — 오브젝트 0
|
||||||
|
if (radius <= 0f) { Clear(); return; }
|
||||||
|
|
||||||
|
bool moved = !s_active || (s_center - center).sqrMagnitude > 0.0001f || !Mathf.Approximately(s_radius, radius);
|
||||||
|
|
||||||
|
s_center = center;
|
||||||
|
s_radius = radius;
|
||||||
|
s_active = true;
|
||||||
|
SetCount++;
|
||||||
|
s_outsideSince.Clear();
|
||||||
|
s_lastWarp.Clear();
|
||||||
|
s_faulted = false;
|
||||||
|
s_nextRescan = s_nextCheck = 0f;
|
||||||
|
|
||||||
|
StageBarrierBoot.EnsureRunner();
|
||||||
|
|
||||||
|
if (moved) { RebuildCarve(cfg); }
|
||||||
|
StageBarrierVisual.Show(cfg, center, radius);
|
||||||
|
|
||||||
|
Log("세움 중심 " + center.ToString("F1") + " 반경 " + radius.ToString("F1") + " m · 스타일 " + StageBarrierVisual.CurrentStyle +
|
||||||
|
(cfg.carveNavMesh ? " · 카브 " + CarveSegments + "세그(" + LastCarveBuildMs.ToString("F1") + " ms)" : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>스테이지 클리어 — 장벽을 연다. 그림은 fadeSeconds 동안 사라진 뒤 풀에 반납된다.</summary>
|
||||||
|
public static void Clear()
|
||||||
|
{
|
||||||
|
if (!s_active && s_carve.Count == 0 && !StageBarrierVisual.HasActive) return;
|
||||||
|
s_active = false;
|
||||||
|
ClearCount++;
|
||||||
|
s_outsideSince.Clear();
|
||||||
|
s_lastWarp.Clear();
|
||||||
|
ClearCarve();
|
||||||
|
StageBarrierVisual.Hide(Cfg);
|
||||||
|
Log("열림(클리어)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>이 좌표가 장벽 안인가. 장벽이 없으면 항상 true(제약 없음).</summary>
|
||||||
|
public static bool IsInside(Vector3 pos)
|
||||||
|
{
|
||||||
|
if (!s_active) return true;
|
||||||
|
return HorizontalSqr(pos, s_center) <= s_radius * s_radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>여유를 더해서 판정한다(음수면 안쪽으로 좁힌다).</summary>
|
||||||
|
public static bool IsInside(Vector3 pos, float extraMargin)
|
||||||
|
{
|
||||||
|
if (!s_active) return true;
|
||||||
|
float r = Mathf.Max(0f, s_radius + extraMargin);
|
||||||
|
return HorizontalSqr(pos, s_center) <= r * r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>중심에서의 수평 거리(m). 장벽이 없으면 −1.</summary>
|
||||||
|
public static float DistanceFromCenter(Vector3 pos)
|
||||||
|
{
|
||||||
|
if (!s_active) return -1f;
|
||||||
|
return Mathf.Sqrt(HorizontalSqr(pos, s_center));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>스타일만 바꾼다(PD 비교용 · 장벽은 그대로). 판정에는 영향이 없다.</summary>
|
||||||
|
public static void SetStyle(StageBarrierStyle style)
|
||||||
|
{
|
||||||
|
var cfg = Cfg;
|
||||||
|
if (cfg == null) return;
|
||||||
|
StageBarrierVisual.StyleOverride = style;
|
||||||
|
if (s_active) StageBarrierVisual.Show(cfg, s_center, s_radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// 판정 · 워프백
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
internal static void Tick()
|
||||||
|
{
|
||||||
|
StageBarrierVisual.Tick(Cfg);
|
||||||
|
|
||||||
|
if (s_faulted || !s_active) return;
|
||||||
|
var cfg = Cfg;
|
||||||
|
if (cfg == null || !cfg.enabled || WLStageBarrierSettings.RuntimeDisabled || !cfg.blockEnabled) return;
|
||||||
|
|
||||||
|
float now = Time.unscaledTime;
|
||||||
|
if (now >= s_nextRescan)
|
||||||
|
{
|
||||||
|
s_nextRescan = now + Mathf.Max(0.1f, cfg.rescanSeconds);
|
||||||
|
Rescan();
|
||||||
|
}
|
||||||
|
if (now < s_nextCheck) return;
|
||||||
|
s_nextCheck = now + Mathf.Max(0.02f, cfg.checkSeconds);
|
||||||
|
|
||||||
|
// 🔴 Debug.LogException 을 쓰지 않는다 — ErrorLogHookManager 가 그것을 플레이어 모달로 띄운다(813z).
|
||||||
|
try { CheckAll(cfg, now); }
|
||||||
|
catch (System.Exception ex) { s_faulted = true; Log("중단(예외) — " + ex.Message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Rescan()
|
||||||
|
{
|
||||||
|
// FindObjectsInactive.Exclude — 비활성 몹은 풀에 들어간 사체다(Actor.cs:633 에서 에이전트를 끈다).
|
||||||
|
s_mobs = Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||||
|
Scans++;
|
||||||
|
if (s_outsideSince.Count > 128) s_outsideSince.Clear(); // 풀 재사용으로 쌓인 죽은 키 정리
|
||||||
|
if (s_lastWarp.Count > 128) s_lastWarp.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CheckAll(WLStageBarrierSettings cfg, float now)
|
||||||
|
{
|
||||||
|
if (cfg.blockPlayer)
|
||||||
|
{
|
||||||
|
var pc = MyValue.MyPC;
|
||||||
|
if (!DSUtil.CheckNull(pc)) CheckOne(cfg, pc, now, true);
|
||||||
|
}
|
||||||
|
if (!cfg.blockMobs) return;
|
||||||
|
|
||||||
|
var mobs = s_mobs;
|
||||||
|
for (int i = 0; i < mobs.Length; i++)
|
||||||
|
{
|
||||||
|
var m = mobs[i];
|
||||||
|
if (DSUtil.CheckNull(m)) continue;
|
||||||
|
CheckOne(cfg, m, now, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CheckOne(WLStageBarrierSettings cfg, Actor actor, float now, bool isPlayer)
|
||||||
|
{
|
||||||
|
int key = actor.GetInstanceID();
|
||||||
|
if (actor.IsDead()) { s_outsideSince.Remove(key); return; }
|
||||||
|
Checks++;
|
||||||
|
|
||||||
|
Vector3 pos = actor.Get_position();
|
||||||
|
float margin = isPlayer ? cfg.leaveMargin : cfg.mobLeaveMargin;
|
||||||
|
float limit = s_radius + Mathf.Max(0f, margin);
|
||||||
|
if (HorizontalSqr(pos, s_center) <= limit * limit) { s_outsideSince.Remove(key); return; }
|
||||||
|
|
||||||
|
float since;
|
||||||
|
if (!s_outsideSince.TryGetValue(key, out since))
|
||||||
|
{
|
||||||
|
s_outsideSince[key] = now; // 넉백·대시일 수 있다 — 유예를 준다
|
||||||
|
OutsideSeen++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float grace = isPlayer ? cfg.graceSeconds : cfg.mobGraceSeconds;
|
||||||
|
if (now - since < Mathf.Max(0f, grace)) return;
|
||||||
|
|
||||||
|
float last;
|
||||||
|
float cool = isPlayer ? cfg.warpCooldownSeconds : cfg.mobWarpCooldownSeconds;
|
||||||
|
if (s_lastWarp.TryGetValue(key, out last) && now - last < Mathf.Max(0f, cool))
|
||||||
|
{ WarpsSkippedCooldown++; return; }
|
||||||
|
|
||||||
|
WarpBack(cfg, actor, key, pos, now, isPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 되돌릴 지점 = 「가장 가까운 안쪽 지점」 — 중심에서 대상 방향으로 (반경 − 들여놓기) 만큼.
|
||||||
|
/// **좌표만 다루는 순수 함수**라 프로브가 실 액터 없이 같은 코드를 검증할 수 있다(GC 0).
|
||||||
|
/// </summary>
|
||||||
|
public static Vector3 NearestInsidePoint(WLStageBarrierSettings cfg, Vector3 center, float radius, Vector3 from)
|
||||||
|
{
|
||||||
|
Vector3 d = from - center; d.y = 0f;
|
||||||
|
float len = d.magnitude;
|
||||||
|
float target = Mathf.Max(0f, radius - Mathf.Max(0f, cfg.warpInsetMeters));
|
||||||
|
if (len < 0.001f) return new Vector3(center.x, from.y, center.z); // 정중앙 = 이미 안
|
||||||
|
Vector3 p = center + d * (target / len);
|
||||||
|
p.y = from.y; // 높이는 NavMesh 스냅이 정한다
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>되돌릴 지점을 NavMesh 위로 스냅한다. 못 찾으면 false = 워프하지 않는다(지형 뚫기 방지).</summary>
|
||||||
|
public static bool ResolveWarpTarget(WLStageBarrierSettings cfg, Vector3 center, float radius, Vector3 from, out Vector3 target)
|
||||||
|
{
|
||||||
|
Vector3 want = NearestInsidePoint(cfg, center, radius, from);
|
||||||
|
NavMeshHit hit;
|
||||||
|
if (NavMesh.SamplePosition(want, out hit, Mathf.Max(0.1f, cfg.warpNavSampleRadius), NavMesh.AllAreas)
|
||||||
|
|| NavMesh.SamplePosition(want, out hit, Mathf.Max(0.1f, cfg.warpNavSampleRadiusFar), NavMesh.AllAreas)
|
||||||
|
|| NavMesh.SamplePosition(center, out hit, Mathf.Max(0.1f, cfg.warpNavSampleRadiusFar), NavMesh.AllAreas))
|
||||||
|
{ target = hit.position; return true; }
|
||||||
|
target = want;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WarpBack(WLStageBarrierSettings cfg, Actor actor, int key, Vector3 from, float now, bool isPlayer)
|
||||||
|
{
|
||||||
|
s_outsideSince.Remove(key); // 성공이든 실패든 창을 다시 연다(실패 시 다음 유예 뒤 재시도)
|
||||||
|
|
||||||
|
Vector3 target;
|
||||||
|
if (!ResolveWarpTarget(cfg, s_center, s_radius, from, out target))
|
||||||
|
{
|
||||||
|
WarpsFailedNavMesh++;
|
||||||
|
Log("복귀 실패 — 안쪽 지점이 NavMesh 밖 " + from.ToString("F1"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.warpResetsPath)
|
||||||
|
{
|
||||||
|
var agent = actor.Get_Agent();
|
||||||
|
if (agent != null && agent.enabled && agent.isOnNavMesh) agent.ResetPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.Set_Warp(target); // 원본 API · 이펙트 0 · 이미 스냅된 지점을 받는다
|
||||||
|
if (isPlayer && cfg.warpSnapsCameraLead) SnapCameraLead();
|
||||||
|
|
||||||
|
s_lastWarp[key] = now;
|
||||||
|
if (isPlayer) PlayerWarps++; else MobWarps++;
|
||||||
|
LastWarpFrom = from; LastWarpTo = target;
|
||||||
|
Log("복귀 " + (isPlayer ? "PC" : "몹 " + actor.name) + " " + from.ToString("F1") + " → " + target.ToString("F1") +
|
||||||
|
" (중심 " + Mathf.Sqrt(HorizontalSqr(from, s_center)).ToString("F1") + " m → " +
|
||||||
|
Mathf.Sqrt(HorizontalSqr(target, s_center)).ToString("F1") + " m)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>되돌린 직후 카메라 이동 리드를 0 으로 스냅한다(811 CameraMoveLead.Snap · 813t2 방식 · RealCamera 무수정).</summary>
|
||||||
|
static void SnapCameraLead()
|
||||||
|
{
|
||||||
|
var cam = MyValue.m_RealCamera;
|
||||||
|
if (DSUtil.CheckNull(cam)) return;
|
||||||
|
var lead = cam.Get_MoveLead();
|
||||||
|
if (lead != null) lead.Snap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// NavMesh 카브(옵션)
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>둘레 길이·세그먼트 길이로 세그먼트 수를 낸다(상한 적용). 순수 함수 — 프로브가 그대로 검증한다.</summary>
|
||||||
|
public static int CarveSegmentCount(WLStageBarrierSettings cfg, float radius)
|
||||||
|
{
|
||||||
|
if (cfg == null || radius <= 0f) return 0;
|
||||||
|
float circumference = 2f * Mathf.PI * radius;
|
||||||
|
int n = Mathf.CeilToInt(circumference / Mathf.Max(0.5f, cfg.carveSegmentArcMeters));
|
||||||
|
return Mathf.Clamp(n, 3, Mathf.Max(3, cfg.maxCarveSegments));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RebuildCarve(WLStageBarrierSettings cfg)
|
||||||
|
{
|
||||||
|
ClearCarve();
|
||||||
|
if (!cfg.carveNavMesh) { CarveSegments = 0; LastCarveBuildMs = 0f; return; }
|
||||||
|
|
||||||
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
int n = CarveSegmentCount(cfg, s_radius);
|
||||||
|
EnsureCarveRoot();
|
||||||
|
|
||||||
|
float arc = 2f * Mathf.PI * s_radius / n;
|
||||||
|
float width = arc * (1f + Mathf.Max(0f, cfg.carveOverlap));
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
float a = (i / (float)n) * Mathf.PI * 2f;
|
||||||
|
Vector3 dir = new Vector3(Mathf.Cos(a), 0f, Mathf.Sin(a));
|
||||||
|
Vector3 p = s_center + dir * s_radius;
|
||||||
|
|
||||||
|
var go = new GameObject("carve_" + i);
|
||||||
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
go.transform.SetParent(s_carveRoot, false);
|
||||||
|
go.transform.position = p;
|
||||||
|
go.transform.rotation = Quaternion.LookRotation(dir, Vector3.up); // +Z = 반경 방향
|
||||||
|
|
||||||
|
var ob = go.AddComponent<NavMeshObstacle>();
|
||||||
|
ob.shape = NavMeshObstacleShape.Box;
|
||||||
|
ob.size = new Vector3(width, Mathf.Max(0.5f, cfg.carveHeight), Mathf.Max(0.2f, cfg.carveThickness));
|
||||||
|
ob.center = new Vector3(0f, Mathf.Max(0.5f, cfg.carveHeight) * 0.5f, 0f);
|
||||||
|
ob.carving = true;
|
||||||
|
ob.carveOnlyStationary = cfg.carveStaticOnly;
|
||||||
|
s_carve.Add(go);
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
CarveSegments = n;
|
||||||
|
LastCarveBuildMs = (float)sw.Elapsed.TotalMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EnsureCarveRoot()
|
||||||
|
{
|
||||||
|
if (s_carveRoot != null) return;
|
||||||
|
var go = new GameObject("[WL815c] StageBarrierCarve");
|
||||||
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
s_carveRoot = go.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ClearCarve()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < s_carve.Count; i++)
|
||||||
|
{
|
||||||
|
var g = s_carve[i];
|
||||||
|
if (g == null) continue;
|
||||||
|
if (Application.isPlaying) Object.Destroy(g); else Object.DestroyImmediate(g);
|
||||||
|
}
|
||||||
|
s_carve.Clear();
|
||||||
|
CarveSegments = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// 유틸 · 진단
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
static float HorizontalSqr(Vector3 a, Vector3 b)
|
||||||
|
{
|
||||||
|
float dx = a.x - b.x, dz = a.z - b.z;
|
||||||
|
return dx * dx + dz * dz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Log(string msg)
|
||||||
|
{
|
||||||
|
LastLog = msg;
|
||||||
|
var c = Cfg;
|
||||||
|
if (c != null && c.verboseLog) Debug.Log("[StageBarrier] " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프로브용: 카운터·유예 창 초기화(장벽 자체는 건드리지 않는다).</summary>
|
||||||
|
public static void ResetDiagnostics()
|
||||||
|
{
|
||||||
|
SetCount = ClearCount = Checks = Scans = 0;
|
||||||
|
PlayerWarps = MobWarps = WarpsFailedNavMesh = WarpsSkippedCooldown = OutsideSeen = 0;
|
||||||
|
LastLog = ""; LastWarpFrom = LastWarpTo = Vector3.zero;
|
||||||
|
s_outsideSince.Clear(); s_lastWarp.Clear(); s_mobs = new MobActor[0];
|
||||||
|
s_nextRescan = s_nextCheck = 0f; s_faulted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프로브용: 전부(장벽·그림·카브·풀) 원상 복구.</summary>
|
||||||
|
public static void HardResetForProbe()
|
||||||
|
{
|
||||||
|
s_active = false;
|
||||||
|
ClearCarve();
|
||||||
|
StageBarrierVisual.HardResetForProbe();
|
||||||
|
ResetDiagnostics();
|
||||||
|
if (s_carveRoot != null)
|
||||||
|
{
|
||||||
|
if (Application.isPlaying) Object.Destroy(s_carveRoot.gameObject); else Object.DestroyImmediate(s_carveRoot.gameObject);
|
||||||
|
s_carveRoot = null;
|
||||||
|
}
|
||||||
|
SetCount = ClearCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프로브용: 러너 없이 1회 판정(에디트 모드 · 타이머 초기화).</summary>
|
||||||
|
public static void ForceTick() { s_nextRescan = s_nextCheck = 0f; Tick(); }
|
||||||
|
|
||||||
|
/// <summary>프로브용: 러너가 부르는 것과 똑같은 틱(타이머 초기화 0 — GC 측정용).</summary>
|
||||||
|
public static void ProbeTick() { Tick(); }
|
||||||
|
|
||||||
|
/// <summary>프로브용: 임의 액터 1명을 지금 판정한다(유예·쿨 무시하고 즉시 복귀까지). 되돌렸으면 true.</summary>
|
||||||
|
public static bool ForceCheck(Actor actor, bool asPlayer)
|
||||||
|
{
|
||||||
|
var cfg = Cfg;
|
||||||
|
if (cfg == null || actor == null || !s_active) return false;
|
||||||
|
Vector3 pos = actor.Get_position();
|
||||||
|
float margin = asPlayer ? cfg.leaveMargin : cfg.mobLeaveMargin;
|
||||||
|
float limit = s_radius + Mathf.Max(0f, margin);
|
||||||
|
if (HorizontalSqr(pos, s_center) <= limit * limit) return false;
|
||||||
|
WarpBack(cfg, actor, actor.GetInstanceID(), pos, Time.unscaledTime, asPlayer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프로브용: 카브 오브젝트 수(실제 씬에 있는 것).</summary>
|
||||||
|
public static int CarveObjectCount { get { return s_carve.Count; } }
|
||||||
|
|
||||||
|
/// <summary>프로브용: i 번째 카브 세그먼트. 카브 오브젝트는 HideAndDontSave 라 FindObjectsByType 에 안 잡힌다.</summary>
|
||||||
|
public static NavMeshObstacle CarveObstacleAt(int i)
|
||||||
|
{
|
||||||
|
if (i < 0 || i >= s_carve.Count || s_carve[i] == null) return null;
|
||||||
|
return s_carve[i].GetComponent<NavMeshObstacle>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
/// <summary>StageBarrier 의 시간 축. 숨김 GameObject 1개 · 코루틴 0.</summary>
|
||||||
|
internal sealed class StageBarrierRunner : MonoBehaviour
|
||||||
|
{
|
||||||
|
void Update() { StageBarrier.Tick(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>러너 부팅(C8 — SO 가 꺼져 있으면 오브젝트 0). Set() 이 처음 불릴 때만 뜬다.</summary>
|
||||||
|
public static class StageBarrierBoot
|
||||||
|
{
|
||||||
|
static StageBarrierRunner s_runner;
|
||||||
|
|
||||||
|
/// <summary>러너가 떠 있는가(프로브).</summary>
|
||||||
|
public static bool RunnerAlive { get { return !DSUtil.CheckNull(s_runner); } }
|
||||||
|
|
||||||
|
internal static void EnsureRunner()
|
||||||
|
{
|
||||||
|
if (!Application.isPlaying || !DSUtil.CheckNull(s_runner)) return;
|
||||||
|
if (!WLStageBarrierSettings.Active) return;
|
||||||
|
var go = new GameObject("[WL815c] StageBarrierRunner");
|
||||||
|
go.hideFlags = HideFlags.HideAndDontSave; // 씬 전환에도 살아남고 씬·프리팹에 저장되지 않는다
|
||||||
|
s_runner = go.AddComponent<StageBarrierRunner>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프로브용: 러너를 회수한다.</summary>
|
||||||
|
public static void KillRunnerForProbe()
|
||||||
|
{
|
||||||
|
if (DSUtil.CheckNull(s_runner)) { s_runner = null; return; }
|
||||||
|
if (Application.isPlaying) Object.Destroy(s_runner.gameObject); else Object.DestroyImmediate(s_runner.gameObject);
|
||||||
|
s_runner = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 60022f941d7bf9549bd96048f7478aa9
|
||||||
|
|
@ -0,0 +1,623 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// StageBarrierVisual.cs — 「보이는 장벽」 3스타일 (PD 대안 비교 · 값 하나로 전환)
|
||||||
|
//
|
||||||
|
// PD 질문(2026-09-11) 「스테이지 지역을 어떻게 구역으로 막을 것인지 대안
|
||||||
|
// (울타리 오브젝트 배치 · 불꽃 이펙트 장벽 등)」
|
||||||
|
// → 대안을 **코드로 만들어** PD 가 WLStageBarrierSettings.style 값 하나만 바꿔 보게 한다.
|
||||||
|
//
|
||||||
|
// ⓐ MagicRing — Hovl 「Marker 3 Zone Loop」 1개를 반경에 맞게 스케일(+겹). 가장 싸다.
|
||||||
|
// ⓑ Props — 울타리·바위 프리팹을 둘레에 propSpacing 간격으로 배치. 가장 「막혔다」고 읽힌다.
|
||||||
|
// ⓒ Fire — 불꽃 파티클을 둘레에 fireSpacing 간격으로 배치(총량 상한). 가장 비싸다.
|
||||||
|
//
|
||||||
|
// ■ 실측 근거(WL815c_SURVEY / SURVEY2 · 이 worktree 헤드리스 에디터)
|
||||||
|
// Marker 3 Zone Loop : 파티클시스템 4 · 머티리얼 1 · **시각 반경 1.10 m(스케일 1)** · 버스트 1/루프 → 살아있는 파티클 3~4
|
||||||
|
// Fence_01 : 메시 1 · 머티리얼 1 · **삼각형 120** · 크기 2.38 × 1.02 × 0.27 m · pivot 지면·중앙
|
||||||
|
// Fire_Particles : 파티클시스템 3(FireRed 20/s×1.5 s · FireYellow 15/s×1.0 s · Smoke 10/s×8 s)
|
||||||
|
// → **인스턴스 1개 정상상태 125 파티클**
|
||||||
|
//
|
||||||
|
// ■ 원본 무수정
|
||||||
|
// · 프리팹은 `Object.Instantiate` 로 **끊어진 클론**을 만든다(PrefabUtility 를 쓰지 않는다) → 원본 프리팹 무수정.
|
||||||
|
// · 색은 인스턴스의 `ParticleSystem.main.startColor` 에만 대입한다 → **머티리얼·셰이더 무수정**.
|
||||||
|
// · 프롭은 transform 과 (옵션) Collider 제거만 — 머티리얼을 건드리지 않는다.
|
||||||
|
//
|
||||||
|
// ■ 811s EffectScaleGuard / 811q 예산과 충돌 0
|
||||||
|
// EffectScaleGuard 는 **원본 이펙트 풀(`InGameInfo.tf_Effects` 의 직속 자식)** 만 훑는다.
|
||||||
|
// 장벽 그림은 전부 `[WL815c] StageBarrierVisual` 루트 밑에 있으므로 그 스윕 대상이 아니다.
|
||||||
|
// 불꽃은 그것과 별개로 `maxTotalParticles` 로 자체 상한을 건다.
|
||||||
|
//
|
||||||
|
// ■ 연출 · 풀링
|
||||||
|
// 입장 = appearSeconds 동안 0 → 1 (링은 반경이 퍼지고 · 프롭은 지면에서 솟고 · 불꽃은 커진다)
|
||||||
|
// 클리어 = fadeSeconds 동안 1 → 0 뒤 **SetActive(false) 로 풀에 반납**(Destroy 0 · 다음 Set 은 재사용).
|
||||||
|
// 스타일이 바뀌면 이전 스타일의 인스턴스는 즉시 풀에 반납된다(스타일마다 별도 풀).
|
||||||
|
//
|
||||||
|
// 🔴 Debug.LogError/LogException 금지(813z).
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace WL.Combat.Stage
|
||||||
|
{
|
||||||
|
/// <summary>장벽 그림 3스타일. 정적 · 풀링 · 원본 프리팹 무수정.</summary>
|
||||||
|
public static class StageBarrierVisual
|
||||||
|
{
|
||||||
|
// ───────────────────────────────────────── 상태
|
||||||
|
static Transform s_root;
|
||||||
|
static StageBarrierStyle s_current = StageBarrierStyle.None;
|
||||||
|
static Vector3 s_center;
|
||||||
|
static float s_radius;
|
||||||
|
static float s_phaseStart; // 연출 시작 시각
|
||||||
|
static float s_phaseLen; // 연출 길이(초)
|
||||||
|
static bool s_showing; // true = 나타나는 중/유지 · false = 사라지는 중/꺼짐
|
||||||
|
static float s_t = 0f; // 0..1 현재 연출 진행도
|
||||||
|
|
||||||
|
/// <summary>PD·프로브가 SO 를 안 고치고 스타일만 바꿀 때(−1 = 무시).</summary>
|
||||||
|
public static StageBarrierStyle StyleOverride = (StageBarrierStyle)(-1);
|
||||||
|
|
||||||
|
/// <summary>지금 그려지고 있는 스타일.</summary>
|
||||||
|
public static StageBarrierStyle CurrentStyle { get { return s_current; } }
|
||||||
|
|
||||||
|
/// <summary>살아 있는(꺼지지 않은) 그림이 있는가.</summary>
|
||||||
|
public static bool HasActive { get { return s_showing || s_t > 0f; } }
|
||||||
|
|
||||||
|
// ───────────────────────────────────────── 진단(프로브가 읽는다)
|
||||||
|
/// <summary>Instantiate 호출 누계. 두 번째 Set 부터 늘지 않으면 풀링이 도는 것이다.</summary>
|
||||||
|
public static int Instantiates;
|
||||||
|
/// <summary>지금 활성인 인스턴스 수 · 풀에 반납된 수.</summary>
|
||||||
|
public static int ActiveInstances, PooledInstances;
|
||||||
|
/// <summary>스타일 전환 횟수 · Show/Hide 호출 수.</summary>
|
||||||
|
public static int StyleSwitches, Shows, Hides;
|
||||||
|
/// <summary>ⓑ 실제로 놓인 프롭 수 · 상한에 걸려 벌어진 간격(m).</summary>
|
||||||
|
public static int PropCount; public static float PropSpacingUsed;
|
||||||
|
/// <summary>ⓒ 실제로 놓인 불꽃 수 · 적용된 방출률 배수 · 추정 총 파티클.</summary>
|
||||||
|
public static int FireCount; public static float FireRateMul; public static int FireParticleEstimate;
|
||||||
|
/// <summary>ⓒ 프리팹 1개당 정상상태 파티클 추정(실측 계산).</summary>
|
||||||
|
public static int FirePerInstanceEstimate;
|
||||||
|
/// <summary>ⓐ 링에 적용된 스케일.</summary>
|
||||||
|
public static float RingScale;
|
||||||
|
/// <summary>마지막 Show 가 든 시간(ms).</summary>
|
||||||
|
public static float LastBuildMs;
|
||||||
|
|
||||||
|
// ───────────────────────────────────────── 풀(스타일별 · Destroy 0)
|
||||||
|
sealed class Slot
|
||||||
|
{
|
||||||
|
public GameObject go;
|
||||||
|
public Transform tf;
|
||||||
|
public Vector3 basePos;
|
||||||
|
public Quaternion baseRot;
|
||||||
|
public Vector3 baseScale;
|
||||||
|
public ParticleSystem[] ps;
|
||||||
|
/// <summary>프리팹 원본 색·상한·방출률(인스턴스 생성 때 1회 채집 — 풀 재사용 때 여기서 되돌린다).</summary>
|
||||||
|
public Color[] psOrig;
|
||||||
|
public int[] psMaxOrig;
|
||||||
|
public ParticleSystem.MinMaxCurve[] psRateOrig;
|
||||||
|
/// <summary>이번 회차에 쓸 색(연출 알파가 여기에 곱해진다).</summary>
|
||||||
|
public Color[] psColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly List<Slot> s_ring = new List<Slot>();
|
||||||
|
static readonly List<Slot> s_props = new List<Slot>();
|
||||||
|
static readonly List<Slot> s_fires = new List<Slot>();
|
||||||
|
static readonly List<Slot> s_live = new List<Slot>(); // 지금 쓰는 슬롯(연출 대상)
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// 공개 — Show / Hide / Tick
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>장벽 그림을 세운다(입장 연출 시작).</summary>
|
||||||
|
public static void Show(WLStageBarrierSettings cfg, Vector3 center, float radius)
|
||||||
|
{
|
||||||
|
if (cfg == null) return;
|
||||||
|
Shows++;
|
||||||
|
|
||||||
|
StageBarrierStyle want = StyleOverride >= 0 ? StyleOverride : cfg.style;
|
||||||
|
if (!cfg.showVisual) want = StageBarrierStyle.None;
|
||||||
|
|
||||||
|
s_center = center;
|
||||||
|
s_radius = Mathf.Max(0.1f, radius) * Mathf.Max(0.05f, cfg.visualRadiusScale);
|
||||||
|
|
||||||
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
if (want != s_current) { ReturnAll(); StyleSwitches++; }
|
||||||
|
s_current = want;
|
||||||
|
|
||||||
|
switch (want)
|
||||||
|
{
|
||||||
|
case StageBarrierStyle.MagicRing: BuildRing(cfg); break;
|
||||||
|
case StageBarrierStyle.Props: BuildProps(cfg); break;
|
||||||
|
case StageBarrierStyle.Fire: BuildFire(cfg); break;
|
||||||
|
default: ReturnAll(); break;
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
LastBuildMs = (float)sw.Elapsed.TotalMilliseconds;
|
||||||
|
|
||||||
|
s_showing = true;
|
||||||
|
s_phaseStart = Time.unscaledTime;
|
||||||
|
s_phaseLen = Mathf.Max(0.01f, cfg.appearSeconds);
|
||||||
|
ApplyPhase(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>장벽 그림을 연다(클리어 연출 시작 → fadeSeconds 뒤 풀 반납).</summary>
|
||||||
|
public static void Hide(WLStageBarrierSettings cfg)
|
||||||
|
{
|
||||||
|
if (!s_showing && s_t <= 0f) return;
|
||||||
|
Hides++;
|
||||||
|
s_showing = false;
|
||||||
|
s_phaseStart = Time.unscaledTime;
|
||||||
|
s_phaseLen = Mathf.Max(0.01f, cfg != null ? cfg.fadeSeconds : 0.6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>연출 진행. StageBarrier.Tick 이 부른다.</summary>
|
||||||
|
internal static void Tick(WLStageBarrierSettings cfg)
|
||||||
|
{
|
||||||
|
if (s_live.Count == 0 && s_t <= 0f) return;
|
||||||
|
float len = Mathf.Max(0.01f, s_phaseLen);
|
||||||
|
float k = Mathf.Clamp01((Time.unscaledTime - s_phaseStart) / len);
|
||||||
|
float t = s_showing ? k : 1f - k;
|
||||||
|
if (Mathf.Approximately(t, s_t) && (t <= 0f || t >= 1f)) { if (t <= 0f && !s_showing) ReturnAll(); return; }
|
||||||
|
ApplyPhase(t);
|
||||||
|
if (!s_showing && t <= 0f) ReturnAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// ⓐ 마법진 링 — Hovl Marker 를 반경에 맞게 스케일
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
static void BuildRing(WLStageBarrierSettings cfg)
|
||||||
|
{
|
||||||
|
ReturnAll();
|
||||||
|
if (cfg.ringPrefab == null) { RingScale = 0f; return; }
|
||||||
|
|
||||||
|
int layers = 1 + Mathf.Max(0, cfg.ringExtraRings);
|
||||||
|
float baseR = Mathf.Max(0.01f, cfg.ringPrefabVisualRadius);
|
||||||
|
|
||||||
|
for (int i = 0; i < layers; i++)
|
||||||
|
{
|
||||||
|
float r = s_radius + (i - (layers - 1) * 0.5f) * Mathf.Max(0f, cfg.ringExtraSpacing);
|
||||||
|
float scale = r / baseR;
|
||||||
|
if (i == 0) RingScale = scale;
|
||||||
|
|
||||||
|
var s = Take(s_ring, cfg.ringPrefab);
|
||||||
|
if (s == null) continue;
|
||||||
|
s.basePos = new Vector3(s_center.x, s_center.y + cfg.visualYOffset + i * 0.01f, s_center.z);
|
||||||
|
s.baseRot = Quaternion.identity;
|
||||||
|
s.baseScale = new Vector3(scale, scale * Mathf.Max(0.01f, cfg.ringYScale), scale);
|
||||||
|
s.tf.SetPositionAndRotation(s.basePos, s.baseRot);
|
||||||
|
if (cfg.ringTintEnabled) Tint(s, cfg.ringColor);
|
||||||
|
Restart(s);
|
||||||
|
s_live.Add(s);
|
||||||
|
}
|
||||||
|
RefreshCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// ⓑ 프롭 링 — 울타리 · 바위를 둘레에 배치(풀링 · 상한 · 지면 스냅)
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
static void BuildProps(WLStageBarrierSettings cfg)
|
||||||
|
{
|
||||||
|
ReturnAll();
|
||||||
|
PropCount = 0; PropSpacingUsed = 0f;
|
||||||
|
if (cfg.propPrefabs == null || cfg.propPrefabs.Length == 0) return;
|
||||||
|
|
||||||
|
float circumference = 2f * Mathf.PI * s_radius;
|
||||||
|
int n = Mathf.Max(3, Mathf.RoundToInt(circumference / Mathf.Max(0.2f, cfg.propSpacing)));
|
||||||
|
if (n > cfg.maxProps) n = Mathf.Max(3, cfg.maxProps); // 🔴 상한 — 넘으면 간격이 벌어진다
|
||||||
|
PropSpacingUsed = circumference / n;
|
||||||
|
|
||||||
|
var rnd = new System.Random(cfg.propSeed);
|
||||||
|
int variants = cfg.propPrefabs.Length;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
var prefab = cfg.propPrefabs[i % variants];
|
||||||
|
if (prefab == null) continue;
|
||||||
|
|
||||||
|
float a = (i / (float)n) * Mathf.PI * 2f;
|
||||||
|
Vector3 dir = new Vector3(Mathf.Cos(a), 0f, Mathf.Sin(a));
|
||||||
|
float radial = s_radius + ((float)rnd.NextDouble() * 2f - 1f) * Mathf.Max(0f, cfg.propRadialJitter);
|
||||||
|
Vector3 p = s_center + dir * radial;
|
||||||
|
|
||||||
|
if (cfg.propGroundSnap)
|
||||||
|
{
|
||||||
|
RaycastHit hit;
|
||||||
|
if (Physics.Raycast(p + Vector3.up * Mathf.Max(1f, cfg.propRayUp), Vector3.down, out hit,
|
||||||
|
Mathf.Max(2f, cfg.propRayLength)))
|
||||||
|
p.y = hit.point.y;
|
||||||
|
else p.y = s_center.y;
|
||||||
|
}
|
||||||
|
else p.y = s_center.y;
|
||||||
|
p.y += cfg.visualYOffset;
|
||||||
|
|
||||||
|
// 접선 정렬: 프롭의 긴축(+X · Fence_01 실측 2.38 m)이 둘레의 접선을 따라가게 한다.
|
||||||
|
// LookRotation(접선) 은 +Z 를 접선에 맞추므로 +Y 90° 를 더해 +X 를 접선에 놓는다.
|
||||||
|
Quaternion rot = Quaternion.identity;
|
||||||
|
if (cfg.propAlignToTangent)
|
||||||
|
{
|
||||||
|
Vector3 tangent = new Vector3(-dir.z, 0f, dir.x);
|
||||||
|
rot = Quaternion.LookRotation(tangent, Vector3.up) * Quaternion.Euler(0f, 90f, 0f);
|
||||||
|
}
|
||||||
|
if (cfg.propYawJitter > 0f)
|
||||||
|
rot = rot * Quaternion.Euler(0f, ((float)rnd.NextDouble() * 2f - 1f) * cfg.propYawJitter, 0f);
|
||||||
|
|
||||||
|
var s = Take(s_props, prefab);
|
||||||
|
if (s == null) continue;
|
||||||
|
s.basePos = p;
|
||||||
|
s.baseRot = rot;
|
||||||
|
s.baseScale = Vector3.one * Mathf.Max(0.01f, cfg.propScale);
|
||||||
|
s.tf.SetPositionAndRotation(p, rot);
|
||||||
|
if (cfg.propStripColliders) StripColliders(s.go);
|
||||||
|
s_live.Add(s);
|
||||||
|
PropCount++;
|
||||||
|
}
|
||||||
|
RefreshCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// ⓒ 불꽃 링 — 파티클 총량 상한을 지키며 둘레에 배치
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
static void BuildFire(WLStageBarrierSettings cfg)
|
||||||
|
{
|
||||||
|
ReturnAll();
|
||||||
|
FireCount = 0; FireRateMul = 1f; FireParticleEstimate = 0; FirePerInstanceEstimate = 0;
|
||||||
|
if (cfg.firePrefabs == null || cfg.firePrefabs.Length == 0) return;
|
||||||
|
|
||||||
|
float circumference = 2f * Mathf.PI * s_radius;
|
||||||
|
int n = Mathf.Max(3, Mathf.RoundToInt(circumference / Mathf.Max(0.2f, cfg.fireSpacing)));
|
||||||
|
if (n > cfg.maxFires) n = Mathf.Max(3, cfg.maxFires);
|
||||||
|
|
||||||
|
// 🔴 프리팹 1개당 정상상태 파티클 추정 = Σ min(rate × life, maxParticles) — 프리팹에서 읽는다(코드 상수 0).
|
||||||
|
FirePerInstanceEstimate = EstimateSteadyState(cfg.firePrefabs[0]);
|
||||||
|
|
||||||
|
// 총량 상한 적용
|
||||||
|
float mul = 1f;
|
||||||
|
if (FirePerInstanceEstimate > 0 && cfg.maxTotalParticles > 0)
|
||||||
|
{
|
||||||
|
int would = FirePerInstanceEstimate * n;
|
||||||
|
if (would > cfg.maxTotalParticles)
|
||||||
|
{
|
||||||
|
if (cfg.fireBudgetMode == FireBudgetMode.ReduceCount)
|
||||||
|
n = Mathf.Max(3, cfg.maxTotalParticles / Mathf.Max(1, FirePerInstanceEstimate));
|
||||||
|
else
|
||||||
|
mul = Mathf.Clamp01(cfg.maxTotalParticles / (float)would);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FireRateMul = mul;
|
||||||
|
|
||||||
|
int variants = cfg.firePrefabs.Length;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
var prefab = cfg.firePrefabs[i % variants];
|
||||||
|
if (prefab == null) continue;
|
||||||
|
|
||||||
|
float a = (i / (float)n) * Mathf.PI * 2f;
|
||||||
|
Vector3 dir = new Vector3(Mathf.Cos(a), 0f, Mathf.Sin(a));
|
||||||
|
Vector3 p = s_center + dir * s_radius;
|
||||||
|
|
||||||
|
if (cfg.fireGroundSnap)
|
||||||
|
{
|
||||||
|
RaycastHit hit;
|
||||||
|
if (Physics.Raycast(p + Vector3.up * Mathf.Max(1f, cfg.propRayUp), Vector3.down, out hit,
|
||||||
|
Mathf.Max(2f, cfg.propRayLength)))
|
||||||
|
p.y = hit.point.y;
|
||||||
|
else p.y = s_center.y;
|
||||||
|
}
|
||||||
|
else p.y = s_center.y;
|
||||||
|
p.y += cfg.visualYOffset;
|
||||||
|
|
||||||
|
var s = Take(s_fires, prefab);
|
||||||
|
if (s == null) continue;
|
||||||
|
s.basePos = p;
|
||||||
|
s.baseRot = Quaternion.LookRotation(dir, Vector3.up);
|
||||||
|
s.baseScale = Vector3.one * Mathf.Max(0.01f, cfg.fireScale);
|
||||||
|
s.tf.SetPositionAndRotation(p, s.baseRot);
|
||||||
|
ApplyFireBudget(s, mul, cfg);
|
||||||
|
if (cfg.fireTintEnabled) Tint(s, cfg.fireColor);
|
||||||
|
Restart(s);
|
||||||
|
s_live.Add(s);
|
||||||
|
FireCount++;
|
||||||
|
}
|
||||||
|
FireParticleEstimate = Mathf.RoundToInt(FirePerInstanceEstimate * FireCount * mul);
|
||||||
|
RefreshCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프리팹 1개의 정상상태 파티클 추정 = Σ min(rateOverTime × startLifetime, maxParticles). 순수 계산.</summary>
|
||||||
|
public static int EstimateSteadyState(GameObject prefab)
|
||||||
|
{
|
||||||
|
if (prefab == null) return 0;
|
||||||
|
var pss = prefab.GetComponentsInChildren<ParticleSystem>(true);
|
||||||
|
float sum = 0f;
|
||||||
|
for (int i = 0; i < pss.Length; i++)
|
||||||
|
{
|
||||||
|
var m = pss[i].main;
|
||||||
|
var e = pss[i].emission;
|
||||||
|
float rate = e.enabled ? e.rateOverTime.constantMax : 0f;
|
||||||
|
float life = m.startLifetime.constantMax;
|
||||||
|
sum += Mathf.Min(rate * life, m.maxParticles);
|
||||||
|
}
|
||||||
|
return Mathf.RoundToInt(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>인스턴스에만 방출률·상한을 건다(프리팹 무수정 · 총량 상한의 실행부).</summary>
|
||||||
|
static void ApplyFireBudget(Slot s, float mul, WLStageBarrierSettings cfg)
|
||||||
|
{
|
||||||
|
if (s.ps == null || s.psRateOrig == null) return;
|
||||||
|
for (int i = 0; i < s.ps.Length; i++)
|
||||||
|
{
|
||||||
|
var ps = s.ps[i];
|
||||||
|
if (ps == null || i >= s.psRateOrig.Length) continue;
|
||||||
|
|
||||||
|
var orig = s.psRateOrig[i];
|
||||||
|
var e = ps.emission;
|
||||||
|
if (mul < 1f) e.rateOverTime = ScaleCurve(orig, mul);
|
||||||
|
|
||||||
|
var m = ps.main;
|
||||||
|
float life = m.startLifetime.constantMax;
|
||||||
|
int cap = Mathf.Max(4, Mathf.CeilToInt(orig.constantMax * mul * life) + 4);
|
||||||
|
int max = s.psMaxOrig != null && i < s.psMaxOrig.Length ? s.psMaxOrig[i] : m.maxParticles;
|
||||||
|
m.maxParticles = Mathf.Min(max, cap); // 🔴 하드 상한 — 버스트가 튀어도 못 넘는다
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// 연출 — 0 → 1 (나타남) / 1 → 0 (사라짐)
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
static void ApplyPhase(float t)
|
||||||
|
{
|
||||||
|
s_t = t;
|
||||||
|
float e = Mathf.SmoothStep(0f, 1f, Mathf.Clamp01(t));
|
||||||
|
for (int i = 0; i < s_live.Count; i++)
|
||||||
|
{
|
||||||
|
var s = s_live[i];
|
||||||
|
if (s == null || s.tf == null) continue;
|
||||||
|
|
||||||
|
if (s_current == StageBarrierStyle.Props)
|
||||||
|
{
|
||||||
|
// 프롭 = 지면에서 솟는다(Y 만 자란다 · XZ 고정) → 머티리얼 무수정으로 「생겨남」이 읽힌다
|
||||||
|
s.tf.localScale = new Vector3(s.baseScale.x, s.baseScale.y * Mathf.Max(0.001f, e), s.baseScale.z);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s.tf.localScale = s.baseScale * Mathf.Max(0.001f, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.ps != null && s.ps.Length > 0) SetAlpha(s, e);
|
||||||
|
|
||||||
|
bool on = e > 0.002f;
|
||||||
|
if (s.go.activeSelf != on) s.go.SetActive(on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>연출 진행도를 「원래 색」의 알파에 곱한다(인스턴스만 · 머티리얼 무수정 · 프리팹 원색 보존).</summary>
|
||||||
|
static void SetAlpha(Slot s, float a)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < s.ps.Length; i++)
|
||||||
|
{
|
||||||
|
var ps = s.ps[i];
|
||||||
|
if (ps == null) continue;
|
||||||
|
Color c = s.psColor != null && i < s.psColor.Length ? s.psColor[i] : ps.main.startColor.color;
|
||||||
|
var m = ps.main;
|
||||||
|
m.startColor = new ParticleSystem.MinMaxGradient(new Color(c.r, c.g, c.b, c.a * a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>SO 색으로 「원래 색」을 갈아 끼운다(다음 SetAlpha 부터 이 색이 쓰인다).</summary>
|
||||||
|
static void Tint(Slot s, Color color)
|
||||||
|
{
|
||||||
|
if (s.psColor == null) return;
|
||||||
|
for (int i = 0; i < s.psColor.Length; i++) s.psColor[i] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Restart(Slot s)
|
||||||
|
{
|
||||||
|
if (s.ps == null) return;
|
||||||
|
for (int i = 0; i < s.ps.Length; i++)
|
||||||
|
{
|
||||||
|
var ps = s.ps[i];
|
||||||
|
if (ps == null) continue;
|
||||||
|
ps.Clear(true);
|
||||||
|
if (Application.isPlaying) ps.Play(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// 풀
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
static Slot Take(List<Slot> pool, GameObject prefab)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < pool.Count; i++)
|
||||||
|
{
|
||||||
|
var s = pool[i];
|
||||||
|
if (s.go == null) continue;
|
||||||
|
if (s_live.Contains(s)) continue;
|
||||||
|
if (s.go.name != prefab.name + "#815c") continue; // 프리팹이 다르면 재사용하지 않는다
|
||||||
|
RestoreOriginals(s); // 🔴 지난 회차의 색·예산을 되돌린다
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureRoot();
|
||||||
|
var go = Object.Instantiate(prefab); // 🔴 끊어진 클론 — 원본 프리팹 무수정
|
||||||
|
go.name = prefab.name + "#815c";
|
||||||
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
go.transform.SetParent(s_root, false);
|
||||||
|
Instantiates++;
|
||||||
|
|
||||||
|
var ps = go.GetComponentsInChildren<ParticleSystem>(true);
|
||||||
|
var slot = new Slot
|
||||||
|
{
|
||||||
|
go = go,
|
||||||
|
tf = go.transform,
|
||||||
|
baseScale = Vector3.one,
|
||||||
|
ps = ps,
|
||||||
|
psOrig = new Color[ps.Length],
|
||||||
|
psMaxOrig = new int[ps.Length],
|
||||||
|
psRateOrig = new ParticleSystem.MinMaxCurve[ps.Length],
|
||||||
|
psColor = new Color[ps.Length],
|
||||||
|
};
|
||||||
|
for (int i = 0; i < ps.Length; i++)
|
||||||
|
{
|
||||||
|
slot.psOrig[i] = ps[i].main.startColor.color; // 프리팹 원색 채집
|
||||||
|
slot.psMaxOrig[i] = ps[i].main.maxParticles;
|
||||||
|
slot.psRateOrig[i] = ps[i].emission.rateOverTime;
|
||||||
|
slot.psColor[i] = slot.psOrig[i];
|
||||||
|
}
|
||||||
|
pool.Add(slot);
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>풀에서 꺼낼 때 프리팹 원본 색·파티클 상한·방출률 배수로 되돌린다.</summary>
|
||||||
|
static void RestoreOriginals(Slot s)
|
||||||
|
{
|
||||||
|
if (s.ps == null) return;
|
||||||
|
for (int i = 0; i < s.ps.Length; i++)
|
||||||
|
{
|
||||||
|
var ps = s.ps[i];
|
||||||
|
if (ps == null) continue;
|
||||||
|
if (s.psColor != null && s.psOrig != null && i < s.psOrig.Length) s.psColor[i] = s.psOrig[i];
|
||||||
|
if (s.psMaxOrig != null && i < s.psMaxOrig.Length) { var m = ps.main; m.maxParticles = s.psMaxOrig[i]; }
|
||||||
|
if (s.psRateOrig != null && i < s.psRateOrig.Length) { var e = ps.emission; e.rateOverTime = s.psRateOrig[i]; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 방출률 곡선에 배수를 건다.
|
||||||
|
/// 🔴 `rateOverTimeMultiplier` 는 Constant 모드에서 **상수 그 자체**라 거기에 mul 을 대입하면 rate 가 mul(개/s)이 된다.
|
||||||
|
/// 모드별로 원본 값을 읽어 곱해야 한다.
|
||||||
|
/// </summary>
|
||||||
|
public static ParticleSystem.MinMaxCurve ScaleCurve(ParticleSystem.MinMaxCurve src, float mul)
|
||||||
|
{
|
||||||
|
switch (src.mode)
|
||||||
|
{
|
||||||
|
case ParticleSystemCurveMode.Constant:
|
||||||
|
return new ParticleSystem.MinMaxCurve(src.constant * mul);
|
||||||
|
case ParticleSystemCurveMode.TwoConstants:
|
||||||
|
return new ParticleSystem.MinMaxCurve(src.constantMin * mul, src.constantMax * mul);
|
||||||
|
case ParticleSystemCurveMode.Curve:
|
||||||
|
return new ParticleSystem.MinMaxCurve(src.curveMultiplier * mul, src.curve);
|
||||||
|
default:
|
||||||
|
return new ParticleSystem.MinMaxCurve(src.curveMultiplier * mul, src.curveMin, src.curveMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReturnAll()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < s_live.Count; i++)
|
||||||
|
{
|
||||||
|
var s = s_live[i];
|
||||||
|
if (s == null || s.go == null) continue;
|
||||||
|
s.go.SetActive(false); // 🔴 Destroy 0 — 풀에 반납
|
||||||
|
}
|
||||||
|
s_live.Clear();
|
||||||
|
s_t = 0f;
|
||||||
|
RefreshCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StripColliders(GameObject go)
|
||||||
|
{
|
||||||
|
var cols = go.GetComponentsInChildren<Collider>(true);
|
||||||
|
for (int i = 0; i < cols.Length; i++)
|
||||||
|
{
|
||||||
|
if (cols[i] == null) continue;
|
||||||
|
if (Application.isPlaying) Object.Destroy(cols[i]); else Object.DestroyImmediate(cols[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EnsureRoot()
|
||||||
|
{
|
||||||
|
if (s_root != null) return;
|
||||||
|
var go = new GameObject("[WL815c] StageBarrierVisual");
|
||||||
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
s_root = go.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RefreshCounts()
|
||||||
|
{
|
||||||
|
ActiveInstances = s_live.Count;
|
||||||
|
PooledInstances = (s_ring.Count + s_props.Count + s_fires.Count) - s_live.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
// 진단 · 프로브
|
||||||
|
// ═════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>프로브용: 활성 인스턴스의 렌더러 수 · 머티리얼 종류 · 삼각형 합(드로우콜 근사).</summary>
|
||||||
|
public static void MeasureCost(out int renderers, out int materials, out long triangles, out int particleSystems)
|
||||||
|
{
|
||||||
|
renderers = 0; materials = 0; triangles = 0; particleSystems = 0;
|
||||||
|
var seen = new HashSet<int>();
|
||||||
|
for (int i = 0; i < s_live.Count; i++)
|
||||||
|
{
|
||||||
|
var s = s_live[i];
|
||||||
|
if (s == null || s.go == null) continue;
|
||||||
|
var rs = s.go.GetComponentsInChildren<Renderer>(true);
|
||||||
|
for (int k = 0; k < rs.Length; k++)
|
||||||
|
{
|
||||||
|
renderers++;
|
||||||
|
var mf = rs[k].GetComponent<MeshFilter>();
|
||||||
|
if (mf != null && mf.sharedMesh != null) triangles += mf.sharedMesh.triangles.Length / 3;
|
||||||
|
var ms = rs[k].sharedMaterials;
|
||||||
|
for (int j = 0; j < ms.Length; j++) if (ms[j] != null && seen.Add(ms[j].GetInstanceID())) materials++;
|
||||||
|
}
|
||||||
|
if (s.ps != null) particleSystems += s.ps.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프로브용: 지금 활성 인스턴스 수.</summary>
|
||||||
|
public static int LiveCount { get { return s_live.Count; } }
|
||||||
|
|
||||||
|
/// <summary>프로브용: 연출 진행도(0..1).</summary>
|
||||||
|
public static float Phase { get { return s_t; } }
|
||||||
|
|
||||||
|
/// <summary>프로브용: 연출을 끝난 상태로 즉시 만든다(캡처용 · 0 = 사라진 상태).</summary>
|
||||||
|
public static void ForcePhase(float t) { ApplyPhase(Mathf.Clamp01(t)); }
|
||||||
|
|
||||||
|
/// <summary>프로브용: 지금 도는 연출(입장/클리어)의 시계를 끝으로 돌린다 — 다음 Tick 이 실제 종료 경로를 탄다.</summary>
|
||||||
|
public static void FinishPhaseForProbe() { s_phaseStart = Time.unscaledTime - Mathf.Max(0.01f, s_phaseLen) - 0.01f; }
|
||||||
|
|
||||||
|
/// <summary>프로브용: 파티클을 에디트 모드에서 t 초만큼 시뮬레이트한다(캡처용).</summary>
|
||||||
|
public static void SimulateForProbe(float seconds)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < s_live.Count; i++)
|
||||||
|
{
|
||||||
|
var s = s_live[i];
|
||||||
|
if (s == null || s.ps == null) continue;
|
||||||
|
for (int k = 0; k < s.ps.Length; k++)
|
||||||
|
{
|
||||||
|
if (s.ps[k] == null) continue;
|
||||||
|
s.ps[k].Clear(true);
|
||||||
|
s.ps[k].Simulate(seconds, true, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프로브용: 풀까지 전부 파괴하고 카운터 초기화.</summary>
|
||||||
|
public static void HardResetForProbe()
|
||||||
|
{
|
||||||
|
s_live.Clear();
|
||||||
|
DestroyPool(s_ring); DestroyPool(s_props); DestroyPool(s_fires);
|
||||||
|
if (s_root != null)
|
||||||
|
{
|
||||||
|
if (Application.isPlaying) Object.Destroy(s_root.gameObject); else Object.DestroyImmediate(s_root.gameObject);
|
||||||
|
s_root = null;
|
||||||
|
}
|
||||||
|
s_current = StageBarrierStyle.None;
|
||||||
|
StyleOverride = (StageBarrierStyle)(-1);
|
||||||
|
s_showing = false; s_t = 0f;
|
||||||
|
Instantiates = ActiveInstances = PooledInstances = StyleSwitches = Shows = Hides = 0;
|
||||||
|
PropCount = FireCount = FireParticleEstimate = FirePerInstanceEstimate = 0;
|
||||||
|
PropSpacingUsed = 0f; FireRateMul = 1f; RingScale = 0f; LastBuildMs = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DestroyPool(List<Slot> pool)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < pool.Count; i++)
|
||||||
|
{
|
||||||
|
var s = pool[i];
|
||||||
|
if (s == null || s.go == null) continue;
|
||||||
|
if (Application.isPlaying) Object.Destroy(s.go); else Object.DestroyImmediate(s.go);
|
||||||
|
}
|
||||||
|
pool.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프로브·캡처용: 그림 루트(임시 씬에서 카메라 프레이밍에 쓴다).</summary>
|
||||||
|
public static Transform Root { get { return s_root; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 62bdc4832e9738745a6ad7e320f975cb
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// WLStageBarrierSettings.cs — 스테이지 장벽 값의 단일 출처(SOT · C45)
|
||||||
|
//
|
||||||
|
// PD 지시 #815 「스테이지에 들어가면 고정 영역(장벽) 안에서만 싸운다」
|
||||||
|
// + PD 질문(2026-09-11 00:3x) 「지역을 어떻게 구역으로 막을 것인지 — 울타리 오브젝트 · 불꽃 이펙트 등 대안」
|
||||||
|
// 발주서 WL-815c · SOT = 기준서 「스테이지_전환_기준서_v1.md」 §D
|
||||||
|
//
|
||||||
|
// ■ PD 가 값 하나만 바꿔서 3가지 「보이는 장벽」을 비교한다 → style 필드
|
||||||
|
// 0 없음 · 1 마법진 링(Hovl Marker) · 2 프롭 링(울타리/바위) · 3 불꽃 링
|
||||||
|
//
|
||||||
|
// ■ 막는 방법은 기준서 §D-2 실측 결론 그대로 = ③ 거리 판정 + 워프백(신규 코드 0 에 가깝게 재사용).
|
||||||
|
// ① 물리 콜라이더 벽은 **불가**(PC 캡슐 IsTrigger · Rigidbody Constraints 126).
|
||||||
|
// ② NavMesh 카브는 가능하지만 런타임 재베이크 비용이 있어 **옵션(기본 off)** 으로 둔다.
|
||||||
|
//
|
||||||
|
// 에셋 경로(고정): Assets/WL/Combat/Stage/Barrier/Resources/WL/WLStageBarrierSettings.asset
|
||||||
|
// → Resources.Load<WLStageBarrierSettings>("WL/WLStageBarrierSettings")
|
||||||
|
// (WLCameraFramingSettings 가 같은 방식으로 기능 폴더 안 Resources 를 쓴다 — 815b 의 Stage/*.cs 와 파일 충돌 0)
|
||||||
|
//
|
||||||
|
// ■ C8 롤백: ① 에셋 없음 / ② enabled = 0 / ③ RuntimeDisabled = true → Set() 이 아무것도 하지 않는다(오브젝트 0).
|
||||||
|
// 축별 off = blockEnabled 0(워프백만 끔) · showVisual 0(그림만 끔) · carveNavMesh 0(카브만 끔).
|
||||||
|
//
|
||||||
|
// 🔴 아래 기본값은 에셋 생성 시 초기값이다. 실제로 읽히는 값은 항상 .asset 이고 코드에 게임 수치 상수는 없다(C45).
|
||||||
|
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(Actor/MobActor 가 Assembly-CSharp 에 있다).
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace WL.Combat.Stage
|
||||||
|
{
|
||||||
|
/// <summary>장벽을 「보이게」 하는 방식. PD 가 이 값 하나로 3안을 바꿔 본다.</summary>
|
||||||
|
public enum StageBarrierStyle
|
||||||
|
{
|
||||||
|
/// <summary>그림 없음 — 판정만(워프백). 비용 0.</summary>
|
||||||
|
None = 0,
|
||||||
|
/// <summary>ⓐ 마법진 링 — Hovl Marker 프리팹 1개를 반경에 맞게 스케일.</summary>
|
||||||
|
MagicRing = 1,
|
||||||
|
/// <summary>ⓑ 프롭 링 — 울타리·바위 프리팹을 둘레에 일정 간격 배치.</summary>
|
||||||
|
Props = 2,
|
||||||
|
/// <summary>ⓒ 불꽃 링 — 불꽃 파티클 프리팹을 둘레에 배치(총량 상한).</summary>
|
||||||
|
Fire = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>불꽃 총량 상한을 어떻게 지키는가.</summary>
|
||||||
|
public enum FireBudgetMode
|
||||||
|
{
|
||||||
|
/// <summary>개수는 유지하고 인스턴스마다 방출률을 낮춘다(링이 끊기지 않는다 · 권장).</summary>
|
||||||
|
ReduceRate = 0,
|
||||||
|
/// <summary>방출률은 유지하고 인스턴스 개수를 줄인다(불꽃 하나하나가 진하다 · 간격이 벌어진다).</summary>
|
||||||
|
ReduceCount = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
[CreateAssetMenu(fileName = "WLStageBarrierSettings", menuName = "WL/Stage Barrier Settings", order = 333)]
|
||||||
|
public sealed class WLStageBarrierSettings : ScriptableObject
|
||||||
|
{
|
||||||
|
public const string ResourcesPath = "WL/WLStageBarrierSettings";
|
||||||
|
|
||||||
|
static WLStageBarrierSettings s_cached;
|
||||||
|
static bool s_lookupDone;
|
||||||
|
|
||||||
|
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null = 기능 전체 off.</summary>
|
||||||
|
public static WLStageBarrierSettings Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!s_lookupDone)
|
||||||
|
{
|
||||||
|
s_cached = Resources.Load<WLStageBarrierSettings>(ResourcesPath);
|
||||||
|
s_lookupDone = true;
|
||||||
|
}
|
||||||
|
return s_cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>진단·A/B 전용 런타임 스위치. 도메인 리로드로 자동 false.</summary>
|
||||||
|
public static bool RuntimeDisabled;
|
||||||
|
|
||||||
|
/// <summary>장벽 축이 살아 있는가.</summary>
|
||||||
|
public static bool Active
|
||||||
|
{
|
||||||
|
get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>테스트용: 캐시를 비워 다음 접근 때 다시 Resources.Load 한다.</summary>
|
||||||
|
public static void ClearCache() { s_cached = null; s_lookupDone = false; }
|
||||||
|
|
||||||
|
/// <summary>프로브용: 임시 SO 를 잠시 Instance 자리에 끼운다(C8 진리표 · 실에셋은 읽기만 한다).</summary>
|
||||||
|
public static void SetInstanceForProbe(WLStageBarrierSettings so) { s_cached = so; s_lookupDone = true; }
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────── 공통
|
||||||
|
[Header("공통")]
|
||||||
|
[Tooltip("0 이면 Set() 이 무동작 = 원본 동작 100% (C8 롤백).")]
|
||||||
|
public bool enabled = true;
|
||||||
|
|
||||||
|
[Tooltip("워프백·스타일 전환마다 로그 1줄.")]
|
||||||
|
public bool verboseLog;
|
||||||
|
|
||||||
|
[Tooltip("이탈 판정 주기(초). 813w3 아레나 폴링(0.25 s)과 같은 등급.")]
|
||||||
|
public float checkSeconds = 0.2f;
|
||||||
|
|
||||||
|
[Tooltip("몬스터 목록 재수집 주기(초). FindObjectsByType 는 여기서만 부른다(GC).")]
|
||||||
|
public float rescanSeconds = 3f;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────── ① 막기 = 워프백(기준서 §D-2 ③)
|
||||||
|
[Header("① 막기 — 거리 판정 + 워프백 (기준서 §D-2 ③ · 813w3/813t2 워프 API)")]
|
||||||
|
[Tooltip("0 이면 판정·워프백을 끈다(그림만 남는다).")]
|
||||||
|
public bool blockEnabled = true;
|
||||||
|
|
||||||
|
[Tooltip("반경 + 이 여유(m) 밖이어야 「나갔다」고 본다. 813d playerLeaveMargin(4)보다 좁게 잡아 장벽 그림 밖으로 크게 벗어나지 않게 한다.")]
|
||||||
|
public float leaveMargin = 2f;
|
||||||
|
|
||||||
|
[Tooltip("밖에 이만큼(초) 넘게 있어야 되돌린다. 넉백·대시로 잠깐 스치는 것은 흘려보낸다.")]
|
||||||
|
public float graceSeconds = 0.5f;
|
||||||
|
|
||||||
|
[Tooltip("같은 대상을 다시 되돌리기까지의 최소 간격(초).")]
|
||||||
|
public float warpCooldownSeconds = 1f;
|
||||||
|
|
||||||
|
[Tooltip("되돌릴 지점을 경계에서 안쪽으로 이만큼(m) 들여놓는다. 0 이면 경계에 딱 붙어 바로 다시 나간다.")]
|
||||||
|
public float warpInsetMeters = 1.5f;
|
||||||
|
|
||||||
|
[Tooltip("되돌릴 지점을 NavMesh 위로 스냅할 1차 반경(m). PC 는 Get_NavSnapRadius()=0 이라 Set_Warp 이 스스로 스냅하지 않는다(813w3 실측).")]
|
||||||
|
public float warpNavSampleRadius = 4f;
|
||||||
|
|
||||||
|
[Tooltip("1차에서 못 찾으면 쓰는 2차 반경(m). 그래도 못 찾으면 워프하지 않는다(지형 뚫기 방지 · 813w3 규칙).")]
|
||||||
|
public float warpNavSampleRadiusFar = 20f;
|
||||||
|
|
||||||
|
[Tooltip("플레이어를 가둔다.")]
|
||||||
|
public bool blockPlayer = true;
|
||||||
|
|
||||||
|
[Tooltip("몬스터도 가둔다(밖으로 밀린 몹 복귀 · 813x MobNavRecovery 와 같은 뿌리).")]
|
||||||
|
public bool blockMobs = true;
|
||||||
|
|
||||||
|
[Tooltip("몬스터용 여유(m). PC 보다 넉넉하게 — 몹은 넉백으로 자주 밀린다.")]
|
||||||
|
public float mobLeaveMargin = 3f;
|
||||||
|
|
||||||
|
[Tooltip("몬스터용 유예(초).")]
|
||||||
|
public float mobGraceSeconds = 1f;
|
||||||
|
|
||||||
|
[Tooltip("몬스터용 쿨다운(초).")]
|
||||||
|
public float mobWarpCooldownSeconds = 2f;
|
||||||
|
|
||||||
|
[Tooltip("플레이어를 되돌린 직후 카메라 이동 리드를 0 으로 스냅한다(813t2 방식 · RealCamera 무수정).")]
|
||||||
|
public bool warpSnapsCameraLead = true;
|
||||||
|
|
||||||
|
[Tooltip("되돌리기 전에 남은 NavMesh 경로를 지운다(원본이 밖으로 다시 끌고 가지 않게).")]
|
||||||
|
public bool warpResetsPath = true;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────── ② NavMesh 카브(옵션 · 기본 off)
|
||||||
|
[Header("② NavMesh 카브 (옵션 · 기본 off · 기준서 §D-2 ②)")]
|
||||||
|
[Tooltip("1 이면 링 둘레에 NavMeshObstacle(carve) 세그먼트를 런타임 생성한다 = 몹이 경로로는 못 나간다. " +
|
||||||
|
"재베이크 비용이 있어 기본 0. 원본 NavMesh 에셋·프리팹은 건드리지 않는다(런타임 오브젝트만).")]
|
||||||
|
public bool carveNavMesh;
|
||||||
|
|
||||||
|
[Tooltip("세그먼트 1개가 덮는 둘레 길이(m). 둘레 ÷ 이 값 = 세그먼트 수.")]
|
||||||
|
public float carveSegmentArcMeters = 4f;
|
||||||
|
|
||||||
|
[Tooltip("세그먼트 수 상한. 반경이 커도 이 수를 넘기지 않는다(재베이크 비용 방어).")]
|
||||||
|
public int maxCarveSegments = 32;
|
||||||
|
|
||||||
|
[Tooltip("세그먼트 상자의 두께(m · 반경 방향). 얇으면 몹이 뚫고 지나갈 수 있다.")]
|
||||||
|
public float carveThickness = 1.5f;
|
||||||
|
|
||||||
|
[Tooltip("세그먼트 상자의 높이(m).")]
|
||||||
|
public float carveHeight = 3f;
|
||||||
|
|
||||||
|
[Tooltip("이웃 세그먼트가 겹치는 비율(0.15 = 15 % 겹침). 0 이면 모서리에 틈이 생긴다.")]
|
||||||
|
public float carveOverlap = 0.15f;
|
||||||
|
|
||||||
|
[Tooltip("carveOnlyMovementThreshold — 장벽은 움직이지 않으므로 1 이면 정적 카브(재베이크 1회). 0 이면 매 프레임 카브 검사.")]
|
||||||
|
public bool carveStaticOnly = true;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────── ③ 보이기 — 공통
|
||||||
|
[Header("③ 보이기 — 스타일 (🔴 PD 는 이 값 하나만 바꾼다)")]
|
||||||
|
[Tooltip("0 없음 · 1 마법진 링 · 2 프롭 링(울타리·바위) · 3 불꽃 링")]
|
||||||
|
public StageBarrierStyle style = StageBarrierStyle.MagicRing;
|
||||||
|
|
||||||
|
[Tooltip("0 이면 그림을 전부 끈다(판정만 남는다).")]
|
||||||
|
public bool showVisual = true;
|
||||||
|
|
||||||
|
[Tooltip("스테이지 입장 때 장벽이 나타나는 시간(초).")]
|
||||||
|
public float appearSeconds = 0.6f;
|
||||||
|
|
||||||
|
[Tooltip("클리어(장벽이 열릴 때) 사라지는 시간(초).")]
|
||||||
|
public float fadeSeconds = 0.6f;
|
||||||
|
|
||||||
|
[Tooltip("그림을 지면 위 이만큼(m) 띄운다(Z-파이팅 방지).")]
|
||||||
|
public float visualYOffset = 0.05f;
|
||||||
|
|
||||||
|
[Tooltip("그림 반경 = 판정 반경 × 이 배율. 1 보다 살짝 크게 두면 「선 안쪽에서 싸운다」로 읽힌다.")]
|
||||||
|
public float visualRadiusScale = 1f;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────── ⓐ 마법진 링
|
||||||
|
[Header("ⓐ 마법진 링 (Hovl Map track markers · 기준서 §D-3 1순위)")]
|
||||||
|
[Tooltip("링 프리팹. 기본 = Marker 3 Zone Loop.")]
|
||||||
|
public GameObject ringPrefab;
|
||||||
|
|
||||||
|
[Tooltip("🔴 실측값 — 스케일 1 일 때 이 프리팹의 시각 반경(m). 스케일 = 목표반경 ÷ 이 값. " +
|
||||||
|
"Marker 3 Zone Loop 실측 1.10 m(Simulate 2 s · 파티클 위치+반크기 최대).")]
|
||||||
|
public float ringPrefabVisualRadius = 1.1f;
|
||||||
|
|
||||||
|
[Tooltip("링 색(파티클 startColor 에 인스턴스 단위로 대입 · 원본 머티리얼·프리팹 무수정). a 는 밝기.")]
|
||||||
|
public Color ringColor = new Color(1f, 0.35f, 0.15f, 1f);
|
||||||
|
|
||||||
|
[Tooltip("0 이면 색을 건드리지 않고 프리팹 원색 그대로 쓴다.")]
|
||||||
|
public bool ringTintEnabled = true;
|
||||||
|
|
||||||
|
[Tooltip("도트 룩(814b/814c)에서 링이 뭉개지지 않게 세로로 늘리는 배율. 1 = 프리팹 그대로. " +
|
||||||
|
"🔴 링은 평면 쿼드라 Y 스케일은 두께가 아니라 「기울기」를 만든다 — 두께는 ringExtraRings 로 낸다.")]
|
||||||
|
public float ringYScale = 1f;
|
||||||
|
|
||||||
|
[Tooltip("링을 반경을 조금씩 달리해 몇 겹 더 깐다(0 = 1겹). 도트 룩에서 1px 로 뭉개지는 것을 막는 가장 싼 방법. " +
|
||||||
|
"겹당 인스턴스 1개 = 파티클 4개(실측).")]
|
||||||
|
public int ringExtraRings = 1;
|
||||||
|
|
||||||
|
[Tooltip("추가 겹의 반경 차이(m).")]
|
||||||
|
public float ringExtraSpacing = 0.6f;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────── ⓑ 프롭 링
|
||||||
|
[Header("ⓑ 프롭 링 (울타리 · 바위 · 기둥)")]
|
||||||
|
[Tooltip("둘레에 놓을 프롭 프리팹 목록. 여러 개면 번갈아 쓴다(회전 랜덤과 함께 반복감을 줄인다).")]
|
||||||
|
public GameObject[] propPrefabs = new GameObject[0];
|
||||||
|
|
||||||
|
[Tooltip("프롭 간격(m). 울타리는 프롭 길이(Fence_01 실측 2.38 m)보다 살짝 좁게 둬야 틈이 안 보인다.")]
|
||||||
|
public float propSpacing = 2.5f;
|
||||||
|
|
||||||
|
[Tooltip("프롭 개수 상한. 둘레 ÷ 간격 이 이 수를 넘으면 간격을 자동으로 벌린다(개수 고정).")]
|
||||||
|
public int maxProps = 64;
|
||||||
|
|
||||||
|
[Tooltip("프롭 스케일.")]
|
||||||
|
public float propScale = 1f;
|
||||||
|
|
||||||
|
[Tooltip("1 이면 프롭의 정면(+X = 실측 긴축)이 둘레의 접선을 향한다 = 울타리가 벽처럼 이어진다. " +
|
||||||
|
"0 이면 회전 없음(바위처럼 방향이 없는 프롭용).")]
|
||||||
|
public bool propAlignToTangent = true;
|
||||||
|
|
||||||
|
[Tooltip("접선 정렬 뒤 추가로 흔들 각도(±도). 바위·나무는 크게(180), 울타리는 작게(0~8).")]
|
||||||
|
public float propYawJitter = 6f;
|
||||||
|
|
||||||
|
[Tooltip("반경 방향 흔들기(±m). 0 이면 정확히 원 위.")]
|
||||||
|
public float propRadialJitter = 0.2f;
|
||||||
|
|
||||||
|
[Tooltip("1 이면 Raycast 로 지면 높이에 스냅한다(WL_Nature 는 평지가 아니다).")]
|
||||||
|
public bool propGroundSnap = true;
|
||||||
|
|
||||||
|
[Tooltip("지면 Raycast 시작 높이(m · 장벽 중심 y 기준 위쪽).")]
|
||||||
|
public float propRayUp = 50f;
|
||||||
|
|
||||||
|
[Tooltip("지면 Raycast 길이(m).")]
|
||||||
|
public float propRayLength = 200f;
|
||||||
|
|
||||||
|
[Tooltip("1 이면 인스턴스의 Collider 를 전부 제거한다. 프롭 원본(LMHPOLY With_Box_Colliders 등)에 붙은 콜라이더가 " +
|
||||||
|
"카메라 가림 페이드(811 CameraFramingOverride)·투사체와 엉키지 않게 한다. 원본 프리팹은 무수정.")]
|
||||||
|
public bool propStripColliders = true;
|
||||||
|
|
||||||
|
[Tooltip("랜덤 시드. 같은 시드 = 같은 배치(캡처 재현용).")]
|
||||||
|
public int propSeed = 815;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────── ⓒ 불꽃 링
|
||||||
|
[Header("ⓒ 불꽃 링 (보유 불꽃 파티클)")]
|
||||||
|
[Tooltip("둘레에 놓을 불꽃 프리팹 목록. 기본 = LMHPOLY Fire_Particles(실측 3 PS · 정상상태 125 파티클).")]
|
||||||
|
public GameObject[] firePrefabs = new GameObject[0];
|
||||||
|
|
||||||
|
[Tooltip("불꽃 간격(m).")]
|
||||||
|
public float fireSpacing = 3f;
|
||||||
|
|
||||||
|
[Tooltip("불꽃 개수 상한.")]
|
||||||
|
public int maxFires = 48;
|
||||||
|
|
||||||
|
[Tooltip("불꽃 스케일.")]
|
||||||
|
public float fireScale = 1f;
|
||||||
|
|
||||||
|
[Tooltip("🔴 링 전체의 파티클 총량 상한. 811s EffectScaleGuard/811q 예산과 부딪히지 않게 이 선에서 자른다. " +
|
||||||
|
"실측 = Fire_Particles 1개당 정상상태 125 → 상한 1200 이면 인스턴스 29개에서 방출률 ×0.33.")]
|
||||||
|
public int maxTotalParticles = 1200;
|
||||||
|
|
||||||
|
[Tooltip("총량 상한을 지키는 방법. ReduceRate(0) = 개수 유지 · 방출률 축소(권장) · ReduceCount(1) = 개수 축소.")]
|
||||||
|
public FireBudgetMode fireBudgetMode = FireBudgetMode.ReduceRate;
|
||||||
|
|
||||||
|
[Tooltip("불꽃 색(파티클 startColor · 0 이면 프리팹 원색).")]
|
||||||
|
public bool fireTintEnabled;
|
||||||
|
|
||||||
|
[Tooltip("불꽃 색.")]
|
||||||
|
public Color fireColor = new Color(1f, 0.45f, 0.1f, 1f);
|
||||||
|
|
||||||
|
[Tooltip("1 이면 Raycast 로 지면 높이에 스냅한다.")]
|
||||||
|
public bool fireGroundSnap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3c3dc9bd8de148744b88ce512ce0f09a
|
||||||
Loading…
Reference in New Issue