using System.Collections; using UnityEngine; namespace TKS { /// /// 유닛 표시. 아트(Resources/Units/<art>/ 프레임 애니 · UnitArt)가 있으면 스프라이트(아군 = 뒷모습 back_* · 적군 = 앞모습 front_*), /// 없으면 도형(몸통 원 + 병종 글자). 공통 = 이름 · HP 바 · 선택/표적 링 · 보스 테두리 · 창병 대기 반경. /// public class UnitView : MonoBehaviour { public static readonly Color PlayerColor = new Color(0.25f, 0.55f, 0.95f); public static readonly Color EnemyColor = new Color(0.9f, 0.32f, 0.3f); static readonly Color DimColor = new Color(0.3f, 0.3f, 0.32f); static readonly Color ArtDimColor = new Color(0.45f, 0.45f, 0.5f); static readonly Color ArtHitTint = new Color(1f, 0.45f, 0.45f); const float HpBarWidth = 1.0f; const float AttackHold = 0.35f; const float HitHold = 0.3f; Unit unit; SpriteRenderer body; FrameAnimator anim; SpriteRenderer bossRing; SpriteRenderer selectRing; SpriteRenderer highlightRing; SpriteRenderer overwatchArea; SpriteRenderer hpBg; SpriteRenderer hpFill; TextMesh mark; TextMesh nameLabel; TextMesh badge; Color baseColor; Coroutine flash; bool aiming; /// 스프라이트 아트로 그리는가(false = 도형). public bool HasArt { get { return anim != null; } } public FrameAnimator Anim { get { return anim; } } public void Build(Unit u) { unit = u; foreach (Transform c in transform) Destroy(c.gameObject); baseColor = u.Faction == Faction.Player ? PlayerColor : EnemyColor; float d = u.Radius * 2f; // 창병 대기 반경 표시(창병만 · 발동 중에만 오브젝트 통째로 켬) overwatchArea = ProceduralSprites.NewSprite(transform, "OverwatchArea", ProceduralSprites.Circle, new Color(0.3f, 0.65f, 1f, 0.16f), u.Type == UnitType.Spear ? u.AttackRange * 2f : 0f, -6); var owRing = ProceduralSprites.NewSprite(overwatchArea.transform, "Ring", ProceduralSprites.Ring, new Color(0.5f, 0.8f, 1f, 0.6f), 1f, -5); owRing.transform.localScale = Vector3.one; overwatchArea.gameObject.SetActive(false); if (u.IsBoss) { bossRing = ProceduralSprites.NewSprite(transform, "BossRing", ProceduralSprites.Circle, new Color(1f, 0.85f, 0.3f), d + 0.16f, -1); } body = ProceduralSprites.NewSprite(transform, "Body", ProceduralSprites.Circle, baseColor, d, 0); // 아트가 있으면 몸통 원 대신 스프라이트 애니(발 = 원 아래쪽) anim = null; var art = UnitArt.Load(u.ArtId); if (art != null) { body.enabled = false; var ago = new GameObject("Art"); ago.transform.SetParent(transform, false); ago.transform.localPosition = new Vector3(0f, -u.Radius * 0.55f, 0f); anim = ago.AddComponent(); anim.Setup(art, u.ArtHeight, 0); anim.SetIdle(ClipName("idle")); anim.PlayIdle(); } selectRing = ProceduralSprites.NewSprite(transform, "SelectRing", ProceduralSprites.Ring, new Color(1f, 0.95f, 0.4f), d + 0.28f, 1); selectRing.enabled = false; highlightRing = ProceduralSprites.NewSprite(transform, "HighlightRing", ProceduralSprites.Ring, new Color(1f, 0.5f, 0.15f), d + 0.28f, 1); highlightRing.enabled = false; hpBg = ProceduralSprites.NewSprite(transform, "HpBg", ProceduralSprites.Square, new Color(0.08f, 0.08f, 0.08f, 0.9f), 1f, 2); hpBg.transform.localPosition = new Vector3(0f, u.Radius + 0.22f, 0f); hpBg.transform.localScale = new Vector3(HpBarWidth, 0.12f, 1f); hpFill = ProceduralSprites.NewSprite(transform, "HpFill", ProceduralSprites.Square, new Color(0.35f, 0.9f, 0.35f), 1f, 3); hpFill.transform.localPosition = hpBg.transform.localPosition; hpFill.transform.localScale = new Vector3(HpBarWidth, 0.12f, 1f); mark = TextUtil.CreateWorldText(transform, UnitTypeInfo.Mark(u.Type), u.Radius * 1.1f, Color.white, 4); if (anim != null) mark.gameObject.SetActive(false); // 아트가 있으면 병종은 이름 옆에 nameLabel = TextUtil.CreateWorldText(transform, anim != null ? u.DisplayName + "·" + UnitTypeInfo.Mark(u.Type) : u.DisplayName, 0.26f, new Color(0.95f, 0.95f, 0.9f), 4); nameLabel.transform.localPosition = new Vector3(0f, -u.Radius - 0.22f, 0f); badge = TextUtil.CreateWorldText(transform, "추가 이동", 0.22f, new Color(1f, 0.95f, 0.4f), 5); badge.transform.localPosition = new Vector3(0f, u.Radius + 0.46f, 0f); badge.gameObject.SetActive(false); Refresh(); } /// 보기(아군 = back · 적군 = front)에 맞는 클립 이름. 그 보기에 없으면 다른 보기 → 대기. string ClipName(string suffix) { string v = unit.Faction == Faction.Player ? "back" : "front"; string o = unit.Faction == Faction.Player ? "front" : "back"; if (anim.Has(v + "_" + suffix)) return v + "_" + suffix; if (anim.Has(o + "_" + suffix)) return o + "_" + suffix; return anim.Has(v + "_idle") ? v + "_idle" : o + "_idle"; } public void Refresh() { if (unit == null || hpFill == null) return; float ratio = unit.MaxHp > 0 ? Mathf.Clamp01((float)unit.Hp / unit.MaxHp) : 0f; hpFill.transform.localScale = new Vector3(HpBarWidth * ratio, 0.12f, 1f); hpFill.transform.localPosition = new Vector3(-HpBarWidth * 0.5f + HpBarWidth * ratio * 0.5f, hpBg.transform.localPosition.y, 0f); hpFill.color = ratio > 0.5f ? new Color(0.35f, 0.9f, 0.35f) : (ratio > 0.25f ? new Color(0.95f, 0.8f, 0.2f) : new Color(0.95f, 0.3f, 0.2f)); bool dim = unit.Faction == Faction.Player && unit.ActedThisTurn; if (flash == null) { if (anim != null) anim.Renderer.color = dim ? Color.Lerp(Color.white, ArtDimColor, 0.6f) : Color.white; else body.color = dim ? Color.Lerp(baseColor, DimColor, 0.6f) : baseColor; } mark.color = dim ? new Color(1f, 1f, 1f, 0.6f) : Color.white; bool ow = unit.Overwatch && unit.Type == UnitType.Spear && unit.IsAlive; if (overwatchArea.gameObject.activeSelf != ow) overwatchArea.gameObject.SetActive(ow); badge.gameObject.SetActive(unit.BonusMoveAvailable && unit.IsAlive); } public void SetSelected(bool on) { if (selectRing != null) selectRing.enabled = on; } public void SetHighlight(bool on) { if (highlightRing != null) highlightRing.enabled = on; } /// 조준(당기는) 중 = 준비 자세 · 끝나면 대기. public void SetAiming(bool on) { if (anim == null || aiming == on) return; aiming = on; if (on) anim.Play(ClipName("ready"), true); else anim.PlayIdle(); } /// 공격 자세(1 프레임) 잠깐 → 대기. public void PlayAttack() { if (anim == null || !isActiveAndEnabled) return; aiming = false; anim.Play(ClipName("action"), false, AttackHold, true); } /// 승리 동작(앞모습 · 끝 프레임 유지). public void PlayVictory() { if (anim == null || !isActiveAndEnabled || !unit.IsAlive) return; aiming = false; anim.Play(ClipName("victory"), false); } public void OnHit() { if (!isActiveAndEnabled) return; if (flash != null) StopCoroutine(flash); flash = StartCoroutine(FlashRoutine()); } IEnumerator FlashRoutine() { if (anim != null) { anim.Renderer.color = ArtHitTint; if (unit.IsAlive) anim.Play(ClipName("damage"), false, HitHold, true); } else { body.color = Color.white; } float t = 0f; while (t < 0.18f) { t += Time.deltaTime; yield return null; } flash = null; Refresh(); } public void PlayDeath() { if (!isActiveAndEnabled) return; StartCoroutine(DeathRoutine()); } IEnumerator DeathRoutine() { selectRing.enabled = false; highlightRing.enabled = false; overwatchArea.gameObject.SetActive(false); if (anim != null) anim.Play(ClipName("damage"), false, 99f, false); float t = 0f; const float dur = 0.45f; Vector3 s0 = transform.localScale; var sr = anim != null ? anim.Renderer : body; while (t < dur) { t += Time.deltaTime; float k = Mathf.Clamp01(t / dur); transform.localScale = s0 * (1f - 0.5f * k); var c = sr.color; c.a = 1f - k; sr.color = c; yield return null; } unit.gameObject.SetActive(false); } } }