Project_WL/Assets/WL/Character/WLPcScaleCompensator.cs

95 lines
5.5 KiB
C#
Raw Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLPcScaleCompensator.cs — 치비 PC 프리팹의 월드 높이를 Ai01 과 똑같이 유지한다
//
// 발주서 WL-814n §1-4 「f_Scale 재계산으로 월드 높이 1.191 m 유지」
//
// ■ 문제
// 원본 `PCActor.Set_Obj()` 가 루트 스케일을 **대입**한다(원본 `PCActor.cs:318-325` 실측):
// 로비 : transform.localScale = Vector3.one → 1.0
// 인게임 : transform.localScale = m_ClassData.f_Scale → 0.7 (ClassConfig 12행 전부)
// M05 원 높이는 1.1316 m 라 그대로 두면 인게임 0.792 m · 로비 1.132 m 로 작아진다.
//
// ■ 왜 여기(프리팹 컴포넌트)에서 고치나 — 되돌리기(C8)와 원본 1줄 상한 때문이다
// · 테이블 `ClassConfig.f_Scale` 을 바꾸면 스위치를 껐을 때 Ai01 까지 1.79 m 가 된다(원본 100 % 깨짐).
// · `PCActor.cs:324` 에 훅을 하나 더 넣으면 원본 수정이 2줄이 되어 발주서 상한(1줄)을 넘는다.
// · 이 컴포넌트는 **새 프리팹 `LH_M05` 에만** 붙는다 → Ai01 경로는 코드·데이터 모두 무변경.
//
// ■ 어떻게 (멱등 · 누적 곱 아님)
// 원본이 루트 스케일에 새 값을 대입하면(로비 1.0 / 인게임 0.7) LateUpdate 가 그것을 **원 값**으로 보고
// `원 값 × heightCompensation` 을 다시 대입한다. 자기가 쓴 값은 기억해 두고 건너뛰므로 매 프레임 곱해지지 않는다.
// 스케일은 애니메이션이 건드리지 않으므로(Humanoid 는 위치·회전만 쓴다) LateUpdate 대입으로 충분하다.
//
// ■ 실측 결과 (AgentScripts/staging/WL814n/)
// 1.1316 m × 1.5038 = 1.7017 m(= Ai01 원 높이) → × 0.7 = **1.1912 m**(811s 실측 PC 키와 동일)
//
// 🔴 루트 스케일을 쓰는 이유: Hips 가 `Root_M` = 루트의 직속 자식이라, Hips 이하를 스케일하면
// Humanoid 리타깃이 매 프레임 쓰는 Hips 위치와 어긋나 발이 땅을 뚫는다(PROBE2 실측).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
namespace WL.Character
{
[DisallowMultipleComponent]
public sealed class WLPcScaleCompensator : MonoBehaviour
{
[Tooltip("비워 두면 WLCharacterSwapSettings.heightCompensation 을 쓴다. 0 보다 크면 이 값이 우선(프리팹 단위 예외용).")]
public float overrideCompensation;
float _lastWritten = float.NaN;
// WL-822(2026-10-09 PD 「플레이어 크기가 자꾸 커진다」): 원본이 쓰는 값은 두 가지뿐(로비 1.0 · 인게임 f_Scale)이다.
// 그 밖의 변화(피격·레벨업 스케일 펀치 등 **일시 연출**)까지 「원본이 새로 썼다」로 보고 ×k 를 또 곱하니
// 펀치마다 누적 확대됐다(1.05 → 1.64 → 3.96 실측). 이제 **아는 원본 값**일 때만 보정한다.
// 구분 기준 = **안정성**: 원본 대입(1.0 / f_Scale)은 그 값이 그대로 머물고, 연출 펀치는 매 프레임(사인 곡선) 값이 바뀐다.
// → 같은 값이 stableFrames 프레임 연속이면 원본 값으로 보고 보정, 아니면 무시.
[Tooltip("원본 값으로 인정하기까지 같은 값이 유지돼야 하는 LateUpdate 수")]
public int stableFrames = 3;
float _pendingValue = float.NaN;
int _pendingFrames;
public static int Ignored, Applied;
/// <summary>지금 적용 중인 보정 배수. 스위치가 꺼져 있으면 1.</summary>
public float Compensation
{
get
{
if (overrideCompensation > 0f) return overrideCompensation;
return WLCharacterSwapSettings.HeightCompensation;
}
}
void OnEnable() { _lastWritten = float.NaN; Apply(); }
void LateUpdate() { Apply(); }
void Apply()
{
float k = Compensation;
if (k <= 0f) return;
float cur = transform.localScale.x;
// 자기가 마지막으로 쓴 값이면 원본이 아직 안 건드린 것 = 할 일 없음(멱등).
if (!float.IsNaN(_lastWritten) && Mathf.Abs(cur - _lastWritten) <= 1e-5f) return;
// 첫 적용은 즉시(OnEnable 시점 값 = 원본 대입). 이후엔 같은 값이 stableFrames 연속일 때만 원본 대입으로 인정한다.
if (!float.IsNaN(_lastWritten))
{
if (float.IsNaN(_pendingValue) || Mathf.Abs(cur - _pendingValue) > 1e-5f) { _pendingValue = cur; _pendingFrames = 1; Ignored++; return; }
_pendingFrames++;
if (_pendingFrames < Mathf.Max(1, stableFrames)) { Ignored++; return; }
}
_pendingValue = float.NaN; _pendingFrames = 0;
float target = cur * k;
Applied++;
_lastWritten = target;
transform.localScale = new Vector3(target, target, target);
var s = WLCharacterSwapSettings.Instance;
if (s != null && s.verboseLog)
Debug.Log(string.Format("[WL-814n] PC 크기 보정: {0:F4} × {1:F4} = {2:F4} ({3})", cur, k, target, name));
}
}
}