// ───────────────────────────────────────────────────────────────────────────── // WLWeaponFitSettings.cs — 치비 PC 의 무기 크기 자동 보정 단일 출처(SOT · C45) // // 발주서 WL-814p (#814 잔여 결함) · 근거 = 완료보고 WL-814n §1 ⑰ // 치비 교체 후 `Elven_Sword_01` 월드 최대변 1.341 m = 캐릭터 키(1.1912 m)의 **113 %** // → 어른 몸(Ai01 1.7017 m) 기준으로 만든 무기가 치비 손에 그대로 붙어 과대. // // ■ 어디를 고치나 — 원본 수정 0 // 원본이 무기를 다는 지점은 `Assets/Script/Character/PCActor.cs:358` // Make_Weapon(fullpath, tfs_weapon[index], Vector3.zero, Vector3.one) // = Addressables 비동기 콜백에서 소켓에 **localScale 1** 로 붙인다(실측). // 814n 에서 원본 1줄 예외를 이미 소진했으므로 훅을 더 넣지 않는다 → // **새 프리팹 `LH_M05` 에만 붙는 `WLWeaponFitter`** 가 소켓을 감시해서 보정한다. // Ai01 경로에는 이 컴포넌트가 없으므로 구조적으로 영향 0. // // ■ 되돌리기(C8) // 이 에셋의 `enabled_` 를 끄거나 에셋을 지우면 배수 1.0000 = 컴포넌트 no-op = 지금 상태 100 %. // // 에셋 경로(고정): Assets/WL/Settings/Resources/WL/WLWeaponFitSettings.asset // 🔴 Assets/WL/Character/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일). // ───────────────────────────────────────────────────────────────────────────── using System; using System.Collections.Generic; using UnityEngine; namespace WL.Character { [CreateAssetMenu(fileName = "WLWeaponFitSettings", menuName = "WL/Weapon Fit Settings", order = 332)] public sealed class WLWeaponFitSettings : ScriptableObject { public const string ResourcesPath = "WL/WLWeaponFitSettings"; static WLWeaponFitSettings s_cached; static bool s_lookupDone; /// 설정 에셋(1회 로드 후 캐시). 없으면 null = 무동작. public static WLWeaponFitSettings Instance { get { if (!s_lookupDone) { s_cached = Resources.Load(ResourcesPath); s_lookupDone = true; } return s_cached; } } /// QA·A/B 전용 런타임 스위치. 0 = 에셋의 enabled_ · 1 = 강제 on · -1 = 강제 off. public static int RuntimeOverride; /// 보정이 살아 있는가. false 면 무기 스케일에 손대지 않는다(C8). public static bool Enabled { get { var i = Instance; if (i == null || RuntimeOverride < 0) return false; return RuntimeOverride > 0 || i.enabled_; } } /// 테스트용: 캐시를 비운다. public static void ClearCache() { s_cached = null; s_lookupDone = false; } [Serializable] public sealed class WeaponRatio { [Tooltip("무기 프리팹 이름(Assets/Res_Addr/Weapon/<이름>.prefab). \"(Clone)\" 은 무시하고 비교한다.")] public string weaponName; [Tooltip("이 무기만 쓸 비율(무기 최대변 ÷ 캐릭터 키). 0 이하면 targetRatio 를 쓴다.")] public float ratio; } [Header("전체 스위치 (PD 되돌리기 1순위)")] [Tooltip("끄면 무기 스케일에 아무것도 하지 않는다 = 지금 상태 100 %. 에셋을 지워도 같다.")] public bool enabled_ = true; [Tooltip("보정이 일어날 때 Debug.Log 를 남긴다(A/B 확인용). 기본 끔.")] public bool verboseLog; [Header("목표 비율")] [Tooltip("보정 후 무기 최대변 ÷ 캐릭터 키. 0.60 = 캐릭터 키의 60 %.\n" + "치비 교체 전 어른 Ai01(1.7017 m) 기준 Elven_Sword_01 은 0.79 였다(1.341/1.7017).")] public float targetRatio = 0.60f; [Tooltip("보정 배수 하한(원본 크기 대비). 0.3 = 아무리 커도 30 % 미만으로는 줄이지 않는다.")] public float minScale = 0.3f; [Tooltip("보정 배수 상한(원본 크기 대비). 1.0 = 무기를 키우지는 않는다.")] public float maxScale = 1.0f; [Header("무기별 예외 (비어 있으면 전부 targetRatio)")] [Tooltip("이름 → 비율 override. 방패·오브처럼 길이 기준이 다른 무기를 따로 잡을 때만 쓴다.")] public List perWeapon = new List(); /// 무기 이름에 맞는 목표 비율. 스위치가 꺼져 있으면 0(= 무보정). public static float ResolveRatio(string weaponName) { var i = Instance; if (!Enabled || i == null) return 0f; if (i.perWeapon != null && !string.IsNullOrEmpty(weaponName)) { var key = Strip(weaponName); for (int n = 0; n < i.perWeapon.Count; n++) { var row = i.perWeapon[n]; if (row == null || string.IsNullOrEmpty(row.weaponName) || row.ratio <= 0f) continue; if (string.Equals(Strip(row.weaponName), key, StringComparison.OrdinalIgnoreCase)) return row.ratio; } } return i.targetRatio > 0f ? i.targetRatio : 0f; } /// "Elven_Sword_01(Clone)" → "Elven_Sword_01" (원본 `DSUtil.Get_Clone` 이 Instantiate 기본 이름을 쓴다). public static string Strip(string n) { if (string.IsNullOrEmpty(n)) return n; int i = n.IndexOf("(Clone)", StringComparison.Ordinal); return i >= 0 ? n.Substring(0, i).Trim() : n.Trim(); } } }