2026-11-03 19:10:10 +00:00
|
|
|
|
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;
|
2026-11-05 04:06:32 +00:00
|
|
|
|
/// <summary>궁병·책사 = 사거리 = 슬링샷 최대 거리(적 가장자리까지의 간격 기준 · 제자리 사격) · 창병 = 대기(견제) 반경.</summary>
|
2026-11-03 19:10:10 +00:00
|
|
|
|
public float AttackRange;
|
2026-11-05 04:06:32 +00:00
|
|
|
|
/// <summary>궁병·책사가 사거리 밖(~ 사거리 × (1 + archerRangeRatio))의 적에게 다가갈 수 있는 최대 거리. 다른 병종 = 0.</summary>
|
2026-11-04 06:56:02 +00:00
|
|
|
|
public float ArcherMoveAllow;
|
2026-11-04 04:05:46 +00:00
|
|
|
|
/// <summary>Resources/Units/<ArtId>/ 아트 폴더 · 비면 도형.</summary>
|
|
|
|
|
|
public string ArtId = "";
|
2026-11-09 10:23:50 +00:00
|
|
|
|
/// <summary>아트 머리 끝 높이(월드) — Init = 표준 키(unitArtHeight × 반지름 배율) · 아트가 있으면 UnitView 가 크기 표 배율 뒤 실제 머리 끝으로 바꾼다.</summary>
|
2026-11-04 04:05:46 +00:00
|
|
|
|
public float ArtHeight;
|
2026-11-09 10:33:00 +00:00
|
|
|
|
/// <summary>말 탄 유닛 머리 끝 상한 배율(BalanceConfig.mountedArtMaxTop · 표준 키 기준).</summary>
|
|
|
|
|
|
public float MountedArtMaxTop;
|
2026-11-03 19:10:10 +00:00
|
|
|
|
|
|
|
|
|
|
/// <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); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-04 04:05:46 +00:00
|
|
|
|
public void Init(BalanceConfig cfg, UnitType type, Faction faction, string displayName, float hpMult, float atkMult, bool boss, string artId = null)
|
2026-11-03 19:10:10 +00:00
|
|
|
|
{
|
|
|
|
|
|
var s = cfg.GetStats(type);
|
|
|
|
|
|
Type = type;
|
|
|
|
|
|
Faction = faction;
|
|
|
|
|
|
DisplayName = displayName;
|
|
|
|
|
|
IsBoss = boss;
|
2026-11-04 04:05:46 +00:00
|
|
|
|
ArtId = artId ?? "";
|
2026-11-03 19:10:10 +00:00
|
|
|
|
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;
|
2026-11-04 04:05:46 +00:00
|
|
|
|
float radiusMult = boss ? cfg.bossRadiusMult : 1f;
|
|
|
|
|
|
Radius = s.radius * radiusMult;
|
|
|
|
|
|
ArtHeight = cfg.unitArtHeight * radiusMult;
|
2026-11-09 10:33:00 +00:00
|
|
|
|
MountedArtMaxTop = cfg.mountedArtMaxTop;
|
2026-11-03 19:10:10 +00:00
|
|
|
|
SlingMaxDistance = s.slingMaxDistance;
|
2026-11-04 06:56:02 +00:00
|
|
|
|
// PD 2026-11-04 #1080: 궁병 사거리 = 슬링샷 범위(제자리 사격) · 그 밖 +비율 안의 적은 들어올 만큼만 다가가 사격 · 창병은 자산의 대기 반경
|
2026-11-05 04:06:32 +00:00
|
|
|
|
// 책사(PD 2026-11-05 #1087 ⑤) = 궁병과 같은 사거리 규칙
|
|
|
|
|
|
bool ranged = UnitTypeInfo.IsRanged(type);
|
|
|
|
|
|
AttackRange = ranged ? s.slingMaxDistance : s.attackRange;
|
|
|
|
|
|
ArcherMoveAllow = ranged ? s.slingMaxDistance * cfg.archerRangeRatio : 0f;
|
2026-11-03 19:10:10 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-09 14:12:52 +00:00
|
|
|
|
/// <summary>스킬 테스트 씬(2026-11-09) 더미·시험 유닛: 피해는 받되 HP 1 아래로 내려가지 않음(죽지 않음).</summary>
|
|
|
|
|
|
public bool Invincible;
|
|
|
|
|
|
|
2026-11-10 04:19:44 +00:00
|
|
|
|
/// <summary>진영(heroes.csv faction = shu · wei · wu · other) — 출전 영웅만 · 그 밖(적 병사 등) = 빈칸(이펙트 표에서는 other 로 봄 · 2026-11-09 진영별 돌진 이펙트).</summary>
|
|
|
|
|
|
public string Camp = "";
|
2026-11-10 10:00:18 +00:00
|
|
|
|
/// <summary>여성 영웅(heroes.csv gender f) — 스킬 조건 남성·여성(2026-11-10 #1112) · 그 밖(적 병사 포함) = 남성.</summary>
|
|
|
|
|
|
public bool Female;
|
|
|
|
|
|
/// <summary>heroes.csv 영웅 id(출전 영웅 · 영웅과 연결된 적 무장) · 그 밖 = 빈칸.</summary>
|
|
|
|
|
|
public string HeroId = "";
|
2026-11-10 04:19:44 +00:00
|
|
|
|
|
2026-11-03 19:10:10 +00:00
|
|
|
|
public void TakeDamage(int dmg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!IsAlive) return;
|
2026-11-09 14:12:52 +00:00
|
|
|
|
Hp = Mathf.Max(Invincible ? 1 : 0, Hp - dmg);
|
2026-11-03 19:10:10 +00:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
/// <summary>재행동(스킬) — 다시 일반 행동 1회(공격 가능).</summary>
|
|
|
|
|
|
public void GrantReAction()
|
|
|
|
|
|
{
|
|
|
|
|
|
ActedThisTurn = false;
|
|
|
|
|
|
BonusMoveAvailable = false;
|
|
|
|
|
|
if (View != null) View.Refresh();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 19:10:10 +00:00
|
|
|
|
public void GrantBonusMove()
|
|
|
|
|
|
{
|
|
|
|
|
|
ActedThisTurn = false;
|
|
|
|
|
|
BonusMoveAvailable = true;
|
|
|
|
|
|
if (View != null) View.Refresh();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|