Project_WL/Assets/WL/UI/Settings/WLPowerDeltaSettings.cs

255 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// WLPowerDeltaSettings.cs — 성장 체감 표시(전투력 % 토스트 · 팝업 전투력 줄 · 처치 타수 비교) 값의 단일 출처
// (WL-813l · PD 지시 #813)
//
// 기준서 v1 §D-2 813l 행 + §E 5-3 「강화 후 체감(전투력 % + 처치 타수 변화) = 표시 + 타수 1 로그」.
// 발주서 WL-813l §1(Lead 설계 결정) · 구독 계약 = 813k `GrowthEvents.LevelUp`(완료보고 §3) ·
// 811b `CombatEvents`(AttackStarted/HitConfirmed/Killed) · 813p `RunEvents.ZoneCleared`(완료보고 §4).
//
// ■ 🔴 전투력 산식은 「추정」이다
// 원본(`Assets/Script/**`)에는 전투력·CP 개념이 **없다**(실측: `combatpower|battlepower|전투력` 전수 검색 0건).
// 그래서 발주서 §1-1 대안인 WL 산식을 쓴다:
// power = ATK × (1 + 치명 기여) × (1 + 공속 기여) + MaxHP × hpWeight
// 구성 스탯도 계수도 전부 이 에셋 값이다 — 코드 상수 0. 기획이 바꾸면 표시가 따라 바뀐다.
//
// ■ 값 읽기는 원본 API 만 쓴다 — `Actor.Get_StatInfo()` → `ActorStatInfo.Get_Stat(eStat)`. 원본 훅 0 · 쓰기 0.
//
// ■ 좌표계 (px)
// 813c·813g·813q 와 같은 **세로 1080×1920 기준 화면 픽셀**. px → 캔버스 유닛 환산은
// `WLCombatTextSettings.UnitsPerPx(canvas)` **하나만** 쓴다(산식 중복 0 · 813tj 선례).
//
// ■ C8 롤백
// `enabled_ = 0` → 구독 0 · 폴링 0 · 노드 0 · 표시 0. 팝업 전투력 줄도 붙지 않는다.
// 개별 스위치(`toastEnabled` · `statPopupLineEnabled` · `hitsPerKillEnabled`)로 셋을 따로 끌 수도 있다.
//
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
namespace WL.UI
{
/// <summary>「몇 방에 죽었나」를 세는 분자(타수)의 출처.</summary>
public enum WLHitSource
{
/// <summary>메인 PC 가 **실제로 적중시킨** 타격 수(`CombatEvents.HitConfirmed.byMainPC`).
/// 광역기로 여러 마리를 때리면 마리 수만큼 센다 = 「한 마리를 몇 방에」에 가장 가깝다.</summary>
HitConfirmed = 0,
/// <summary>메인 PC 의 **공격 모션 시작** 수(`CombatEvents.AttackStarted.isMainPC`).
/// 기준서 §E 5-3 의 검증 방법(`AttackStarted`/`Killed` 비)과 같은 분자다.</summary>
AttackStarted = 1,
}
[CreateAssetMenu(fileName = "WLPowerDeltaSettings", menuName = "WL/Power Delta Settings", order = 34)]
public sealed class WLPowerDeltaSettings : ScriptableObject
{
/// <summary>Resources 경로 — 런타임 로드 키.</summary>
public const string ResourcesPath = "WL/WLPowerDeltaSettings";
private static WLPowerDeltaSettings s_instance;
private static bool s_tried;
/// <summary>에셋이 없으면 null(호출부는 null 이면 전부 건너뛴다 = C8 롤백).</summary>
public static WLPowerDeltaSettings Instance
{
get
{
if (s_instance == null && !s_tried)
{
s_tried = true;
s_instance = Resources.Load<WLPowerDeltaSettings>(ResourcesPath);
}
return s_instance;
}
}
/// <summary>에디터에서 에셋을 새로 만들거나 값을 바꾼 뒤 캐시를 버린다.</summary>
public static void ClearCache() { s_instance = null; s_tried = false; }
/// <summary>프로브·QA 용 런타임 스위치(에셋을 건드리지 않고 끈다).</summary>
public static bool RuntimeDisabled;
/// <summary>성장 체감 축이 살아 있는가 = 에셋 존재 · enabled_ · 런타임 스위치 off.</summary>
public static bool Enabled
{
get
{
if (RuntimeDisabled) return false;
var s = Instance;
return s != null && s.enabled_;
}
}
// ── 공통 ──────────────────────────────────────────────────────────────
[Header("공통 (C8 롤백)")]
[Tooltip("끄면 전투력 토스트·팝업 전투력 줄·타수 비교가 전부 무동작(구독 0 · 노드 0 · 표시 0).")]
// ScriptableObject 에는 `enabled` 멤버가 없지만(그건 Behaviour) 혼동을 피해 뒤에 _ 를 붙인다 —
// 813q WLRunUiSettings 와 같은 규약. 인스펙터·에셋 YAML 에는 `enabled_` 로 나온다.
public bool enabled_ = true;
[Tooltip("콘솔 로그(전투력 변화 · 타수 창 마감). QA·프로브 때만 켠다.")]
public bool verboseLog = false;
// ── 전투력 산식 (🔴 원본에 CP 없음 = 「추정」 · 발주서 §1-1 대안) ──────
[Header("전투력 산식 — 원본에 CP 개념이 없어 WL 산식을 쓴다(「추정」)")]
[Tooltip("공격력 스탯. 기본 BaseDmg = 원본이 ATK × ATK_Multiplier × DMG_Multiplier 로 만든 최종 공격력.")]
public eStat atkStat = eStat.BaseDmg;
[Tooltip("위 값이 0 일 때 쓰는 대체 공격력 스탯(전투 밖·로비에서 BaseDmg 가 안 채워진 경우).")]
public eStat atkFallbackStat = eStat.ATK;
[Tooltip("치명타 확률 스탯. 원본 런타임 값은 0~1 비율이다(실측: DSUtil.RandomTrue(p) 가 Random.Range(0,1) 와 비교).")]
public eStat critRateStat = eStat.CRI;
[Tooltip("치명타 피해 증가량 스탯(0~1 비율). useCritDamage 가 켜져 있을 때만 읽는다.")]
public eStat critDamageStat = eStat.CRI_DMG_Multiplier;
[Tooltip("켜면 치명 기여 = 확률 × 피해증가(기대값). 끄면 확률만 쓴다(발주서 §1-1 문자 그대로의 (1 + CRIT%)).")]
public bool useCritDamage = true;
[Tooltip("치명 기여에 곱하는 가중치. 0 이면 치명을 무시한다.")]
public float critWeight = 1f;
[Tooltip("공격 속도 배율 스탯. 원본 기본값은 1 이다(실측: MyValue.cs Set_Stat(ATK_SPD_Multiplier, 1) · FinalAttackSpeed = HERO_ATK_SPD × (배율 1)).")]
public eStat attackSpeedStat = eStat.ATK_SPD_Multiplier;
[Tooltip("공속 배율의 기준값(이 값을 빼서 증가분을 만든다). 원본 실측 기본값 1.")]
public float attackSpeedBase = 1f;
[Tooltip("공속 기여 가중치. 0 이면 공속을 무시한다.")]
public float attackSpeedWeight = 1f;
[Tooltip("체력 스탯(최종 최대 체력).")]
public eStat hpStat = eStat.MaxHP;
[Tooltip("체력 계수. 발주서 §1-1 = 0.1.")]
public float hpWeight = 0.1f;
[Tooltip("최종 전투력에 곱하는 표시 배율(자릿수 조정용). 1 = 산식 그대로.")]
public float powerScale = 1f;
// ── 전투력 % 토스트 ───────────────────────────────────────────────────
[Header("전투력 % 토스트")]
[Tooltip("끄면 토스트만 표시 안 함(팝업 줄·타수 비교는 그대로).")]
public bool toastEnabled = true;
[Tooltip("토스트를 붙일 HUD 경로(NewGameUI 루트 기준). 813q RunHud 와 같은 부모.")]
public string hudParentPath = "IngameUIs/WL_HUD";
[Tooltip("화면 중심에서의 세로 오프셋(px · +위). 실측 대역: 스탯 팝업(+180 · 전투력 줄 포함 시 위 끝 +328) 과 연쇄 처치(+520 · 아래 끝 +472) 사이의 빈 구간에 앉힌다.")]
public float toastCenterOffsetPx = 430f;
[Tooltip("줄 수(전투력 줄 + 타수 비교 줄이 같이 뜰 수 있다).")]
[Range(1, 4)] public int toastLines = 2;
[Tooltip("줄 간격(px).")]
public float toastLineHeightPx = 60f;
[Tooltip("글자 크기(px).")]
public float toastFontPx = 52f;
[Tooltip("표시 시간(초). 발주서 §1-2 = 2.0.")]
public float toastSeconds = 2f;
[Tooltip("이 비율이 지난 뒤부터 사라진다(0.6 = 마지막 40%).")]
[Range(0f, 1f)] public float toastFadeStartRatio = 0.6f;
[Tooltip("등장 펀치 배율(1 = 없음).")]
public float toastPunchScale = 1.25f;
[Tooltip("펀치가 1 로 수렴하는 시간(초).")]
public float toastPunchSeconds = 0.18f;
[Tooltip("전투력이 오를 때 색.")]
public Color powerUpColor = new Color(1f, 0.84f, 0.35f, 1f);
[Tooltip("전투력이 내릴 때 색(showOnPowerDown 이 켜져 있을 때만 쓴다).")]
public Color powerDownColor = new Color(0.72f, 0.78f, 0.88f, 1f);
[Tooltip("타수 비교 줄 색.")]
public Color hitsCompareColor = new Color(0.62f, 1f, 0.78f, 1f);
[Tooltip("전투력 상승 문구. {0} = 퍼센트.")]
public string powerUpFormat = "전투력 +{0}%";
[Tooltip("전투력 하락 문구. {0} = 퍼센트(부호 없는 절대값).")]
public string powerDownFormat = "전투력 -{0}%";
[Tooltip("퍼센트 소수 자릿수.")]
[Range(0, 3)] public int percentDigits = 1;
[Tooltip("이 퍼센트 미만의 변화는 띄우지 않는다(반올림 잡음 차단).")]
public float minPercentToShow = 0.5f;
[Tooltip("전투력이 내려갈 때도 띄울지(장비 해제 등). 기본 off = 「성장 체감」만 보여준다.")]
public bool showOnPowerDown = false;
[Tooltip("전투력 값 변화를 확인하는 주기(초). 원본에 장비·강화 변경 이벤트가 없어 폴링한다(실측: Assets/Script 훅 0).")]
public float pollIntervalSeconds = 0.5f;
[Tooltip("직전 토스트 뒤 이 시간 안의 변화는 한 줄로 합친다(강화 연타 대비).")]
public float announceCooldownSeconds = 1f;
[Tooltip("레벨업 프레임에는 토스트를 생략한다 — 같은 값을 스탯 팝업의 전투력 줄이 이미 보여 준다(중복 표시 0).")]
public bool suppressToastOnLevelUp = true;
[Tooltip("토스트 테두리(813y WLTextEdge 규약 · 밝은 배경 대비). enabled=0 이면 원본 머티리얼 그대로.")]
public WLTextEdge toastEdge = new WLTextEdge
{
enabled = true,
outlineWidth = 0.26f,
outlineColor = new Color(0.03f, 0.03f, 0.06f, 1f),
shadowOffset = new Vector2(0.55f, -0.55f),
shadowColor = new Color(0f, 0f, 0f, 0f), // 알파 0 = 그림자 없음(813y 규약)
shadowSoftness = 0.2f,
faceDilate = 0f,
};
// ── 레벨업 팝업 전투력 줄 (813y StatPopup) ───────────────────────────
[Header("레벨업 팝업 전투력 줄 (813y StatPopup 에 1줄)")]
[Tooltip("끄면 팝업에 전투력 줄이 붙지 않는다(팝업 자체는 813y 그대로).")]
public bool statPopupLineEnabled = true;
[Tooltip("변화가 있을 때 문구. {0} = 이전 전투력 · {1} = 새 전투력 · {2} = 퍼센트(부호 포함).")]
public string statPopupLineFormat = "전투력 {0} → {1} ({2}%)";
[Tooltip("변화가 없을 때 문구(원본은 레벨업으로 스탯이 바로 오르지 않는다 — 813k 보고 ①). {0} = 현재 전투력.")]
public string statPopupLineFormatNoChange = "전투력 {0}";
[Tooltip("전투력 줄 글자 크기(px).")]
public float statPopupLineFontPx = 44f;
[Tooltip("전투력 줄 색.")]
public Color statPopupLineColor = new Color(1f, 0.84f, 0.35f, 1f);
// ── 처치 타수 비교 ────────────────────────────────────────────────────
[Header("처치 타수 비교 (기준서 §E 5-3 「타수 1」)")]
[Tooltip("끄면 타수 계측·비교 토스트가 전부 무동작.")]
public bool hitsPerKillEnabled = true;
[Tooltip("타수의 분자. HitConfirmed = 실제 적중 수(권장) · AttackStarted = 기준서 §E 5-3 검증 방법과 동일.")]
public WLHitSource hitsSource = WLHitSource.HitConfirmed;
[Tooltip("한 창(window)의 길이(초). 발주서 §1-4 = 60.")]
public float windowSeconds = 60f;
[Tooltip("이 처치 수를 못 채운 창은 버린다(표본 부족).")]
public int minKillsPerWindow = 5;
[Tooltip("비교 문구. {0} = 이전 타수 · {1} = 새 타수.")]
public string hitsCompareFormat = "이제 {0}방 → {1}방";
[Tooltip("켜면 다음 존 클리어(813p RunEvents.ZoneCleared) 때 띄운다 — 발주서 §1-4.")]
public bool showCompareOnZoneCleared = true;
[Tooltip("런 축이 안 돌 때(ZoneCleared 가 안 옴) 무장 즉시 띄울지.")]
public bool showCompareImmediatelyIfNoRun = true;
[Tooltip("무장한 비교 문구가 이 시간 안에 표시되지 않으면 버린다(초).")]
public float compareArmSeconds = 120f;
[Tooltip("타수 감소가 이 값 이상일 때만 띄운다(방). 1 = 「타수 1」 = 기준서 §E 5-3 2점 조건.")]
public int compareMinDropHits = 1;
}
}