320 lines
18 KiB
C#
320 lines
18 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
|
||
{
|
||
/// <summary>
|
||
/// WL-813o2 — 효과 클래스가 **코드 문자열로** 추가 요구하는 Addressables 키 1건.
|
||
/// 예: `Select_Star.cs:27 Shoot_Projectile("ProjectileStar", …)` → `Assets/Res_Addr/Projectile/ProjectileStar.prefab`.
|
||
/// 값(키 문자열)을 코드에 두지 않기 위해 SO 배열로 뺀다(C45).
|
||
/// </summary>
|
||
[System.Serializable]
|
||
public sealed class WLPickupExtraKey
|
||
{
|
||
[Tooltip("eEffect 이름(table_selectskill 의 SkillEffect 열과 같은 문자열).")]
|
||
public string skillEffect = "";
|
||
|
||
[Tooltip("그 효과가 추가로 요구하는 Addressables 키(에셋 경로 그대로).")]
|
||
public string assetKey = "";
|
||
|
||
[Tooltip("메모 — 어느 파일 몇 줄에서 나온 키인지(실측 근거).")]
|
||
public string source = "";
|
||
}
|
||
|
||
[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;
|
||
|
||
// ── WL-813c2 (Q6 D-29) — 카드가 **먼저 열린 뒤** 죽는 경우 ─────────────
|
||
// `deferWhileRevive` 는 열기 전(Pending)만 본다. 카드가 열린 뒤 사망하면 부활 팝업이 카드를 덮고,
|
||
// 그 밑에서 타이머가 계속 흘러 플레이어가 보지도 못한 채 자동 선택된다(`R3_15` · 10초 → 2초 → 자동).
|
||
[Tooltip("카드가 열려 있는 동안 부활 팝업이 뜨면 자동 선택 타임아웃을 **멈춘다**(부활 뒤 남은 시간부터 재개). " +
|
||
"끄면 813o 동작 그대로 = C8 롤백.")]
|
||
public bool pauseTimeoutWhileRevive = true;
|
||
|
||
[Tooltip("카드 루트에 전용 Canvas(overrideSorting)를 붙여 그리는 순서를 값으로 고정한다. 끄면 형제 순서만 쓴다(813o 동작).")]
|
||
public bool ownCanvasEnabled = true;
|
||
|
||
[Tooltip("카드 전용 Canvas 의 sortingOrder. 🔴 813z 부활 팝업(`WLSurvivalSettings.reviveSortingOrder` = 60 실측)보다 " +
|
||
"**작아야** 부활이 카드 위로 온다(발주서 §1-3). 공용 팝업 프리팹 SortOrder_5 = 5.")]
|
||
public int cardSortingOrder = 40;
|
||
|
||
// ── 카드 ──────────────────────────────────────────────────────────────
|
||
[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;
|
||
|
||
// ── WL-813o2 (Q9 D-41) — 참조 프리팹이 없는 카드 제외 ─────────────────
|
||
// 「별」(ID 14 · Select_Star)은 `Assets/Res_Addr/Projectile/ProjectileStar.prefab` 을
|
||
// 초당 20발 요구하는데 그 파일이 없다 → InvalidKeyException 845건/런 + 결과창 모달(D-42).
|
||
// 원본 `table_selectskill.Get_DataList(n)` 은 그대로 쓰고 **결과만 후필터**한다(원본 수정 0).
|
||
[Header("풀 검증 — 부재 프리팹 카드 제외 (WL-813o2 · Q9 D-41)")]
|
||
[Tooltip("끄면 813o 동작 그대로(제외 0 · 재뽑기 0 · 키 검사 0) = C8 롤백.")]
|
||
public bool validatePool = true;
|
||
|
||
[Tooltip("뽑기에서 뺄 table_selectskill 행 ID. 에디트 모드 풀 검증(AgentScripts/WL813o2_Probe)이 실측한 목록. " +
|
||
"비우면 정적 제외 0(런타임 키 검사만 남는다).")]
|
||
public int[] excludeSkillIds = new int[] { 3, 11, 14 };
|
||
|
||
[Tooltip("런타임에도 카드마다 참조 키가 Addressables 에 있는지 검사해 없으면 뺀다(ID 당 1회 · 결과 캐시 · GC 0). " +
|
||
"카탈로그가 아직 없으면 검사를 건너뛴다(모르면 빼지 않는다).")]
|
||
public bool runtimeKeyCheck = true;
|
||
|
||
[Tooltip("제외 때문에 카드가 모자라면 원본 뽑기를 다시 부르는 최대 횟수. 0 = 재뽑기 없음.")]
|
||
public int maxRedraws = 6;
|
||
|
||
[Tooltip("제외·부재 키를 Console 에 1줄 남긴다(같은 ID 는 1회만 · Error 아님 = 모달 유발 0).")]
|
||
public bool logExclusion = true;
|
||
|
||
[Tooltip("원본 InGameInfo.Get_SkillPrefabPath 와 같은 규칙 — eEffect 이름에 'Select' 가 있으면 이 형식.")]
|
||
public string selectSkillPathFormat = "Assets/Res_Addr/SelectSkill/{0}.prefab";
|
||
|
||
[Tooltip("원본 규칙 — eEffect 이름에 'Skill' 이 있으면 이 형식.")]
|
||
public string skillPathFormat = "Assets/Res_Addr/Skill/{0}.prefab";
|
||
|
||
[Tooltip("원본 규칙 — 그 외에는 이 형식.")]
|
||
public string effectPathFormat = "Assets/Res_Addr/Effect/{0}.prefab";
|
||
|
||
[Tooltip("원본 InGameInfo.Add_SelectSkill 스위치가 **프리팹을 로드하지 않는** eEffect 이름들(실측). " +
|
||
"이 목록의 행은 1차 키를 만들지 않는다(없는 프리팹으로 오판하지 않기 위해).")]
|
||
public string[] noPrefabEffects = new string[0];
|
||
|
||
[Tooltip("원본 효과 클래스(Select_*.cs)가 코드 문자열로 추가 요구하는 키(실측 grep). " +
|
||
"1차 키와 별개로 이것들도 존재 검사한다.")]
|
||
public WLPickupExtraKey[] extraKeys = new WLPickupExtraKey[0];
|
||
|
||
// ── 타임아웃 ──────────────────────────────────────────────────────────
|
||
[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;
|
||
}
|
||
}
|
||
}
|