using UnityEngine; namespace TKS { /// /// 슬링샷 방향 결정 UI — IB(쿠로네코) GameTouchArrow 이식(PD 2026-11-10 「IB 와 같이 슬링샷 조작 시 방향 결정 UI 등을 그대로 이식」). /// 프리팹 = Resources/Fx/TKS_Fx_IB_GameArrow(원본 Assets/VFX/IB/AssetResources/Game/GameArrow · 사본 = Tools/make_ib_fx_copies.cs · IB 스크립트 제거). /// IB 와 같은 동작: 누르면(Ready) 유닛 둘레에 고리 1개 + 퍼지는 고리 3개(3초 동안 0 → 1.3배 · 투명해짐 · 1초 간격 · 반복) · /// 당기면 화살표가 쏠 방향으로 돌고(정면 +Y) 당긴 세기만큼 길어짐(가로 0.8 + v × 0.2 · 세로 v · v = 0.5~1.5) · 화살 끝이 전장 밖이면 안으로 밀어 넣음 · 놓으면(End) 숨김. /// IB 세기 v = 당긴 거리 × 0.6 → 이 게임은 슬링샷 최대 거리 대비 비율로 0.5~1.5 에 맞춘다. 색 = IB 공격 종류 3(하늘색 · 프리팹 기본값). /// public class IbAimArrow : MonoBehaviour { public const string PrefabName = "TKS_Fx_IB_GameArrow"; /// IB GameArrow 공격 종류 3 색(하늘색 · 원본 프리팹 color[2]). static readonly Color RingColor = new Color(0.468f, 0.866f, 0.897f, 1f); const float RingPulseSeconds = 3f; const float RingPulseScale = 1.3f; const float RingStagger = 1f; /// 바닥 고리 정렬(사거리 원 −10·−9 와 같은 바닥 층) · 화살표 = 조준선과 같은 10. const int RingOrder = -9; const int ArrowOrder = 10; Transform root; SpriteRenderer arrow; Transform arrowTip; SpriteRenderer ring; SpriteRenderer[] pulses = new SpriteRenderer[0]; float readyTime; bool ready; Board board; public bool Ready { get { return ready; } } public bool ArrowVisible { get { return arrow != null && arrow.gameObject.activeSelf && ready; } } public void Init(Board b) { board = b; var prefab = SkillVfx.LoadFx(PrefabName); if (prefab == null) return; var go = Instantiate(prefab, transform); go.name = PrefabName; root = go.transform; foreach (var sr in go.GetComponentsInChildren(true)) { if (sr.name == "arrow") arrow = sr; else if (sr.name == "ring01") ring = sr; } var list = new System.Collections.Generic.List(); foreach (var n in new[] { "ring02-01", "ring02-02", "ring02-03" }) { var t = root.Find(n); if (t != null && t.GetComponent() != null) list.Add(t.GetComponent()); } pulses = list.ToArray(); int ringOrder = Mathf.RoundToInt(IbUi.Num("order_ground", RingOrder)); if (arrow != null) { arrowTip = arrow.transform.Find("object"); arrow.sortingOrder = Mathf.RoundToInt(IbUi.Num("order_arrow", ArrowOrder)); } if (ring != null) { ring.sortingOrder = ringOrder; ring.color = RingColor; } foreach (var p in pulses) p.sortingOrder = ringOrder; go.SetActive(false); } /// IB Ready: 유닛을 누른 순간 — 고리 표시 · 화살표는 당길 때까지 숨김. public void Begin(Unit u) { if (root == null || u == null) return; ready = true; readyTime = Time.time; root.gameObject.SetActive(true); Place(u); if (arrow != null) arrow.gameObject.SetActive(false); } /// /// 유닛 크기에 맞춰 놓기 — IB 와 같은 비율(PD 2026-11-10 「IB 와 화살표 크기도 다름」): 배율 = 이 게임 유닛 반지름 / IB 유닛 반지름 0.496(표 ib_ui.csv) · /// 그러면 고리 지름 = 반지름 × 5.4 · 화살표 = 반지름 × 5.9~17.8 길이(IB 실측 · 화살 그림 피벗 y 0.3 = 유닛 가운데). /// void Place(Unit u) { root.position = new Vector3(u.Position.x, u.Position.y, 0f); float k = IbUi.Scale(u); root.localScale = new Vector3(k, k, 1f); } /// /// 당기는 중: launch = 쏠 방향·거리(월드 · 목표점 − 유닛) — IB SetTouchArrowScale 그대로: IB 월드로 바꾼 당김 × 0.6 = 세기 s(0.5~1.5) · /// 가로 0.8 + 0.2 s · 세로 s · 당김이 IB 취소 거리(0.5) 안이면 화살표 숨김(고리만). /// public void Aim(Unit u, Vector2 launch) { if (root == null || u == null) return; if (!ready) Begin(u); Place(u); if (arrow == null) return; float dragIb = launch.magnitude / Mathf.Max(0.001f, IbUi.Scale(u)); bool on = dragIb >= IbUi.Num("arrow_cancel", 0.5f); if (arrow.gameObject.activeSelf != on) arrow.gameObject.SetActive(on); if (!on) return; arrow.transform.localPosition = Vector3.zero; arrow.transform.rotation = Quaternion.FromToRotation(Vector3.up, new Vector3(launch.x, launch.y, 0f)); float v = Mathf.Clamp(dragIb * IbUi.Num("arrow_drag_k", 0.6f), IbUi.Num("arrow_s_min", 0.5f), IbUi.Num("arrow_s_max", 1.5f)); arrow.transform.localScale = new Vector3(0.8f + v * 0.2f, v, 1f); // IB LateUpdate: 화살 끝이 벽(전장 밖)을 넘으면 화살 전체를 안으로 밀어 넣음 if (board != null && arrowTip != null) { Vector2 tip = arrowTip.position; Vector2 inside = board.ClampInside(tip, 0f); if ((inside - tip).sqrMagnitude > 1e-6f) arrow.transform.position += (Vector3)(inside - tip); } } /// IB End: 놓거나 취소 — 모두 숨김. public void End() { ready = false; if (root != null) root.gameObject.SetActive(false); } void Update() { if (!ready || root == null) return; float t = Time.time - readyTime; for (int i = 0; i < pulses.Length; i++) { float local = t - i * RingStagger; var p = pulses[i]; if (local < 0f) { p.transform.localScale = Vector3.zero; continue; } float k = (local % RingPulseSeconds) / RingPulseSeconds; p.transform.localScale = new Vector3(RingPulseScale * k, RingPulseScale * k, 1f); var c = RingColor; c.a = 1f - k; p.color = c; } } } }