// ───────────────────────────────────────────────────────────────────────────── // WLGrowthSettings.cs — 성장(레벨업) 연출 값의 단일 출처(SOT · C45) // // PD 지시 #813 · 발주서 WL-813k §1-2 (2026-09-09) // // 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLGrowthSettings.asset // → Resources.Load("WL/WLGrowthSettings") 1회 로드 후 캐시(WLCombatCoreSettings 관례 그대로). // 에셋이 없거나 enabled = false 면 Enabled == false → LevelUp 이벤트 자체가 발생하지 않고 // ServerClass.Add_Exp 의 훅 1줄은 즉시 반환한다 = 원본 100%(C8 롤백 1순위 스위치). // 아래 필드 기본값은 "에셋 부재 시의 폴백"이 아니라 에셋 생성 시 초기값이다. // // 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(ServerClass/Actor 가 Assembly-CSharp 에 있다). // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; namespace WL.Combat.Growth { [CreateAssetMenu(fileName = "WLGrowthSettings", menuName = "WL/Growth Settings", order = 321)] public sealed class WLGrowthSettings : ScriptableObject { public const string ResourcesPath = "WL/WLGrowthSettings"; static WLGrowthSettings s_cached; static bool s_lookupDone; /// 설정 에셋(1회 로드 후 캐시). 없으면 null. public static WLGrowthSettings Instance { get { if (!s_lookupDone) { s_cached = Resources.Load(ResourcesPath); s_lookupDone = true; } return s_cached; } } /// 진단·A/B 전용 런타임 스위치(WLCombatCoreSettings.RuntimeDisabled 선례). Play 종료 시 도메인 리로드로 자동 false. public static bool RuntimeDisabled; /// 성장 연출이 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off. public static bool Enabled { get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled; } } /// 테스트용: 캐시를 비워 다음 접근 때 다시 Resources.Load 한다(에셋 제거/복구 실험). public static void ClearCache() { s_cached = null; s_lookupDone = false; } // ─────────────────────────────────────────── 전체 스위치 [Header("전체 스위치")] [Tooltip("끄면 LevelUp 이벤트 · 폭발 이펙트 · 전회복 · 스케일 펀치가 전부 비활성 — 원본 Add_Exp 동작 100%. 롤백 1순위.")] public bool enabled = true; [Tooltip("레벨업 처리 결과를 Console 에 남긴다(검증용 · 기본 off · GC 가 발생하므로 서비스 빌드에서 켜지 말 것).")] public bool verboseLog = false; [Tooltip("코어 CombatEvents.LevelUp(811b 가 API 만 둔 것)에도 같은 레벨업을 전달한다. 코어가 꺼져 있으면 자동으로 무시된다.")] public bool forwardToCombatEvents = true; // ─────────────────────────────────────────── 폭발 이펙트 [Header("레벨업 폭발 이펙트 (FX_FireLevelUp · 파티클 9 · 비루프)")] [Tooltip("스폰할 이펙트 프리팹. 비우면 이펙트를 만들지 않는다(전회복·이벤트는 그대로).")] public GameObject burstPrefab; [Tooltip("PC 의 자식으로 붙여 따라다니게 한다. 끄면 발생 위치에 고정된다.")] public bool burstParentToPc = true; [Tooltip("PC 발밑 기준 스폰 오프셋(m · 월드 기준).")] public Vector3 burstOffset = new Vector3(0f, 0.1f, 0f); [Tooltip("프리팹 스케일 배율(1 = 원본). 자식 파티클이 전부 scalingMode=Hierarchy 라 이 값이 파티클 크기에 그대로 걸린다.")] public float burstScale = 1f; // ─────────────────────────────────────────── 813w4 §1-3 화면 점유 상한 (D-37) [Tooltip("🔴 D-37 의 원인 값. 스폰된 인스턴스의 ParticleSystemRenderer.maxParticleSize 상한 " + "(= 파티클 1개가 덮을 수 있는 뷰포트 높이 비율). FX_FireLevelUp 의 프리팹 값이 1.0 이라 " + "가까이서 터지면 흰 quad 하나가 화면 전체를 덮어 813l2 「전투력」 줄을 가린다. " + "0.5 = 화면 절반 이하. 0 이하면 손대지 않는다(프리팹 값 그대로 = C8). 프리팹은 수정하지 않는다.")] [Range(0f, 1f)] public float burstMaxScreenFraction = 0.5f; [Tooltip("스폰된 인스턴스의 파티클 시작색 알파에 곱할 값(1 = 원본). 프리팹·공유 머티리얼은 건드리지 않는다. " + "그라디언트 모드 파티클은 새 Gradient 할당이 필요해 건너뛴다(진단 AlphaSkipped).")] [Range(0f, 1f)] public float burstAlpha = 0.6f; [Tooltip("이펙트 파괴까지의 수명(초). 0 이면 스폰된 ParticleSystem 들의 duration + startLifetime 최댓값으로 자동 측정한다.")] public float burstLifetimeSeconds = 0f; [Tooltip("자동 측정 수명의 상한(초). 루프 파티클이 섞여도 이 시간이면 반드시 파괴된다.")] public float burstLifetimeMaxSeconds = 6f; [Tooltip("연속 레벨업(Add_Exp 의 while 루프 · 한 프레임에 여러 번) 때 이펙트를 다시 내는 최소 간격(초 · unscaled). 전회복은 매 레벨 적용된다.")] public float burstMinIntervalSeconds = 0.5f; [Tooltip("EffectBudget.Heavy 슬롯을 잡고 스폰한다(상한 초과 시 이펙트만 스킵). 코어가 꺼져 있으면 제한 없음.")] public bool useEffectBudget = true; // ─────────────────────────────────────────── HP/MP 전회복 [Header("HP/MP 전회복 (1 = 전회복)")] [Tooltip("레벨업 시 회복할 최대 HP 비율. 1 = 전회복 · 0 = 회복 없음.")] [Range(0f, 1f)] public float hpRecoverRate = 1f; [Tooltip("레벨업 시 회복할 최대 MP 비율. 1 = 전회복 · 0 = 회복 없음.")] [Range(0f, 1f)] public float mpRecoverRate = 1f; [Tooltip("회복량을 머리 위 HUD 숫자로 띄운다(원본 Heal/Heal_MP 의 showhud).")] public bool showRecoverHud = true; // ─────────────────────────────────────────── 스케일 펀치 [Header("스케일 펀치 (PC · 카메라 미접촉)")] [Tooltip("PC 스케일 최대 배율. 1 이하면 펀치를 건너뛴다.")] public float scalePunch = 1.12f; [Tooltip("스케일 펀치 왕복 시간(초 · unscaled).")] public float scalePunchDuration = 0.22f; // ─────────────────────────────────────────── 팝업 스탯 3종 [Header("팝업 스탯 3종 (813g UI 가 페이로드에서 읽는다)")] [Tooltip("레벨업 팝업 1번 칸 스탯.")] public eStat statA = eStat.MaxHP; [Tooltip("레벨업 팝업 2번 칸 스탯.")] public eStat statB = eStat.MaxMP; [Tooltip("레벨업 팝업 3번 칸 스탯.")] public eStat statC = eStat.ATK; [Tooltip("켜면 레벨업 직후 ActorStatInfo 를 재계산(Reset · HP/MP 유지 · StatChangeUI 미호출)해 페이로드의 new 스탯을 채운다. " + "원본은 레벨업으로 스탯이 즉시 오르지 않고 스탯 포인트만 주므로 기본 off = 원본 동작(prev == new).")] public bool refreshStatOnLevelUp = false; } }