249 lines
13 KiB
C#
249 lines
13 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLZonePickupSettings.cs — 존 클리어 픽업 카드 값의 단일 출처 (WL-813o · #813)
|
||
//
|
||
// 기준서 §D-2 813o 행 + 기획안 F-3 + 발주서 WL-813o §1(Lead 설계 결정).
|
||
// 트리거 계약 = 813p 완료보고 §4 `RunEvents.ZoneCleared`(`Assets/WL/Combat/Run/RunEvents.cs` · 읽기만).
|
||
//
|
||
// ■ 왜 이 에셋인가 (C45)
|
||
// 타임아웃 초·카드 수·존 포함 범위·배치·색·문구를 코드 상수로 두지 않는다. 이 파일에는
|
||
// "필드와 기본값"만 있고, 게임이 읽는 값은 `Resources/WL/WLZonePickupSettings.asset` 이다.
|
||
// 🔴 런 길이·존 수 같은 **게임 값은 여기 없다** — 813p `WLRunSettings` 소유이고
|
||
// 픽업은 `ZoneCleared` 페이로드와 `RunDirector.ZoneCount` 로 받는다(수치 중복 0).
|
||
//
|
||
// ■ 좌표계 (px)
|
||
// 813c/813q 와 같은 **세로 1080×1920 기준 화면 픽셀**. px → 캔버스 유닛 환산은
|
||
// `WLHudLayoutSettings.UnitsPerPx(canvas)` **하나만** 쓴다(산식 중복 0).
|
||
//
|
||
// ■ C8 롤백
|
||
// `enabled_ = 0` → 구독 0 · 노드 0 · 표시 0 · 런 타이머 정지 0 = 813o 이전 동작 100%.
|
||
//
|
||
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
using UnityEngine;
|
||
|
||
namespace WL.UI
|
||
{
|
||
[CreateAssetMenu(fileName = "WLZonePickupSettings", menuName = "WL/Zone Pickup Settings", order = 34)]
|
||
public sealed class WLZonePickupSettings : ScriptableObject
|
||
{
|
||
/// <summary>Resources 경로 — 런타임 로드 키.</summary>
|
||
public const string ResourcesPath = "WL/WLZonePickupSettings";
|
||
|
||
private static WLZonePickupSettings s_instance;
|
||
private static bool s_tried;
|
||
|
||
/// <summary>에셋이 없으면 null(호출부는 null 이면 전부 건너뛴다 = C8 롤백).</summary>
|
||
public static WLZonePickupSettings Instance
|
||
{
|
||
get
|
||
{
|
||
if (s_instance == null && !s_tried)
|
||
{
|
||
s_tried = true;
|
||
s_instance = Resources.Load<WLZonePickupSettings>(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 · 런 타이머 정지 0 = 813o 이전 동작 100%.")]
|
||
public bool enabled_ = true;
|
||
|
||
[Tooltip("켜면 상태 전이마다 Console 에 1줄 남긴다(문자열을 만들기 전에 이 값으로 막는다 · GC 주의).")]
|
||
public bool verboseLog = false;
|
||
|
||
// ── 트리거 ────────────────────────────────────────────────────────────
|
||
[Header("트리거 — 813p RunEvents.ZoneCleared")]
|
||
[Tooltip("존 4(마지막 존)에서도 카드를 띄울지. 기본 끔 = 보스 흐름 방해 금지(발주서 §1-1).")]
|
||
public bool includeFinalZone = false;
|
||
|
||
[Tooltip("이 인덱스(0부터) 이상인 존만 카드를 띄운다. 0 = 존1부터 전부.")]
|
||
public int minZoneIndex = 0;
|
||
|
||
[Tooltip("런 1회당 최대 카드 횟수. 0 = 무제한(존 수만큼).")]
|
||
public int maxPicksPerRun = 0;
|
||
|
||
[Tooltip("ZoneCleared 를 받고 실제로 카드를 열기까지의 지연(초 · unscaled). 존 클리어 연출과 겹치지 않게 약간 둔다.")]
|
||
public float openDelaySeconds = 0.35f;
|
||
|
||
[Tooltip("813w RunGuide 가 다음 존으로 이동 중(Travel)이면 도착한 뒤에 연다.")]
|
||
public bool waitForGuideArrival = true;
|
||
|
||
[Tooltip("가이드 이동을 기다리는 최대 시간(초 · unscaled). 넘으면 이동 중이어도 연다(발주서 §1-1 = 8s).")]
|
||
public float maxGuideDelaySeconds = 8f;
|
||
|
||
[Tooltip("813i/813z 부활 팝업이 떠 있거나 예약 중이면 부활을 우선하고 카드를 미룬다.")]
|
||
public bool deferWhileRevive = true;
|
||
|
||
[Tooltip("부활 때문에 미룰 수 있는 최대 시간(초 · unscaled). 넘으면 이번 픽업을 버린다(무한 대기 금지).")]
|
||
public float maxDeferSeconds = 45f;
|
||
|
||
// ── 카드 ──────────────────────────────────────────────────────────────
|
||
[Header("카드 — 원본 table_selectskill / InGameInfo.Add_SelectSkill")]
|
||
[Tooltip("한 번에 보여 줄 카드 수(기획안 F-3 = 3장 중 1장).")]
|
||
public int cardCount = 3;
|
||
|
||
[Tooltip("원본 SelectSkillUI 노드(GameUI.Ins.IngameUI.m_SelectSkillUI)가 있으면 그걸 연다. " +
|
||
"없으면 같은 원본 데이터·효과 경로를 쓰는 런타임 패널로 폴백한다. " +
|
||
"🔴 실측(2026-09-09): 그 노드는 어떤 씬/프리팹에도 없다 = 항상 폴백.")]
|
||
public bool preferOriginalUi = true;
|
||
|
||
// ── 타임아웃 ──────────────────────────────────────────────────────────
|
||
[Header("타임아웃 — 자동 선택")]
|
||
[Tooltip("카드가 열린 뒤 자동 선택까지의 시간(초). 발주서 §1-2 = 10s.")]
|
||
public float pickTimeoutSeconds = 10f;
|
||
|
||
[Tooltip("타임아웃을 unscaled 시간으로 잰다(timeScale 0 이어도 흐른다).")]
|
||
public bool timeoutUnscaled = true;
|
||
|
||
[Tooltip("타임아웃 시 첫 카드를 고른다(발주서 §1-2). 끄면 원본 방식대로 무작위 카드.")]
|
||
public bool autoPickFirstCard = true;
|
||
|
||
[Tooltip("남은 초 표시 문구. TMP 무할당 오버로드 SetText(format, float) 를 쓴다 — {0} 하나만 넣을 것.")]
|
||
public string timerFormat = "{0}초 후 자동 선택";
|
||
|
||
[Tooltip("남은 초가 이 값 이하면 경고색. 0 이면 경고색 없음.")]
|
||
public float timerWarnSeconds = 3f;
|
||
|
||
// ── 정지 ──────────────────────────────────────────────────────────────
|
||
[Header("정지 — 런 타이머 / 자동전투")]
|
||
[Tooltip("카드가 열려 있는 동안 813p 런 타이머를 멈춘다(RunDirector.PauseTimer · 세계 정지 없음).")]
|
||
public bool pauseRunTimer = true;
|
||
|
||
[Tooltip("카드가 열려 있는 동안 timeScale 을 0 으로 세운다(813z DeathFlow.HoldWorld 방식). " +
|
||
"기본 끔 = 전투는 계속(발주서 §1-3 권장). 🔴 켜면 부활 팝업과 정지 주체가 겹친다.")]
|
||
public bool holdWorld = false;
|
||
|
||
[Tooltip("holdWorld 가 켜졌을 때 세울 timeScale 값.")]
|
||
public float holdWorldTimeScale = 0f;
|
||
|
||
[Tooltip("카드가 열려 있는 동안 원본 ActorInfo.Ins.All_Stop(true) 로 액터를 멈춘다 " +
|
||
"(= 원본 SelectSkillUI.Set/SkillSelect 가 하던 그 경로). 선택 즉시 All_Stop(false) 로 되돌린다.")]
|
||
public bool stopActorsWhileOpen = true;
|
||
|
||
// ── 배치 ──────────────────────────────────────────────────────────────
|
||
[Header("배치 (세로 1080×1920 기준 px)")]
|
||
[Tooltip("패널을 붙일 부모 노드의 UI 루트 기준 경로(813y/813q 실측 = IngameUIs/WL_HUD · SafeAreaFitter 아래).")]
|
||
public string parentPath = "IngameUIs/WL_HUD";
|
||
|
||
[Tooltip("배경 어둡게(딤) 색.")]
|
||
public Color dimColor = new Color(0f, 0f, 0f, 0.62f);
|
||
|
||
[Tooltip("딤이 터치를 먹는다(카드 밖을 눌러도 아무 일도 없게).")]
|
||
public bool dimBlocksTouch = true;
|
||
|
||
[Tooltip("카드 묶음 상자 크기(px).")]
|
||
public Vector2 boxSizePx = new Vector2(980f, 720f);
|
||
|
||
[Tooltip("상자 중심의 세로 오프셋(px · 양수 = 위). 0 = 화면 중앙.")]
|
||
public float boxCenterOffsetYPx = 40f;
|
||
|
||
[Tooltip("상자 배경색.")]
|
||
public Color boxColor = new Color(0.043f, 0.047f, 0.071f, 0.92f);
|
||
|
||
[Tooltip("상자 좌우 안쪽 여백(px).")]
|
||
public float padXPx = 28f;
|
||
|
||
[Tooltip("상자 위쪽 안쪽 여백(px).")]
|
||
public float padTopPx = 26f;
|
||
|
||
[Header("제목 · 남은 초")]
|
||
[Tooltip("제목 문구.")]
|
||
public string titleText = "스킬 선택";
|
||
|
||
[Tooltip("제목 글자 크기(px).")]
|
||
public float titleFontPx = 54f;
|
||
|
||
[Tooltip("제목 색.")]
|
||
public Color titleColor = new Color(0.949f, 0.961f, 0.98f, 1f);
|
||
|
||
[Tooltip("제목 아래 간격(px).")]
|
||
public float titleGapPx = 14f;
|
||
|
||
[Tooltip("남은 초 글자 크기(px).")]
|
||
public float timerFontPx = 36f;
|
||
|
||
[Tooltip("남은 초 색.")]
|
||
public Color timerColor = new Color(0.78f, 0.82f, 0.88f, 1f);
|
||
|
||
[Tooltip("남은 초 경고색(timerWarnSeconds 이하).")]
|
||
public Color timerWarnColor = new Color(1f, 0.29f, 0.28f, 1f);
|
||
|
||
[Tooltip("남은 초 줄 아래 간격(px).")]
|
||
public float timerGapPx = 16f;
|
||
|
||
[Header("카드 모양")]
|
||
[Tooltip("카드 1장 크기(px).")]
|
||
public Vector2 cardSizePx = new Vector2(288f, 430f);
|
||
|
||
[Tooltip("카드 사이 간격(px).")]
|
||
public float cardGapPx = 22f;
|
||
|
||
[Tooltip("카드 배경색.")]
|
||
public Color cardBgColor = new Color(0.12f, 0.13f, 0.18f, 1f);
|
||
|
||
[Tooltip("펫 스킬 카드 배경색(원본 SelectSkillCard 의 petreinforce_bg 대응).")]
|
||
public Color cardPetBgColor = new Color(0.20f, 0.13f, 0.22f, 1f);
|
||
|
||
[Tooltip("카드 이름 글자 크기(px).")]
|
||
public float cardNameFontPx = 38f;
|
||
|
||
[Tooltip("카드 이름 색.")]
|
||
public Color cardNameColor = new Color(1f, 0.86f, 0.45f, 1f);
|
||
|
||
[Tooltip("카드 설명 글자 크기(px).")]
|
||
public float cardDescFontPx = 26f;
|
||
|
||
[Tooltip("카드 설명 색.")]
|
||
public Color cardDescColor = new Color(0.86f, 0.89f, 0.94f, 1f);
|
||
|
||
[Tooltip("카드 안쪽 여백(px).")]
|
||
public float cardPadPx = 16f;
|
||
|
||
[Tooltip("카드 이름 줄 높이(px).")]
|
||
public float cardNameHeightPx = 84f;
|
||
|
||
// ── 텍스트 테두리 (813y WLTextFxUtil.ApplyEdge) ────────────────────────
|
||
[Header("텍스트 테두리 (813y ApplyEdge · TMP 머티리얼 아웃라인)")]
|
||
[Tooltip("제목·남은 초·카드 글자에 씌울 아웃라인/그림자.")]
|
||
public WLTextEdge edge = new WLTextEdge
|
||
{
|
||
enabled = true,
|
||
outlineWidth = 0.26f,
|
||
outlineColor = new Color(0.03f, 0.03f, 0.05f, 1f),
|
||
shadowOffset = new Vector2(0.55f, -0.55f),
|
||
shadowColor = new Color(0f, 0f, 0f, 0.72f),
|
||
shadowSoftness = 0.28f,
|
||
faceDilate = 0.06f,
|
||
};
|
||
|
||
// ── 환산 ──────────────────────────────────────────────────────────────
|
||
/// <summary>px → 캔버스 유닛. 813c `WLHudLayoutSettings` 의 산식 하나만 쓴다(중복 0).</summary>
|
||
public float UnitsPerPx(Canvas canvas)
|
||
{
|
||
var hud = WLHudLayoutSettings.Instance;
|
||
return hud != null ? hud.UnitsPerPx(canvas) : 1f;
|
||
}
|
||
}
|
||
}
|