Project_WL/Assets/WL/UI/Scripts/WLBattlePadLayout.cs

316 lines
17 KiB
C#
Raw Permalink Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLBattlePadLayout.cs — 전투 패드(우하단 엄지 부채꼴) 배치 드라이버 (WL-813c · #813)
//
// 기준서 v1 §F-1 권고: 오른손 엄지 부채꼴 **4슬롯** + 물약 1 + 회피 1 · 좌우 미러 옵션.
// §B 요소 3: 버튼 지름 132~150 px · 중심 간격 ≥16 px · 요소 7: Safe Area 안 · 한 손 도달.
//
// ■ 설계
// · 원본 BattleUI.prefab 의 **배열을 줄이지 않는다**(skillCards 6 그대로 · 원복 가능).
// 활성 슬롯 수만 설정으로 정하고, 남는 슬롯은 물약/회피(813j/813r) 자리로 **비활성**으로 둔다.
// · 값은 전부 WLHudLayoutSettings(에셋). 이 파일에 좌표·지름·각도 상수는 없다(C45).
// · 배치는 런타임에만 한다([ExecuteAlways] 아님) — 프리팹 YAML 에 좌표를 굽지 않아
// NewGameUI.prefab diff 를 "노드/컴포넌트 추가"로만 유지한다(발주서 ⓓ).
// · 이 컴포넌트는 **NewGameUI 안 IngameUIs/BattleUI 인스턴스에만** 붙인다.
// 같은 BattleUI.prefab 을 쓰는 SkillUI/SkillEquipUI 인스턴스는 손대지 않는다(회귀 0).
//
// ■ Safe Area
// 같은 오브젝트의 SafeAreaFitter(802b)가 루트를 안전 영역에 맞춘다. 이 스크립트는 루트 rect 를
// 건드리지 않고(피터가 주인) 자식만 **우하단(미러 시 좌하단) 모서리 앵커**로 배치한다.
// 피터가 없으면(단독 테스트) 루트를 스트레치로 정규화한다.
//
// ■ 롤백(C8)
// 설정 에셋이 없거나 battlePadEnabled=false 면 아무 것도 만지지 않는다 = 원본 배치 그대로.
//
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
// ─────────────────────────────────────────────────────────────────────────────
using System.Text;
using UnityEngine;
using UnityEngine.UI;
namespace WL.UI
{
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
public class WLBattlePadLayout : MonoBehaviour
{
[Tooltip("배치 대상 — 비우면 같은 오브젝트의 BattleUI 를 찾는다")]
[SerializeField] private BattleUI target;
[Tooltip("자동전투 토글 오브젝트 이름(BattleUI 자식) — 못 찾으면 건너뛴다")]
[SerializeField] private string autoButtonName = "btn_auto";
[Tooltip("공격 버튼 오브젝트 이름(BattleUI 자식) — 못 찾으면 건너뛴다")]
[SerializeField] private string attackButtonName = "Attack";
private RectTransform _rt;
private Canvas _canvas;
private bool _lastMirror;
private Vector2Int _lastScreen;
// 마지막 적용 결과 — 프로브(에디터)가 읽는 진단 값. 실측 외에는 채우지 않는다.
public float LastUnitsPerPx { get; private set; }
public float LastMaxReachPx { get; private set; }
public float LastMinGapPx { get; private set; }
public int LastActiveSlots { get; private set; }
public int LastReserveSlots { get; private set; }
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
/// <summary>전투 액션 버튼(스킬 4 + 공격 + 물약)만의 도달 반경 — 자동전투 토글 제외(WL-813y).</summary>
public float LastActionReachPx { get; private set; }
/// <summary>마지막 배치 때의 하단 메뉴 상태(true = 메뉴 보임 → 패드가 idle 자리로 올라감).</summary>
public bool LastBottomMenuVisible { get; private set; }
private void Awake() { _rt = GetComponent<RectTransform>(); }
private void OnEnable() { Apply(); }
private void Update()
{
// 해상도 전환·설정 토글에만 다시 배치한다(매 프레임 계산 없음).
var s = WLHudLayoutSettings.Instance;
if (s == null) return;
if (Screen.width != _lastScreen.x || Screen.height != _lastScreen.y || s.mirrorLeftHanded != _lastMirror)
Apply();
}
/// <summary>BattleUI.Set() 직후 훅 — 서버 프리셋이 슬롯을 다시 채운 뒤 배치를 되살린다.</summary>
public static void NotifySet(BattleUI ui)
{
if (ui == null) return;
var layout = ui.GetComponent<WLBattlePadLayout>();
if (layout != null && layout.isActiveAndEnabled) layout.Apply();
}
/// <summary>설정 값대로 슬롯·공격·자동 버튼을 재배치한다. 설정이 없으면 아무 것도 하지 않는다.</summary>
public string Apply()
{
if (_rt == null) _rt = GetComponent<RectTransform>();
if (target == null) target = GetComponent<BattleUI>();
var s = WLHudLayoutSettings.Instance;
if (s == null) return "WLHudLayoutSettings 에셋 없음 — 배치 건너뜀(원본 유지)";
if (!s.battlePadEnabled) return "battlePadEnabled=false — 배치 건너뜀(원본 유지)";
if (target == null) return "BattleUI 없음 — 배치 건너뜀";
_lastScreen = new Vector2Int(Screen.width, Screen.height);
_lastMirror = s.mirrorLeftHanded;
// Safe Area 는 SafeAreaFitter 가 주인 — 없을 때만 루트를 스트레치로 정규화한다.
if (GetComponent<SafeAreaFitter>() == null)
{
_rt.anchorMin = Vector2.zero; _rt.anchorMax = Vector2.one;
_rt.offsetMin = Vector2.zero; _rt.offsetMax = Vector2.zero;
}
float u = ComputeUnitsPerPx(s);
LastUnitsPerPx = u;
var cards = target.skillCards;
int total = cards != null ? cards.Length : 0;
// 🔴 WL-816u ③ — PD 「우선 엑티브 스킬은 사용 Off 해줘」. 잠금이 걸려 있으면 활성 슬롯 0
// = 스킬 버튼 4칸이 전부 비활성(아래 SetActive(isSkill)). 되돌리기는 설정 값 하나.
int want = WL.Combat.Auto.ActiveSkillLock.Locked ? 0 : s.activeSkillSlots;
int active = Mathf.Clamp(want, 0, total);
LastActiveSlots = active;
LastReserveSlots = Mathf.Max(0, total - active);
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
float maxReach = 0f, actionReach = 0f;
var centers = new Vector2[total];
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
// WL-813y — 하단 메뉴가 보이는 동안만 패드를 위로 올린다(전투 중에는 모서리 원점).
bool menuVisible = HudCombatVisibility.BottomMenuVisible;
LastBottomMenuVisible = menuVisible;
for (int i = 0; i < total; i++)
{
var card = cards[i];
if (card == null) continue;
var crt = card.transform as RectTransform;
if (crt == null) continue;
bool isSkill = i < active;
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
Vector2 px = isSkill ? s.FanSlotPx(i, menuVisible) : s.ReserveSlotPx(i - active, menuVisible);
float dia = isSkill ? s.slotDiameterPx : s.reserveDiameterPx;
PlaceFromCorner(crt, px, dia, u, s.mirrorLeftHanded);
centers[i] = px;
maxReach = Mathf.Max(maxReach, px.magnitude + dia * 0.5f);
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
if (isSkill) actionReach = Mathf.Max(actionReach, px.magnitude + dia * 0.5f);
// 4 활성 · 나머지는 물약/회피(813j/813r) 예약 자리 = 비활성
if (card.gameObject.activeSelf != isSkill) card.gameObject.SetActive(isSkill);
}
// 공격 버튼 · 자동전투 토글
var attack = FindChild(attackButtonName);
if (attack != null)
{
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
Vector2 apx = s.AttackCenterPxFor(menuVisible);
PlaceFromCorner(attack, apx, s.attackDiameterPx, u, s.mirrorLeftHanded);
maxReach = Mathf.Max(maxReach, apx.magnitude + s.attackDiameterPx * 0.5f);
actionReach = Mathf.Max(actionReach, apx.magnitude + s.attackDiameterPx * 0.5f);
}
var auto = FindChild(autoButtonName);
if (auto != null)
{
PlaceFromCorner(auto, s.autoButtonCenterPx, s.autoButtonDiameterPx, u, s.mirrorLeftHanded);
maxReach = Mathf.Max(maxReach, s.autoButtonCenterPx.magnitude + s.autoButtonDiameterPx * 0.5f);
}
LastMaxReachPx = maxReach;
LastMinGapPx = MinGapPx(s, centers, active, total);
// 813tj 물약 버튼 — 예약 슬롯 자리에 있으므로 미러·해상도가 바뀌면 같이 다시 앉힌다.
var potion = GetComponentInChildren<PotionButton>(true);
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
if (potion != null)
{
potion.ApplyLayout();
Vector2 ppx = s.ReserveSlotPx(Mathf.Max(0, PotionSlotIndex()), menuVisible);
actionReach = Mathf.Max(actionReach, ppx.magnitude + s.reserveDiameterPx * 0.5f);
}
LastActionReachPx = actionReach;
return "applied slots=" + active + "/" + total + " unitsPerPx=" + u.ToString("F4") +
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
" maxReachPx=" + maxReach.ToString("F1") +
" actionReachPx=" + actionReach.ToString("F1") + "(스킬+공격+물약 · 자동토글 제외)" +
" minGapPx=" + LastMinGapPx.ToString("F1") +
" menuVisible=" + menuVisible + " fanOrigin=" + s.FanOriginPxFor(menuVisible) +
" mirror=" + s.mirrorLeftHanded;
}
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
private static int PotionSlotIndex()
{
var su = WLSurvivalUiSettings.Instance;
return su != null ? su.potionReserveSlotIndex : 0;
}
/// <summary>
/// 모서리(우하단 · 미러면 좌하단) 기준 (dx, dy) px 로 배치한다.
/// 813tj 물약 버튼(PotionButton)이 예약 슬롯 자리에 앉을 때 **같은 산식**을 쓰도록 public 이다.
/// </summary>
public static void PlaceFromCorner(RectTransform rt, Vector2 px, float diameterPx, float unitsPerPx, bool mirror)
{
Vector2 anchor = new Vector2(mirror ? 0f : 1f, 0f);
rt.anchorMin = anchor;
rt.anchorMax = anchor;
rt.pivot = new Vector2(0.5f, 0.5f);
float sx = mirror ? px.x : -px.x;
rt.anchoredPosition = new Vector2(sx * unitsPerPx, px.y * unitsPerPx);
rt.sizeDelta = new Vector2(diameterPx * unitsPerPx, diameterPx * unitsPerPx);
rt.localScale = Vector3.one;
}
/// <summary>이웃 슬롯 중심 거리 지름 = 실제 여백(px). 기준서 §B 요소3 ≥16 검증용.</summary>
private static float MinGapPx(WLHudLayoutSettings s, Vector2[] centers, int active, int total)
{
float min = float.MaxValue;
for (int i = 0; i < total; i++)
{
for (int j = i + 1; j < total; j++)
{
float ri = (i < active ? s.slotDiameterPx : s.reserveDiameterPx) * 0.5f;
float rj = (j < active ? s.slotDiameterPx : s.reserveDiameterPx) * 0.5f;
float gap = Vector2.Distance(centers[i], centers[j]) - ri - rj;
if (gap < min) min = gap;
}
}
return min == float.MaxValue ? 0f : min;
}
/// <summary>px → 캔버스 유닛 계수. 산식은 WLHudLayoutSettings 가 갖는다(보스 바와 같은 것을 쓴다).</summary>
public float ComputeUnitsPerPx(WLHudLayoutSettings s)
{
if (_canvas == null) _canvas = GetComponentInParent<Canvas>(true);
return s.UnitsPerPx(_canvas);
}
private RectTransform FindChild(string childName)
{
if (string.IsNullOrEmpty(childName)) return null;
for (int i = 0; i < transform.childCount; i++)
{
var c = transform.GetChild(i);
if (c.name == childName) return c as RectTransform;
}
return null;
}
// ── 검증·디버그 전용 ──────────────────────────────────────────────────
/// <summary>런타임 미러 토글(로드된 설정 인스턴스만 바꾼다 — .asset 파일은 건드리지 않는다).</summary>
public string SetMirror(bool mirror)
{
var s = WLHudLayoutSettings.Instance;
if (s == null) return "WLHudLayoutSettings 에셋 없음";
s.mirrorLeftHanded = mirror;
return Apply();
}
/// <summary>현재 배치 덤프(슬롯별 앵커·좌표·크기 · px 환산). 발주서 ⓑ 증거용.</summary>
public string Dump()
{
var sb = new StringBuilder();
var s = WLHudLayoutSettings.Instance;
float u = LastUnitsPerPx > 0f ? LastUnitsPerPx : (s != null ? ComputeUnitsPerPx(s) : 1f);
sb.AppendLine("[WLBattlePadLayout] unitsPerPx=" + u.ToString("F4") +
" mirror=" + (s != null ? s.mirrorLeftHanded.ToString() : "?") +
" activeSlots=" + LastActiveSlots + "/" + (LastActiveSlots + LastReserveSlots) +
" maxReachPx=" + LastMaxReachPx.ToString("F1") +
" (목표 " + (s != null ? s.thumbReachRadiusPx.ToString("F0") : "?") + ")" +
[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
" actionReachPx=" + LastActionReachPx.ToString("F1") + "(자동토글 제외)" +
" menuVisible=" + LastBottomMenuVisible +
" fanOrigin=" + (s != null ? s.FanOriginPxFor(LastBottomMenuVisible).ToString() : "?") +
" attackCenter=" + (s != null ? s.AttackCenterPxFor(LastBottomMenuVisible).ToString() : "?") +
" minGapPx=" + LastMinGapPx.ToString("F1"));
sb.AppendLine(" root " + RectLine(_rt != null ? _rt : GetComponent<RectTransform>()) +
" safeAreaFitter=" + (GetComponent<SafeAreaFitter>() != null));
var ui = target != null ? target : GetComponent<BattleUI>();
if (ui != null && ui.skillCards != null)
{
for (int i = 0; i < ui.skillCards.Length; i++)
{
var c = ui.skillCards[i];
if (c == null) { sb.AppendLine(" slot" + i + " : null"); continue; }
var crt = c.transform as RectTransform;
sb.AppendLine(" slot" + i + " " + c.name + " active=" + c.gameObject.activeSelf +
" " + RectLine(crt) + " " + PxLine(crt, u));
sb.AppendLine(" 쿨링 " + CoolRingLine(c));
}
}
foreach (var n in new[] { attackButtonName, autoButtonName, "Avoid", "Equip" })
{
var t = FindChild(n);
sb.AppendLine(" " + n + (t == null ? " : 없음"
: " active=" + t.gameObject.activeSelf + " " + RectLine(t) + " " + PxLine(t, u)));
}
return sb.ToString();
}
/// <summary>쿨 링 실측 — SkillCard 원본의 i_cooltime(Filled/Radial360)이 배치 뒤에도 유효한지(발주서 ⓑ "쿨 링").</summary>
private static string CoolRingLine(SkillCard card)
{
var img = card != null ? card.sprite_cooltime : null;
if (img == null) return "sprite_cooltime 없음";
var irt = img.rectTransform;
return img.gameObject.name + " type=" + img.type + " fillMethod=" + img.fillMethod +
" origin=" + img.fillOrigin + " cw=" + img.fillClockwise + " amount=" + img.fillAmount.ToString("F3") +
" active=" + img.gameObject.activeInHierarchy +
" aMin" + irt.anchorMin + " aMax" + irt.anchorMax + " size" + irt.sizeDelta +
" · 구동 = PCActor.cs:91 Set_CoolTime → CardCoolTimeBase.Set_CoolTimeText";
}
private static string RectLine(RectTransform rt)
{
if (rt == null) return "(rt 없음)";
return "aMin" + rt.anchorMin + " aMax" + rt.anchorMax + " pos" + rt.anchoredPosition +
" size" + rt.sizeDelta + " piv" + rt.pivot + " scl" + (Vector2)(Vector3)rt.localScale;
}
private static string PxLine(RectTransform rt, float u)
{
if (rt == null || u <= 0f) return "";
return "→ px(dx=" + Mathf.Abs(rt.anchoredPosition.x / u).ToString("F1") +
", dy=" + (rt.anchoredPosition.y / u).ToString("F1") +
", d=" + (rt.sizeDelta.x / u).ToString("F1") + ")";
}
}
}