using UnityEngine; namespace TKS { /// 전장의 유닛 1기. 능력치·상태·콜라이더를 가진다. 시각은 UnitView 가 담당. [RequireComponent(typeof(CircleCollider2D))] public class Unit : MonoBehaviour { public string DisplayName; public UnitType Type; public Faction Faction; public bool IsBoss; public int MaxHp; public int Hp; public int Atk; public int Def; public float Radius; public float SlingMaxDistance; /// 궁병·책사 = 사거리 = 슬링샷 최대 거리(적 가장자리까지의 간격 기준 · 제자리 사격) · 창병 = 대기(견제) 반경. public float AttackRange; /// 궁병·책사가 사거리 밖(~ 사거리 × (1 + archerRangeRatio))의 적에게 다가갈 수 있는 최대 거리. 다른 병종 = 0. public float ArcherMoveAllow; /// Resources/Units/<ArtId>/ 아트 폴더 · 비면 도형. public string ArtId = ""; /// 아트 머리 끝 높이(월드) — Init = 표준 키(unitArtHeight × 반지름 배율) · 아트가 있으면 UnitView 가 크기 표 배율 뒤 실제 머리 끝으로 바꾼다. public float ArtHeight; /// 말 탄 유닛 머리 끝 상한 배율(BalanceConfig.mountedArtMaxTop · 표준 키 기준). public float MountedArtMaxTop; /// 이번 아군 턴에 행동을 마쳤는가. public bool ActedThisTurn; /// 보병이 최종 목표점 명중으로 얻은 추가 이동(이동만 가능). public bool BonusMoveAvailable; /// 창병 대기(견제) 상태 — 적군 턴에 반경 안에서 움직이는 적에게 피해. public bool Overwatch; public CircleCollider2D Collider { get; private set; } public UnitView View { get; private set; } public bool IsAlive { get { return Hp > 0; } } public Vector2 Position { get { return transform.position; } set { transform.position = new Vector3(value.x, value.y, 0f); } } public void Init(BalanceConfig cfg, UnitType type, Faction faction, string displayName, float hpMult, float atkMult, bool boss, string artId = null) { var s = cfg.GetStats(type); Type = type; Faction = faction; DisplayName = displayName; IsBoss = boss; ArtId = artId ?? ""; MaxHp = Mathf.Max(1, Mathf.RoundToInt(s.maxHp * hpMult * (boss ? cfg.bossHpMult : 1f))); Hp = MaxHp; Atk = Mathf.Max(1, Mathf.RoundToInt(s.atk * atkMult)); Def = s.def; float radiusMult = boss ? cfg.bossRadiusMult : 1f; Radius = s.radius * radiusMult; ArtHeight = cfg.unitArtHeight * radiusMult; MountedArtMaxTop = cfg.mountedArtMaxTop; SlingMaxDistance = s.slingMaxDistance; // PD 2026-11-04 #1080: 궁병 사거리 = 슬링샷 범위(제자리 사격) · 그 밖 +비율 안의 적은 들어올 만큼만 다가가 사격 · 창병은 자산의 대기 반경 // 책사(PD 2026-11-05 #1087 ⑤) = 궁병과 같은 사거리 규칙 bool ranged = UnitTypeInfo.IsRanged(type); AttackRange = ranged ? s.slingMaxDistance : s.attackRange; ArcherMoveAllow = ranged ? s.slingMaxDistance * cfg.archerRangeRatio : 0f; gameObject.name = (faction == Faction.Player ? "P_" : "E_") + displayName; Collider = GetComponent(); Collider.radius = Radius; Collider.isTrigger = false; Collider.enabled = true; View = GetComponentInChildren(); if (View == null) { var vgo = new GameObject("View"); vgo.transform.SetParent(transform, false); View = vgo.AddComponent(); } View.Build(this); } /// 스킬 테스트 씬(2026-11-09) 더미·시험 유닛: 피해는 받되 HP 1 아래로 내려가지 않음(죽지 않음). public bool Invincible; /// 진영(heroes.csv faction = shu · wei · wu · other) — 출전 영웅만 · 그 밖(적 병사 등) = 빈칸(이펙트 표에서는 other 로 봄 · 2026-11-09 진영별 돌진 이펙트). public string Camp = ""; /// 여성 영웅(heroes.csv gender f) — 스킬 조건 남성·여성(2026-11-10 #1112) · 그 밖(적 병사 포함) = 남성. public bool Female; /// heroes.csv 영웅 id(출전 영웅 · 영웅과 연결된 적 무장) · 그 밖 = 빈칸. public string HeroId = ""; public void TakeDamage(int dmg) { if (!IsAlive) return; Hp = Mathf.Max(Invincible ? 1 : 0, Hp - dmg); if (View != null) { View.OnHit(); View.Refresh(); } if (!IsAlive) Die(); } void Die() { Overwatch = false; BonusMoveAvailable = false; Collider.enabled = false; if (View != null) View.PlayDeath(); } public void SetOverwatch(bool on) { Overwatch = on && IsAlive; if (View != null) View.Refresh(); } public void ResetForNewTurn() { ActedThisTurn = false; BonusMoveAvailable = false; Overwatch = false; if (View != null) View.Refresh(); } public void MarkActed() { ActedThisTurn = true; BonusMoveAvailable = false; if (View != null) View.Refresh(); } /// 재행동(스킬) — 다시 일반 행동 1회(공격 가능). public void GrantReAction() { ActedThisTurn = false; BonusMoveAvailable = false; if (View != null) View.Refresh(); } public void GrantBonusMove() { ActedThisTurn = false; BonusMoveAvailable = true; if (View != null) View.Refresh(); } } }