408 lines
18 KiB
C#
408 lines
18 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLRunUiSettings.cs — 런 HUD · 런 결과 화면 값의 단일 출처 (WL-813q · #813)
|
||
//
|
||
// 기준서 §D-2 813q 행 + 발주서 WL-813q §1 (Lead 설계 결정).
|
||
// 구독 계약 = 813p 완료보고 §4 `RunEvents` 표(`Assets/WL/Combat/Run/RunEvents.cs`).
|
||
//
|
||
// ■ 왜 이 에셋인가 (C45)
|
||
// 위치·글자 크기·색·행 순서·문구를 코드 상수로 두지 않는다. 이 파일에는 "필드와 기본값"만 있고,
|
||
// 게임이 읽는 값은 `Resources/WL/WLRunUiSettings.asset` 이다(PD·기획이 인스펙터에서 바꾼다).
|
||
// 🔴 런 길이(`runSeconds`)·존 수 같은 **게임 값은 여기 없다** — 그건 813p `WLRunSettings` 소유이고
|
||
// UI 는 `RunStarted/RunTick` 페이로드로 받는다(수치 중복 0).
|
||
//
|
||
// ■ 좌표계 (px)
|
||
// 813c 와 같은 **세로 1080×1920 기준 화면 픽셀**. px → 캔버스 유닛 환산은
|
||
// `WLHudLayoutSettings.UnitsPerPx(canvas)` **하나만** 쓴다(산식 중복 0 · 813tj 선례).
|
||
//
|
||
// ■ C8 롤백
|
||
// `enabled = 0` → HUD·결과 화면 둘 다 구독 0 · 노드 0 · 표시 0.
|
||
// 813p `RunDirector` 는 그대로 돈다(이벤트만 아무도 안 듣는다).
|
||
//
|
||
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
using UnityEngine;
|
||
|
||
namespace WL.UI
|
||
{
|
||
/// <summary>결과 화면 본문 행 종류 — 배열 순서 = 표시 순서 · 배열에 없으면 표시 안 함.</summary>
|
||
public enum WLRunResultRow
|
||
{
|
||
/// <summary>총 시간(mm:ss).</summary>
|
||
TotalTime = 0,
|
||
/// <summary>보스 처치 시간(미발생 −1 → 대시 문구).</summary>
|
||
BossClear = 1,
|
||
/// <summary>처치 수(보스 n).</summary>
|
||
Kills = 2,
|
||
/// <summary>최대 연쇄 처치.</summary>
|
||
MaxChain = 3,
|
||
/// <summary>최대 피해(합산 또는 단일 — resultMaxDamageUseBurst).</summary>
|
||
MaxDamage = 4,
|
||
/// <summary>레벨 from → to.</summary>
|
||
Level = 5,
|
||
/// <summary>등급별 획득(0 인 등급 생략 · 등급 색).</summary>
|
||
LootByGrade = 6,
|
||
/// <summary>획득 골드.</summary>
|
||
Gold = 7,
|
||
/// <summary>사망 / 물약.</summary>
|
||
DeathsPotions = 8,
|
||
}
|
||
|
||
[CreateAssetMenu(fileName = "WLRunUiSettings", menuName = "WL/Run UI Settings", order = 33)]
|
||
public sealed class WLRunUiSettings : ScriptableObject
|
||
{
|
||
/// <summary>Resources 경로 — 런타임 로드 키.</summary>
|
||
public const string ResourcesPath = "WL/WLRunUiSettings";
|
||
|
||
private static WLRunUiSettings s_instance;
|
||
private static bool s_tried;
|
||
|
||
/// <summary>에셋이 없으면 null(호출부는 null 이면 전부 건너뛴다 = C8 롤백).</summary>
|
||
public static WLRunUiSettings Instance
|
||
{
|
||
get
|
||
{
|
||
if (s_instance == null && !s_tried)
|
||
{
|
||
s_tried = true;
|
||
s_instance = Resources.Load<WLRunUiSettings>(ResourcesPath);
|
||
}
|
||
return s_instance;
|
||
}
|
||
}
|
||
|
||
/// <summary>에디터에서 에셋을 새로 만들거나 값을 바꾼 뒤 캐시를 버린다.</summary>
|
||
public static void ClearCache() { s_instance = null; s_tried = false; }
|
||
|
||
/// <summary>프로브·QA 용 런타임 스위치(에셋을 건드리지 않고 끈다).</summary>
|
||
public static bool RuntimeDisabled;
|
||
|
||
/// <summary>런 UI 축이 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off.</summary>
|
||
public static bool Enabled
|
||
{
|
||
get
|
||
{
|
||
if (RuntimeDisabled) return false;
|
||
var s = Instance;
|
||
return s != null && s.enabled_;
|
||
}
|
||
}
|
||
|
||
// ── 공통 ──────────────────────────────────────────────────────────────
|
||
[Header("공통 (C8 롤백)")]
|
||
[Tooltip("끄면 런 HUD·결과 화면 둘 다 구독 0 · 노드 0 · 표시 0. 813p RunDirector 는 그대로 돈다.")]
|
||
// ScriptableObject 에 이미 `enabled` 라는 이름이 없지만(그건 MonoBehaviour), 혼동을 피해 뒤에 _ 를 붙인다.
|
||
// 인스펙터·에셋 YAML 에는 `enabled_` 로 나온다.
|
||
public bool enabled_ = true;
|
||
|
||
[Tooltip("켜면 런 UI 가 상태 전이마다 Console 에 1줄 남긴다(문자열을 만들기 전에 이 값으로 막는다 · GC 주의).")]
|
||
public bool verboseLog = false;
|
||
|
||
// ── 런 HUD ────────────────────────────────────────────────────────────
|
||
[Header("런 HUD — 배치 (Safe Area 안 · WL_HUD 자식)")]
|
||
[Tooltip("끄면 HUD 노드를 만들지도 갱신하지도 않는다(결과 화면은 그대로).")]
|
||
public bool runHudEnabled = true;
|
||
|
||
[Tooltip("HUD 를 붙일 부모 노드의 UI 루트 기준 경로(813y 실측 = IngameUIs/WL_HUD · SafeAreaFitter 아래).")]
|
||
public string runHudParentPath = "IngameUIs/WL_HUD";
|
||
|
||
[Tooltip("Safe Area 상단에서 HUD 위쪽까지 여백(px). 813c 보스 HP 바(top 190 + 높이 66 = 256)와 겹치지 않게 그 아래.")]
|
||
public float runHudTopMarginPx = 272f;
|
||
|
||
[Tooltip("가로 중앙 기준 오프셋(px · 양수 = 오른쪽). 0 = 상단 중앙.")]
|
||
public float runHudCenterOffsetXPx = 0f;
|
||
|
||
[Tooltip("HUD 폭(px) — 존 게이지가 이 폭을 칸 수로 나눠 쓴다.")]
|
||
public float runHudWidthPx = 560f;
|
||
|
||
[Tooltip("런이 끝나면(승리·시간 종료·이탈 모두) HUD 를 숨긴다.")]
|
||
public bool runHudHideOnEnd = true;
|
||
|
||
[Header("런 HUD — 남은 시간")]
|
||
[Tooltip("남은 시간 mm:ss 표시.")]
|
||
public bool timerEnabled = true;
|
||
|
||
[Tooltip("타이머 글자 크기(px).")]
|
||
public float timerFontPx = 56f;
|
||
|
||
[Tooltip("타이머 평상시 색.")]
|
||
public Color timerColor = new Color(0.949f, 0.961f, 0.980f, 1f);
|
||
|
||
[Tooltip("남은 시간이 이 값 이하면 경고 색으로 바뀐다(초). 0 이면 경고 없음.")]
|
||
public float timerWarnSeconds = 30f;
|
||
|
||
[Tooltip("경고 색(마지막 구간).")]
|
||
public Color timerWarnColor = new Color(1f, 0.290f, 0.278f, 1f);
|
||
|
||
[Tooltip("타이머 아웃라인·그림자(813y WLTextFxUtil.ApplyEdge).")]
|
||
public WLTextEdge timerEdge = new WLTextEdge
|
||
{
|
||
enabled = true,
|
||
outlineWidth = 0.28f,
|
||
outlineColor = new Color(0.05f, 0.03f, 0.02f, 1f),
|
||
shadowOffset = new Vector2(0.5f, -0.5f),
|
||
shadowColor = new Color(0f, 0f, 0f, 0.8f),
|
||
shadowSoftness = 0.22f,
|
||
faceDilate = 0.08f,
|
||
};
|
||
|
||
[Header("런 HUD — 존 게이지")]
|
||
[Tooltip("존 게이지 칸 표시(칸 수 = RunStarted.zoneCount).")]
|
||
public bool zoneGaugeEnabled = true;
|
||
|
||
[Tooltip("타이머 아래 간격(px).")]
|
||
public float zoneGaugeTopGapPx = 10f;
|
||
|
||
[Tooltip("칸 높이(px).")]
|
||
public float zoneCellHeightPx = 18f;
|
||
|
||
[Tooltip("칸 사이 간격(px).")]
|
||
public float zoneCellGapPx = 8f;
|
||
|
||
[Tooltip("빈 칸(배경) 색.")]
|
||
public Color zoneCellBgColor = new Color(0.05f, 0.05f, 0.06f, 0.78f);
|
||
|
||
[Tooltip("진행 중인 칸의 채움 색(zoneKills / zoneRequired).")]
|
||
public Color zoneCellFillColor = new Color(0.99f, 0.78f, 0.30f, 1f);
|
||
|
||
[Tooltip("클리어한 칸의 채움 색.")]
|
||
public Color zoneCellClearColor = new Color(0.137f, 0.906f, 0.529f, 1f);
|
||
|
||
[Tooltip("존 클리어 순간 칸이 커졌다 돌아오는 배율(1 이면 펄스 없음).")]
|
||
public float zonePulseScale = 1.35f;
|
||
|
||
[Tooltip("펄스 시간(초 · unscaled).")]
|
||
public float zonePulseSeconds = 0.3f;
|
||
|
||
[Header("런 HUD — 단계 라벨")]
|
||
[Tooltip("단계 라벨(\"존 N\" → 게이트 뒤 \"BOSS\") 표시.")]
|
||
public bool phaseLabelEnabled = true;
|
||
|
||
[Tooltip("존 게이지 아래 간격(px).")]
|
||
public float phaseTopGapPx = 8f;
|
||
|
||
[Tooltip("단계 라벨 글자 크기(px).")]
|
||
public float phaseFontPx = 30f;
|
||
|
||
[Tooltip("존 단계 문구 — {0} = 존 번호(1부터).")]
|
||
public string phaseZoneFormat = "존 {0}";
|
||
|
||
[Tooltip("보스 게이트 개방 뒤 문구.")]
|
||
public string phaseBossText = "BOSS";
|
||
|
||
[Tooltip("존 단계 라벨 색.")]
|
||
public Color phaseColor = new Color(0.90f, 0.91f, 0.94f, 1f);
|
||
|
||
[Tooltip("보스 단계 라벨 색.")]
|
||
public Color phaseBossColor = new Color(1f, 0.62f, 0.24f, 1f);
|
||
|
||
[Tooltip("단계 라벨 아웃라인·그림자.")]
|
||
public WLTextEdge phaseEdge = new WLTextEdge
|
||
{
|
||
enabled = true,
|
||
outlineWidth = 0.24f,
|
||
outlineColor = new Color(0.05f, 0.03f, 0.02f, 1f),
|
||
shadowOffset = new Vector2(0.4f, -0.4f),
|
||
shadowColor = new Color(0f, 0f, 0f, 0.75f),
|
||
shadowSoftness = 0.18f,
|
||
faceDilate = 0.06f,
|
||
};
|
||
|
||
// ── 결과 화면 ─────────────────────────────────────────────────────────
|
||
[Header("런 결과 화면 (RunEnded)")]
|
||
[Tooltip("끄면 결과 화면을 만들지도 띄우지도 않는다(HUD 는 그대로).")]
|
||
public bool resultEnabled = true;
|
||
|
||
[Tooltip("결과 화면을 붙일 부모 노드 경로(813tj ReviveDialog 와 같은 대역).")]
|
||
public string resultParentPath = "IngameUIs/WL_HUD";
|
||
|
||
[Tooltip("공용 팝업(Popup.Ins · SortOrder_5)에 본문을 밀어 넣는다. 실측상 공용 팝업은 본문 라벨 1개 + " +
|
||
"프리팹 고정 버튼 문구라 \"다음 런\" 문구를 프리팹 수정 없이 넣을 수 없다 → 기본 off(런타임 패널).")]
|
||
public bool resultUseCommonPopup = false;
|
||
|
||
[Tooltip("이탈(Abandon)에도 결과 화면을 띄운다. 발주서 §1-2 = 띄우지 않는다(HUD 리셋만).")]
|
||
public bool resultShowOnAbandon = false;
|
||
|
||
[Tooltip("부활 팝업(813i/813tj)이 떠 있거나 예약 중이면 결과 화면을 미뤘다가 닫힌 뒤 띄운다.")]
|
||
public bool resultDeferWhileRevive = true;
|
||
|
||
[Tooltip("승리 제목.")]
|
||
public string resultWinTitle = "런 클리어!";
|
||
|
||
[Tooltip("시간 종료 제목.")]
|
||
public string resultTimeoutTitle = "시간 종료";
|
||
|
||
[Tooltip("이탈 제목(resultShowOnAbandon 을 켰을 때만 쓰인다).")]
|
||
public string resultAbandonTitle = "런 중단";
|
||
|
||
[Tooltip("값이 없을 때(−1) 쓰는 문구.")]
|
||
public string resultDashText = "—";
|
||
|
||
[Header("결과 화면 — 배치 · 색")]
|
||
[Tooltip("팝업 상자 크기(px).")]
|
||
public Vector2 resultBoxSizePx = new Vector2(860f, 1080f);
|
||
|
||
[Tooltip("뒤 화면을 덮는 딤 색.")]
|
||
public Color resultDimColor = new Color(0f, 0f, 0f, 0.62f);
|
||
|
||
[Tooltip("팝업 상자 바탕 색.")]
|
||
public Color resultBgColor = new Color(0.06f, 0.07f, 0.09f, 0.94f);
|
||
|
||
[Tooltip("상자 안쪽 좌우 여백(px).")]
|
||
public float resultPadXPx = 40f;
|
||
|
||
[Tooltip("상자 위쪽 여백(px).")]
|
||
public float resultPadTopPx = 36f;
|
||
|
||
[Tooltip("제목 글자 크기(px).")]
|
||
public float resultTitleFontPx = 56f;
|
||
|
||
[Tooltip("승리 제목 색.")]
|
||
public Color resultTitleWinColor = new Color(1f, 0.86f, 0.42f, 1f);
|
||
|
||
[Tooltip("시간 종료·이탈 제목 색.")]
|
||
public Color resultTitleFailColor = new Color(0.86f, 0.88f, 0.92f, 1f);
|
||
|
||
[Tooltip("제목 아래 본문까지 간격(px).")]
|
||
public float resultTitleGapPx = 28f;
|
||
|
||
[Tooltip("본문 행 글자 크기(px).")]
|
||
public float resultRowFontPx = 34f;
|
||
|
||
[Tooltip("본문 행 높이(px).")]
|
||
public float resultRowHeightPx = 56f;
|
||
|
||
[Tooltip("행 이름(왼쪽) 색.")]
|
||
public Color resultRowLabelColor = new Color(0.70f, 0.73f, 0.79f, 1f);
|
||
|
||
[Tooltip("행 값(오른쪽) 색.")]
|
||
public Color resultRowValueColor = new Color(0.96f, 0.97f, 0.99f, 1f);
|
||
|
||
[Tooltip("본문 행 아웃라인·그림자.")]
|
||
public WLTextEdge resultEdge = new WLTextEdge
|
||
{
|
||
enabled = true,
|
||
outlineWidth = 0.22f,
|
||
outlineColor = new Color(0.03f, 0.02f, 0.02f, 1f),
|
||
shadowOffset = new Vector2(0.35f, -0.35f),
|
||
shadowColor = new Color(0f, 0f, 0f, 0.7f),
|
||
shadowSoftness = 0.15f,
|
||
faceDilate = 0.05f,
|
||
};
|
||
|
||
[Header("결과 화면 — 본문 행 (배열 순서 = 표시 순서 · 빼면 표시 안 함)")]
|
||
[Tooltip("발주서 §1-2 기본 순서: 총 시간 · 보스 처치 · 처치 수 · 최대 연쇄 · 최대 피해 · 레벨 · 등급별 획득 · 골드 · 사망/물약")]
|
||
public WLRunResultRow[] resultRows = new WLRunResultRow[]
|
||
{
|
||
WLRunResultRow.TotalTime,
|
||
WLRunResultRow.BossClear,
|
||
WLRunResultRow.Kills,
|
||
WLRunResultRow.MaxChain,
|
||
WLRunResultRow.MaxDamage,
|
||
WLRunResultRow.Level,
|
||
WLRunResultRow.LootByGrade,
|
||
WLRunResultRow.Gold,
|
||
WLRunResultRow.DeathsPotions,
|
||
};
|
||
|
||
[Tooltip("행 이름 문구 — 위 배열의 순서와 무관하게 종류별로 붙는다.")]
|
||
public string rowTotalTimeLabel = "총 시간";
|
||
public string rowBossClearLabel = "보스 처치";
|
||
public string rowKillsLabel = "처치 수";
|
||
public string rowMaxChainLabel = "최대 연쇄";
|
||
public string rowMaxDamageLabel = "최대 피해";
|
||
public string rowLevelLabel = "레벨";
|
||
public string rowLootLabel = "획득";
|
||
public string rowGoldLabel = "골드";
|
||
public string rowDeathsLabel = "사망 / 물약";
|
||
|
||
[Tooltip("처치 수 값 문구 — {0} = 전체 처치, {1} = 그중 보스.")]
|
||
public string rowKillsFormat = "{0} (보스 {1})";
|
||
|
||
[Tooltip("레벨 값 문구 — {0} = 시작 레벨, {1} = 종료 레벨.")]
|
||
public string rowLevelFormat = "{0} → {1}";
|
||
|
||
[Tooltip("사망/물약 값 문구 — {0} = 사망, {1} = 물약 사용.")]
|
||
public string rowDeathsFormat = "{0} / {1}";
|
||
|
||
[Tooltip("등급별 획득 한 칸 문구 — {0} = 개수. 앞에 등급 색 태그가 붙는다.")]
|
||
public string rowLootGradeFormat = "{0}";
|
||
|
||
[Tooltip("등급별 획득 칸 사이 구분 문구.")]
|
||
public string rowLootGradeSeparator = " ";
|
||
|
||
[Tooltip("등급별 획득이 0 건일 때 값 문구.")]
|
||
public string rowLootEmptyText = "0";
|
||
|
||
[Tooltip("최대 피해를 합산(DamageMergeEvent.total 최대 · maxDamageBurst)으로 본다. 끄면 단일 타격 최대(maxDamage).")]
|
||
public bool resultMaxDamageUseBurst = true;
|
||
|
||
[Tooltip("레벨 행은 levelFrom·levelTo 가 둘 다 0 이면(에디트 모드 등) 감춘다.")]
|
||
public bool resultHideLevelWhenZero = true;
|
||
|
||
[Header("결과 화면 — 버튼")]
|
||
[Tooltip("\"다음 런\" 버튼 문구 — 누르면 RunDirector.Restart() 하나만 부른다(813p 공개 API).")]
|
||
public string resultNextRunText = "다음 런";
|
||
|
||
[Tooltip("\"닫기\" 버튼 문구.")]
|
||
public string resultCloseText = "닫기";
|
||
|
||
[Tooltip("버튼 크기(px).")]
|
||
public Vector2 resultButtonSizePx = new Vector2(340f, 104f);
|
||
|
||
[Tooltip("버튼 사이 간격(px).")]
|
||
public float resultButtonGapPx = 28f;
|
||
|
||
[Tooltip("상자 아래에서 버튼까지 여백(px).")]
|
||
public float resultButtonBottomPx = 36f;
|
||
|
||
[Tooltip("버튼 글자 크기(px).")]
|
||
public float resultButtonFontPx = 38f;
|
||
|
||
[Tooltip("\"다음 런\" 버튼 바탕 색.")]
|
||
public Color resultNextBgColor = new Color(0.137f, 0.906f, 0.529f, 1f);
|
||
|
||
[Tooltip("\"다음 런\" 버튼 글자 색.")]
|
||
public Color resultNextLabelColor = new Color(0.04f, 0.09f, 0.06f, 1f);
|
||
|
||
[Tooltip("\"닫기\" 버튼 바탕 색.")]
|
||
public Color resultCloseBgColor = new Color(0.16f, 0.18f, 0.22f, 1f);
|
||
|
||
[Tooltip("\"닫기\" 버튼 글자 색.")]
|
||
public Color resultCloseLabelColor = new Color(0.90f, 0.92f, 0.95f, 1f);
|
||
|
||
// ── 파생 헬퍼 (코드 상수 0) ───────────────────────────────────────────
|
||
/// <summary>px → 캔버스 유닛 계수. 813c 의 산식 하나만 쓴다(중복 0).</summary>
|
||
public float UnitsPerPx(Canvas canvas)
|
||
{
|
||
var hud = WLHudLayoutSettings.Instance;
|
||
return hud != null ? hud.UnitsPerPx(canvas) : 1f;
|
||
}
|
||
|
||
/// <summary>이 행을 표시하는가(배열에 있으면 표시).</summary>
|
||
public bool HasRow(WLRunResultRow row)
|
||
{
|
||
if (resultRows == null) return false;
|
||
for (int i = 0; i < resultRows.Length; i++) if (resultRows[i] == row) return true;
|
||
return false;
|
||
}
|
||
|
||
/// <summary>결과 제목(종료 사유별).</summary>
|
||
public string TitleFor(WL.Combat.Run.RunOutcome outcome)
|
||
{
|
||
switch (outcome)
|
||
{
|
||
case WL.Combat.Run.RunOutcome.Win: return resultWinTitle;
|
||
case WL.Combat.Run.RunOutcome.Timeout: return resultTimeoutTitle;
|
||
default: return resultAbandonTitle;
|
||
}
|
||
}
|
||
|
||
/// <summary>결과 제목 색(종료 사유별).</summary>
|
||
public Color TitleColorFor(WL.Combat.Run.RunOutcome outcome)
|
||
{
|
||
return outcome == WL.Combat.Run.RunOutcome.Win ? resultTitleWinColor : resultTitleFailColor;
|
||
}
|
||
}
|
||
}
|