using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace TKS { /// /// 보스 스테이지 시작 연출(2026-11-12 PD 「보스는 등장할 때 포커스 + 짤막한 대사(말풍선)」 · 「예시처럼 보스 등장 연출」 · 개발 요청서 C2). /// ① 경고(위아래 검은 띠 + 붉은 가장자리 깜빡임) → ② 보스 포커스(카메라 이동·확대 · 다른 유닛 어둡게 · 발밑 붉은 원) → /// ③ 컷인(위 「BOSS 등장!」 붉은 줄무늬 띠 → 보스 전신 + 아래 이름 띠) → ④ 말풍선 대사(타자 효과 · 2인 보스는 n_order 순) → ⑤ 아군 시야로 복귀. /// 값 = ScenarioData.Cfg(boss_intro_config.csv) · 대사 = boss_intro.csv · 그림 = Resources/Scenario/*.png(띠·글자·말풍선 · 없으면 색 면) + 보스 유닛 그림. /// 연출 동안 전투 단계 = Setup(아군 조작 잠금) · 끝나면 onDone(아군 차례). Sim.Active(적 AI 가상 실행) 중에는 아무것도 하지 않는다. /// public class BossIntro : MonoBehaviour { static BossIntro current; BattleManager battle; CameraController cc; Action onDone; List bosses = new List(); List lines; bool withCutin; bool skip; int taps; Vector3 camStart; float viewStart; readonly Dictionary dimmed = new Dictionary(); readonly List rings = new List(); RectTransform root, fx; Image dim, edge, barTop, barBottom; Button skipBtn; static float C(string k) { return ScenarioData.Cfg(k); } /// 이 스테이지에 연출을 틀지(보스 행 있음 · 가상 실행 아님 · 다시 출정 = replay_mode 2 면 안 틂). public static bool Wants(BalanceConfig.StageDef stage, bool cleared) { if (Sim.Active || stage == null || stage.kind != StageKind.Boss) return false; if (ScenarioData.Bosses(stage.chapter, stage.stage).Count == 0) return false; return !(cleared && Mathf.RoundToInt(C("replay_mode")) == 2); } /// 연출 시작 — cleared = 이미 깬 스테이지(replay_mode 1 = 컷인 빼고 ②④⑤). public static void Begin(BattleManager battle, BalanceConfig.StageDef stage, bool cleared, Action onDone) { Abort(); var cc = FindFirstObjectByType(); if (cc == null || cc.Cam == null) { onDone?.Invoke(); return; } var go = new GameObject("BossIntroUI"); var bi = go.AddComponent(); current = bi; bi.battle = battle; bi.cc = cc; bi.onDone = onDone; bi.lines = ScenarioData.Bosses(stage.chapter, stage.stage); bi.withCutin = !(cleared && Mathf.RoundToInt(C("replay_mode")) == 1); foreach (var l in bi.lines) { Unit hit = null; foreach (var u in battle.AliveUnits(Faction.Enemy)) if (u.IsBoss && u.ArtId == l.id) { hit = u; break; } if (hit == null) foreach (var u in battle.AliveUnits(Faction.Enemy)) if (u.IsBoss && !bi.bosses.Contains(u)) { hit = u; break; } bi.bosses.Add(hit); } bi.Build(); bi.StartCoroutine(bi.Run()); } /// 진행 중 연출을 바로 치움(전투 재시작 · 월드맵 복귀) — 어둡게·원·카메라 상태를 되돌린다. public static void Abort() { if (current == null) return; current.Restore(); Destroy(current.gameObject); current = null; } // ---------- 화면 ---------- static Sprite Spr(string name) { return Resources.Load(ScenarioData.Root + name); } static RectTransform Rt(string name, Transform parent, Vector2 aMin, Vector2 aMax, Vector2 pivot, Vector2 pos, Vector2 size) { var go = new GameObject(name, typeof(RectTransform)); go.transform.SetParent(parent, false); var rt = (RectTransform)go.transform; rt.anchorMin = aMin; rt.anchorMax = aMax; rt.pivot = pivot; rt.anchoredPosition = pos; rt.sizeDelta = size; return rt; } static Image Img(string name, Transform parent, Sprite sp, Color col, Vector2 aMin, Vector2 aMax, Vector2 pivot, Vector2 pos, Vector2 size) { var img = Rt(name, parent, aMin, aMax, pivot, pos, size).gameObject.AddComponent(); img.sprite = sp; img.color = col; img.raycastTarget = false; return img; } static Image Full(string name, Transform parent, Sprite sp, Color col) { return Img(name, parent, sp, col, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero); } static Text Txt(Transform parent, string s, int size, Color col, TextAnchor anchor, Vector2 pos, Vector2 box, Font font) { var t = Rt("Text", parent, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), pos, box).gameObject.AddComponent(); t.font = font; t.fontSize = size; t.color = col; t.alignment = anchor; t.text = s; t.supportRichText = false; t.raycastTarget = false; t.horizontalOverflow = HorizontalWrapMode.Wrap; t.verticalOverflow = VerticalWrapMode.Overflow; return t; } static void Stroke(Text t, Color col, float d) { var o = t.gameObject.AddComponent(); o.effectColor = col; o.effectDistance = new Vector2(d, -d); var o2 = t.gameObject.AddComponent(); o2.effectColor = col; o2.effectDistance = new Vector2(-d, d); } void Build() { UiLayer.Root(gameObject, cc.Cam, 460, 0.38f, UiLayer.Reference, UiRate.Event); // 아웃게임 위 덮개(재화 바 · 450)보다 위 · 연출 동안 화면 전체를 덮음 root = (RectTransform)transform; // 누름 받이(탭 1 = 대사 전부 · 탭 2 = 닫기) — 전체 화면 · 투명 var catcher = Full("Tap", root, null, new Color(0f, 0f, 0f, 0f)); catcher.raycastTarget = true; catcher.gameObject.AddComponent