143 lines
8.0 KiB
C#
143 lines
8.0 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLTerrainGuardSettings.cs — 언덕(이동 불가 지형) 가드 값의 단일 출처(SOT · C45)
|
|
//
|
|
// PD 지시 #813 (2026-09-10) · 발주서 WL-813t2 §1-3 · §1-4
|
|
// 「언덕 지형은 플레이어나 몬스터가 갈 수 없도록 · 언덕을 뚫고 가는 상황 및 카메라 연출 꼬임 ·
|
|
// 이동 불가 지역에는 몬스터가 생성되어서도 안 됨」
|
|
// 근거 = QA W2-12 D-48(PC 가 아레나 밖 고지대 y 3.66 → 5.48 로 올라가 보스를 한 대도 못 때리고 시간 종료)
|
|
//
|
|
// 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLTerrainGuardSettings.asset
|
|
// → Resources.Load<WLTerrainGuardSettings>("WL/WLTerrainGuardSettings") 로 1회 로드 후 캐시
|
|
// (WLCombatCoreSettings·WLBossArenaSettings 등 기존 설정 SO 관례 그대로)
|
|
//
|
|
// ■ C8 롤백
|
|
// ① 에셋이 없다 / ② enabled = 0 / ③ RuntimeDisabled = true → 러너 자체가 뜨지 않는다(오브젝트 0).
|
|
// 축별로도 끌 수 있다 — terrainGuardEnabled = 0(가드만 off) · spawnGuardEnabled = 0(스폰 보정만 off).
|
|
// 🔴 코어 스위치(WLCombatCoreSettings.Enabled)에 의존하지 않는다 — MobNavRecovery(813x)와 같은 이유로,
|
|
// 코어를 끈 A/B 중에도 「언덕에 올라간 채 굳는」 상태가 되살아나면 안 된다.
|
|
//
|
|
// ■ 아래 기본값은 에셋 생성 시 초기값이다. 실제로 읽히는 값은 항상 .asset 이며 코드에 수치 상수는 없다(C45).
|
|
//
|
|
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(Actor/MobActor 가 Assembly-CSharp 에 있다).
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using UnityEngine;
|
|
|
|
namespace WL.Combat.Nav
|
|
{
|
|
[CreateAssetMenu(fileName = "WLTerrainGuardSettings", menuName = "WL/Terrain Guard Settings", order = 332)]
|
|
public sealed class WLTerrainGuardSettings : ScriptableObject
|
|
{
|
|
public const string ResourcesPath = "WL/WLTerrainGuardSettings";
|
|
|
|
static WLTerrainGuardSettings s_cached;
|
|
static bool s_lookupDone;
|
|
|
|
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null = 기능 전체 off.</summary>
|
|
public static WLTerrainGuardSettings Instance
|
|
{
|
|
get
|
|
{
|
|
if (!s_lookupDone)
|
|
{
|
|
s_cached = Resources.Load<WLTerrainGuardSettings>(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(WLTerrainGuardSettings so) { s_cached = so; s_lookupDone = true; }
|
|
|
|
// ─────────────────────────────────────────────────────── 공통
|
|
[Header("공통")]
|
|
[Tooltip("0 이면 러너가 뜨지 않는다 = 원본 동작 100% (C8 롤백).")]
|
|
public bool enabled = true;
|
|
|
|
[Tooltip("복구·보정 1건마다 로그 1줄.")]
|
|
public bool verboseLog;
|
|
|
|
[Tooltip("점검 주기(초). 액터 1명당 판정 1회.")]
|
|
public float checkSeconds = 0.2f;
|
|
|
|
[Tooltip("몬스터 목록 재수집 주기(초). FindObjectsByType 는 여기서만 부른다(GC).")]
|
|
public float rescanSeconds = 3f;
|
|
|
|
// ─────────────────────────────────────────────────────── 지형 가드(§1-3)
|
|
[Header("지형 가드 — 언덕 위/NavMesh 밖 복귀")]
|
|
[Tooltip("0 이면 지형 가드만 끈다.")]
|
|
public bool terrainGuardEnabled = true;
|
|
|
|
[Tooltip("이 높이(m)를 넘는 곳에 서 있으면 언덕으로 본다. 813t2 재베이크 후 WL_Nature 의 walkable 최고 y = 2.96 m → 여유 0.5 m.")]
|
|
public float maxWalkableY = 3.5f;
|
|
|
|
[Tooltip("이 반경(m) 안에 NavMesh 가 없으면 「NavMesh 밖」으로 본다. 원본 넉백·점프의 짧은 이탈을 흘려보낼 만큼은 커야 한다.")]
|
|
public float offNavMeshTolerance = 1f;
|
|
|
|
[Tooltip("이상 상태가 이 시간(초)을 넘게 지속돼야 되돌린다. 점프·넉백 중에는 원본이 의도적으로 에이전트를 끈다.")]
|
|
public float dwellSeconds = 1f;
|
|
|
|
[Tooltip("같은 액터를 다시 되돌리기까지의 최소 간격(초).")]
|
|
public float warpCooldownSeconds = 3f;
|
|
|
|
[Tooltip("되돌릴 지점을 찾는 1차 반경(m).")]
|
|
public float warpNearRadius = 4f;
|
|
|
|
[Tooltip("1차에서 못 찾으면 쓰는 2차 반경(m).")]
|
|
public float warpFarRadius = 30f;
|
|
|
|
[Tooltip("플레이어를 가드 대상에 넣는다(D-48 의 당사자).")]
|
|
public bool guardPlayer = true;
|
|
|
|
[Tooltip("일반 몬스터를 가드 대상에 넣는다.")]
|
|
public bool guardMobs = true;
|
|
|
|
[Tooltip("보스를 가드 대상에 넣는다(Anubis 스킬2 점프).")]
|
|
public bool guardBoss = true;
|
|
|
|
[Tooltip("플레이어를 되돌린 직후 카메라 이동 리드를 0 으로 스냅한다(PD 「카메라 연출 꼬임」 · RealCamera 무수정).")]
|
|
public bool warpSnapsCameraLead = true;
|
|
|
|
[Header("높이 규칙의 적용 범위")]
|
|
[Tooltip("높이 규칙(maxWalkableY)을 적용할 맵 ID. WL_Nature = -1(BattleMapConfig 실측). " +
|
|
"다른 맵은 고지대가 정상 플레이 영역일 수 있어 높이 규칙을 적용하지 않는다(「NavMesh 밖」 규칙만 남는다).")]
|
|
public int[] heightGuardMapIds = new int[] { -1 };
|
|
|
|
[Tooltip("1 이면 맵 ID 와 무관하게 높이 규칙을 적용한다(진단용).")]
|
|
public bool heightGuardAllMaps;
|
|
|
|
[Tooltip("현재 맵 ID 를 못 읽을 때 높이 규칙을 적용할지. WL_Nature 는 로비 겸용(Load_Map(-1))이라 기본 1.")]
|
|
public bool heightGuardWhenMapUnknown = true;
|
|
|
|
// ─────────────────────────────────────────────────────── 스폰 위치 검증(§1-4)
|
|
[Header("스폰 위치 검증 — 이동 불가 지역 스폰 0")]
|
|
[Tooltip("0 이면 스폰 보정만 끈다.")]
|
|
public bool spawnGuardEnabled = true;
|
|
|
|
[Tooltip("새로 나타난 몬스터를 훑는 주기(초).")]
|
|
public float spawnCheckSeconds = 0.5f;
|
|
|
|
[Tooltip("스폰 지점이 유효한지 볼 때 쓰는 NavMesh 표집 반경(m). 원본 MapData.spawnSnapRadius(WL_Nature = 2)와 같은 뜻.")]
|
|
public float spawnSampleRadius = 2f;
|
|
|
|
[Tooltip("표집 재시도 횟수. 시도마다 반경을 1배씩 넓힌다(2 → 4 → 6 m).")]
|
|
public int spawnRetries = 3;
|
|
|
|
[Tooltip("재시도가 전부 실패하면 스포너 노드 중심으로 옮긴다(발주서 §1-4).")]
|
|
public bool spawnFallbackToSpawnerNode = true;
|
|
}
|
|
}
|