ThreeKingdomsSlingshot/Assets/Scripts/Runtime/Unit.cs

158 lines
6.3 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
namespace TKS
{
/// <summary>전장의 유닛 1기. 능력치·상태·콜라이더를 가진다. 시각은 UnitView 가 담당.</summary>
[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;
/// <summary>궁병·책사 = 사거리 = 슬링샷 최대 거리(적 가장자리까지의 간격 기준 · 제자리 사격) · 창병 = 대기(견제) 반경.</summary>
public float AttackRange;
/// <summary>궁병·책사가 사거리 밖(~ 사거리 × (1 + archerRangeRatio))의 적에게 다가갈 수 있는 최대 거리. 다른 병종 = 0.</summary>
public float ArcherMoveAllow;
/// <summary>Resources/Units/&lt;ArtId&gt;/ 아트 폴더 · 비면 도형.</summary>
public string ArtId = "";
/// <summary>아트 머리 끝 높이(월드) — Init = 표준 키(unitArtHeight × 반지름 배율) · 아트가 있으면 UnitView 가 크기 표 배율 뒤 실제 머리 끝으로 바꾼다.</summary>
public float ArtHeight;
/// <summary>말 탄 유닛 머리 끝 상한 배율(BalanceConfig.mountedArtMaxTop · 표준 키 기준).</summary>
public float MountedArtMaxTop;
/// <summary>이번 아군 턴에 행동을 마쳤는가.</summary>
public bool ActedThisTurn;
/// <summary>보병이 최종 목표점 명중으로 얻은 추가 이동(이동만 가능).</summary>
public bool BonusMoveAvailable;
/// <summary>창병 대기(견제) 상태 — 적군 턴에 반경 안에서 움직이는 적에게 피해.</summary>
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<CircleCollider2D>();
Collider.radius = Radius;
Collider.isTrigger = false;
Collider.enabled = true;
View = GetComponentInChildren<UnitView>();
if (View == null)
{
var vgo = new GameObject("View");
vgo.transform.SetParent(transform, false);
View = vgo.AddComponent<UnitView>();
}
View.Build(this);
}
/// <summary>스킬 테스트 씬(2026-11-09) 더미·시험 유닛: 피해는 받되 HP 1 아래로 내려가지 않음(죽지 않음).</summary>
public bool Invincible;
/// <summary>진영(heroes.csv faction = shu · wei · wu · other) — 출전 영웅만 · 그 밖(적 병사 등) = 빈칸(이펙트 표에서는 other 로 봄 · 2026-11-09 진영별 돌진 이펙트).</summary>
public string Camp = "";
/// <summary>여성 영웅(heroes.csv gender f) — 스킬 조건 남성·여성(2026-11-10 #1112) · 그 밖(적 병사 포함) = 남성.</summary>
public bool Female;
/// <summary>heroes.csv 영웅 id(출전 영웅 · 영웅과 연결된 적 무장) · 그 밖 = 빈칸.</summary>
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();
}
/// <summary>재행동(스킬) — 다시 일반 행동 1회(공격 가능).</summary>
public void GrantReAction()
{
ActedThisTurn = false;
BonusMoveAvailable = false;
if (View != null) View.Refresh();
}
public void GrantBonusMove()
{
ActedThisTurn = false;
BonusMoveAvailable = true;
if (View != null) View.Refresh();
}
}
}