165 lines
9.1 KiB
C#
165 lines
9.1 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLCollisionTuning.cs — PD 지시 #768/#769 런타임 적용부
|
|
//
|
|
// PD 지시 #768 (2026-09-06): "플레이어와 몬스터의 충돌범위가 너무 넓은 것 같아.
|
|
// (좁게 만들어서 대부분 지나갈 수 있도록 해야 해. (단, 보스 몬스터는 몬스터 크기를 고려해 크게 잡아도 됨)"
|
|
// PD 지시 #769 (2026-09-06): "몬스터 투사체 크기를 지금 크기에서 70% 줄여줘 (작게)"
|
|
//
|
|
// 설계:
|
|
// - 값은 전부 WLGameplaySettings 에셋(데이터)에서 읽는다(C45). 이 파일에는 게임 수치 상수가 없다.
|
|
// - 프리팹 46(PC) + 299(몹) + 145(투사체)개를 개별 편집하지 않고 **런타임 1지점**에서 적용한다.
|
|
// → 롤백 = 에셋의 체크박스 하나(agentTuningEnabled / projectile 배율 1.0). 코드 되돌리기·재빌드 불필요(C8).
|
|
// - 오브젝트 풀 재사용 시 값이 누적되지 않도록 **항상 원본(프리팹) 값에서 다시 계산**한다.
|
|
//
|
|
// 🔴 어셈블리 주의: Assets/Script/ 에는 .asmdef 가 없다(전부 Assembly-CSharp).
|
|
// 이 폴더에도 .asmdef 를 만들지 말 것.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
namespace WL.Settings
|
|
{
|
|
/// <summary>
|
|
/// NavMeshAgent(이동 차단 반경·회피)와 몬스터 투사체 크기의 런타임 적용부.
|
|
/// 호출 지점 = PCActor.Set / MobActor.Set(MonsterTableData…) / ProjectileBase.Set.
|
|
/// </summary>
|
|
public static class WLCollisionTuning
|
|
{
|
|
// 인스턴스별 원본 반경 캐시. 풀에서 재사용된 몹에 배율이 누적되는 것을 막는다.
|
|
static readonly Dictionary<int, float> s_originRadius = new Dictionary<int, float>(512);
|
|
// 투사체 프리팹 인스턴스별 "렌더러가 있는가"(= 눈에 보이는 투사체인가) 캐시.
|
|
static readonly Dictionary<int, bool> s_hasRenderer = new Dictionary<int, bool>(256);
|
|
|
|
/// <summary>이 에이전트의 원본(프리팹) 반경. 처음 보는 에이전트면 현재 값을 원본으로 기록한다.</summary>
|
|
public static float OriginRadius(NavMeshAgent agent)
|
|
{
|
|
if (agent == null) return 0f;
|
|
int id = agent.GetInstanceID();
|
|
float r;
|
|
if (s_originRadius.TryGetValue(id, out r)) return r;
|
|
if (s_originRadius.Count > 8192) s_originRadius.Clear(); // 씬 전환 누적 방지
|
|
r = agent.radius;
|
|
s_originRadius[id] = r;
|
|
return r;
|
|
}
|
|
|
|
/// <summary>플레이어(PC)에 적용. 펫·NPC 는 호출하지 않는다(PetActor 는 PCActor 를 상속하지 않는다).</summary>
|
|
public static void ApplyPC(NavMeshAgent agent)
|
|
{
|
|
var s = WLGameplaySettings.Instance;
|
|
if (agent == null || s == null || !s.agentTuningEnabled) return;
|
|
|
|
float origin = OriginRadius(agent);
|
|
agent.radius = s.pcAgentRadius > 0f ? s.pcAgentRadius : origin;
|
|
if (s.pcAvoidancePriority >= 0) agent.avoidancePriority = Mathf.Clamp(s.pcAvoidancePriority, 0, 99);
|
|
if ((int)s.pcAvoidance >= 0) agent.obstacleAvoidanceType = (ObstacleAvoidanceType)(int)s.pcAvoidance;
|
|
}
|
|
|
|
/// <summary>몬스터에 적용. subRole = MonsterList 테이블의 e_MonsterType(None/Elite/Boss).</summary>
|
|
public static void ApplyMob(NavMeshAgent agent, eSubRol subRole)
|
|
{
|
|
var s = WLGameplaySettings.Instance;
|
|
if (agent == null || s == null) return;
|
|
|
|
// #816x — 몸 크기(PD "기본적인 몬스터의 크기도 50% 키워줘"). 에이전트 튜닝 스위치와 독립이다.
|
|
ApplyMobBodyScale(agent.transform, subRole, s);
|
|
|
|
if (!s.agentTuningEnabled) return;
|
|
|
|
bool isBoss = subRole == eSubRol.Boss;
|
|
bool isElite = subRole == eSubRol.Elite;
|
|
|
|
float origin = OriginRadius(agent);
|
|
float scale = isBoss ? s.bossAgentRadiusScale : isElite ? s.eliteAgentRadiusScale : s.mobAgentRadiusScale;
|
|
float r = origin * (scale > 0f ? scale : 1f);
|
|
if (!isBoss && s.mobAgentRadiusMin > 0f && r < s.mobAgentRadiusMin) r = s.mobAgentRadiusMin;
|
|
agent.radius = r;
|
|
|
|
// 보스는 PD 지시대로 "크게" 유지 — 반경 배율(기본 1.0) 외에는 손대지 않는다.
|
|
if (isBoss && !s.tuneBossAgent) return;
|
|
|
|
if (s.mobAvoidancePriority >= 0) agent.avoidancePriority = Mathf.Clamp(s.mobAvoidancePriority, 0, 99);
|
|
// 회피 품질 오버라이드는 일반 몹(None)만. 정예·보스는 원본 유지(덩치가 있으면 막아야 한다).
|
|
if (!isElite && !isBoss && (int)s.mobAvoidance >= 0)
|
|
agent.obstacleAvoidanceType = (ObstacleAvoidanceType)(int)s.mobAvoidance;
|
|
}
|
|
|
|
// ── #816x 몬스터 몸 크기 ────────────────────────────────────────────────
|
|
/// <summary>몸 크기를 적용한 누적 횟수 · 보스 경계에서 잘린 횟수 · 마지막 값(검증용 읽기 전용).</summary>
|
|
public static int BodyScaleApplied, BodyScaleClamped;
|
|
public static float LastBodyScaleBase, LastBodyScaleApplied;
|
|
|
|
/// <summary>
|
|
/// 몬스터 루트 스케일에 타입별 배율을 곱한다(#816x · PD "기본적인 몬스터의 크기도 50% 키워줘").
|
|
///
|
|
/// 호출 시점 = MobActor.Set 안, `transform.localScale = f_DefaultScale * Vector3.one`(:87) **직후**(:118).
|
|
/// 따라서 base 는 언제나 테이블 기본값이고, 풀에서 재사용돼도 다시 기본값에서 곱한다 = **누적 없음**.
|
|
/// 루트 스케일이므로 자식(콜라이더·tf_Top·HP 바)은 함께 커지고,
|
|
/// NavMeshAgent.radius(월드 단위 · 바로 아래에서 별도 계산) 와 데이터 값(이동 속도 · f_BaseATKRange)은 **그대로다**.
|
|
/// </summary>
|
|
public static void ApplyMobBodyScale(Transform root, eSubRol subRole, WLGameplaySettings s)
|
|
{
|
|
if (root == null || s == null || !s.mobBodyScaleEnabled) return;
|
|
|
|
float m = subRole == eSubRol.Boss ? s.bossBodyScale
|
|
: subRole == eSubRol.Elite ? s.eliteBodyScale
|
|
: s.mobBodyScale;
|
|
if (m <= 0f || Mathf.Abs(m - 1f) < 0.0001f) return;
|
|
|
|
float bas = root.localScale.x;
|
|
if (bas <= 0.0001f) return;
|
|
|
|
float want = bas * m;
|
|
|
|
// 원본이 localScale.x > 2 를 '보스급' 판정에 쓴다(ProjectileBase:305/308 · Actor:950).
|
|
// 스위치가 켜져 있을 때만 그 경계를 넘지 않게 자른다(기본은 꺼짐 = PD 지시 그대로 ×배율).
|
|
float th = s.bodyScaleBossThreshold;
|
|
if (s.mobBodyScaleKeepUnderBossThreshold && th > 0f && bas <= th && want > th)
|
|
{
|
|
want = th - Mathf.Max(s.bodyScaleThresholdEpsilon, 0f);
|
|
BodyScaleClamped++;
|
|
}
|
|
|
|
root.localScale = Vector3.one * want;
|
|
BodyScaleApplied++;
|
|
LastBodyScaleBase = bas;
|
|
LastBodyScaleApplied = want;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이 투사체에 곱할 크기 배율. 1 이면 변경하지 않는다.
|
|
/// 플레이어·펫 투사체는 항상 1(무변경) — 몬스터(eRole.Mob)가 쏜 것만 대상.
|
|
/// </summary>
|
|
public static float ProjectileScale(Actor shooter, GameObject projectile)
|
|
{
|
|
var s = WLGameplaySettings.Instance;
|
|
if (s == null || DSUtil.CheckNull(shooter)) return 1f;
|
|
if (!shooter.IsRole(eRole.Mob)) return 1f;
|
|
|
|
float mult = shooter.IsSubRole(eSubRol.Boss) ? s.bossProjectileScale
|
|
: shooter.IsSubRole(eSubRol.Elite) ? s.eliteProjectileScale
|
|
: s.mobProjectileScale;
|
|
if (mult <= 0f || mult == 1f) return 1f;
|
|
|
|
// 렌더러가 없는 프리팹 = 눈에 보이지 않는 순수 타격 판정 박스(근접 공격 등).
|
|
// 이걸 줄이면 "투사체가 작아지는" 효과는 없고 몹의 공격 사거리만 줄어든다 → 기본은 제외.
|
|
if (s.projectileScaleVisibleOnly && !HasRenderer(projectile)) return 1f;
|
|
return mult;
|
|
}
|
|
|
|
static bool HasRenderer(GameObject go)
|
|
{
|
|
if (go == null) return true;
|
|
int id = go.GetInstanceID();
|
|
bool v;
|
|
if (s_hasRenderer.TryGetValue(id, out v)) return v;
|
|
if (s_hasRenderer.Count > 4096) s_hasRenderer.Clear();
|
|
v = go.GetComponentInChildren<Renderer>(true) != null;
|
|
s_hasRenderer[id] = v;
|
|
return v;
|
|
}
|
|
}
|
|
}
|