175 lines
6.6 KiB
C#
175 lines
6.6 KiB
C#
using System;
|
|
using Platformer.Gameplay;
|
|
using UnityEngine;
|
|
using static Platformer.Core.Simulation;
|
|
|
|
namespace Platformer.Mechanics
|
|
{
|
|
/// <summary>
|
|
/// Represents the current vital statistics of some game entity.
|
|
/// <para>
|
|
/// BT7-Plan PD 지시 2026-04-24 — 하트 분할 시스템 (젤다 방식). 하트 1개 = 4 쿼터(HP).
|
|
/// 카드(패시브)·성장으로 <see cref="maxHearts"/>를 증가시킬 수 있으며, 그에 따라
|
|
/// <see cref="maxHP"/>가 `maxHearts * QuartersPerHeart`로 자동 산정된다.
|
|
/// </para>
|
|
/// <para>
|
|
/// 적 ATK는 쿼터 단위. 피해 1 = 1 쿼터 감소, 피해 2 = 반조각, 피해 4 = 하트 1개 소멸.
|
|
/// </para>
|
|
/// </summary>
|
|
public class Health : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// 하트 1개당 HP(쿼터) 수 — 젤다 방식 고정값 4.
|
|
/// </summary>
|
|
public const int QuartersPerHeart = 4;
|
|
|
|
/// <summary>
|
|
/// 초기 보유 하트 수. Player는 기본 1(= 4 HP), 카드·성장으로 <see cref="IncreaseMaxHearts"/>를 통해 증가.
|
|
/// Enemy는 기본 1(= 4 HP)이나 향후 balance/01 v0.2 수치 테이블에 따라 재설정.
|
|
/// </summary>
|
|
[Tooltip("초기 보유 하트 수. 하트 1개 = 4 HP.")]
|
|
public int maxHearts = 1;
|
|
|
|
/// <summary>
|
|
/// 산정된 최대 HP(쿼터 단위). 직접 설정 대신 <see cref="maxHearts"/>를 통해 조정한다 (시리얼라이즈 호환 유지).
|
|
/// BT7-Plan 이전에는 1로 고정되어 있었으나, 현재 PD 지시로 하트 분할 시스템 전환.
|
|
/// </summary>
|
|
[Tooltip("산정된 최대 HP(쿼터). maxHearts * 4. Inspector에서 직접 수정하지 말 것.")]
|
|
public int maxHP = QuartersPerHeart;
|
|
|
|
/// <summary>
|
|
/// 무적 시간 (i-frame) 지속 초 단위. 기획 04 §3-2 후보 0.4~0.8s.
|
|
/// 연속 히트 방지용 (하트 분할 전환 후에도 강한 단일 공격을 한 번에 과도 피해 받지 않도록 유지).
|
|
/// </summary>
|
|
public float invulnerableDuration = 0.6f;
|
|
|
|
/// <summary>
|
|
/// 현재 무적 상태 여부 (디버그·UX 피드백용 — 깜박임 제어 등에 사용 가능).
|
|
/// </summary>
|
|
public bool IsInvulnerable => Time.time < invulnerableUntil;
|
|
|
|
/// <summary>
|
|
/// Indicates if the entity should be considered 'alive'.
|
|
/// </summary>
|
|
public bool IsAlive => currentHP > 0;
|
|
|
|
/// <summary>
|
|
/// 현재 HP(쿼터). HUD는 이 값과 <see cref="maxHP"/>를 함께 읽어 하트 분할 UI를 그린다.
|
|
/// </summary>
|
|
public int CurrentHP => currentHP;
|
|
|
|
int currentHP;
|
|
float invulnerableUntil = -1f;
|
|
|
|
/// <summary>
|
|
/// Increment the HP of the entity by a single quarter.
|
|
/// 카드·픽업으로 하트 1/4 회복 (기존 API 호환).
|
|
/// </summary>
|
|
public void Increment()
|
|
{
|
|
currentHP = Mathf.Clamp(currentHP + 1, 0, maxHP);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 쿼터 단위 회복. 하트 1개 전체 회복은 quarters=4 호출.
|
|
/// </summary>
|
|
/// <param name="quarters">회복할 쿼터 수 (0 이하면 무시).</param>
|
|
public void Heal(int quarters)
|
|
{
|
|
if (quarters <= 0) return;
|
|
currentHP = Mathf.Clamp(currentHP + quarters, 0, maxHP);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 단일 피해(기본 1 쿼터). 무적 시간 내에는 호출되어도 스킵된다.
|
|
/// </summary>
|
|
public void Decrement()
|
|
{
|
|
Decrement(1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 쿼터 단위 피해 처리. BT7-Plan 2026-04-24 — 적 ATK는 쿼터 단위(`PlayerHP -= EnemyATK`).
|
|
/// 무적 시간 내에는 완전 스킵(부분 히트도 인정하지 않음 — 연타 방지 원칙).
|
|
/// </summary>
|
|
/// <param name="damage">피해량(쿼터). 1 이상 값만 유효.</param>
|
|
public void Decrement(int damage)
|
|
{
|
|
if (damage <= 0) return;
|
|
|
|
// BT5-Dev 2단계: i-frame 보호 — 무적 시간 내 중복 피격 차단
|
|
if (Time.time < invulnerableUntil)
|
|
{
|
|
return;
|
|
}
|
|
|
|
currentHP = Mathf.Clamp(currentHP - damage, 0, maxHP);
|
|
|
|
// 피격 성공 시 무적 시간 활성화 (다음 피격 대비)
|
|
if (invulnerableDuration > 0f)
|
|
{
|
|
invulnerableUntil = Time.time + invulnerableDuration;
|
|
}
|
|
|
|
if (currentHP == 0)
|
|
{
|
|
var ev = Schedule<HealthIsZero>();
|
|
ev.health = this;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 최대 하트 수를 증가시키고 현재 HP도 같은 쿼터만큼 비례 증가.
|
|
/// BT7-Plan 2026-04-24 — 패시브 카드 `[방호]`·`[회복]` 효과 훅.
|
|
/// </summary>
|
|
/// <param name="delta">증가시킬 하트 수 (음수 허용하지만 currentHP가 maxHP를 초과하지 않도록 clamp).</param>
|
|
public void IncreaseMaxHearts(int delta)
|
|
{
|
|
if (delta == 0) return;
|
|
int beforeMax = maxHP;
|
|
maxHearts = Mathf.Max(0, maxHearts + delta);
|
|
maxHP = maxHearts * QuartersPerHeart;
|
|
|
|
if (delta > 0)
|
|
{
|
|
int added = maxHP - beforeMax;
|
|
currentHP = Mathf.Clamp(currentHP + added, 0, maxHP);
|
|
}
|
|
else
|
|
{
|
|
currentHP = Mathf.Clamp(currentHP, 0, maxHP);
|
|
if (currentHP == 0 && maxHP > 0)
|
|
{
|
|
var ev = Schedule<HealthIsZero>();
|
|
ev.health = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrement the HP of the entity until HP reaches 0.
|
|
/// i-frame 우회하여 즉사 처리 (낙사·승리 이탈 등 시스템 강제 사망).
|
|
/// </summary>
|
|
public void Die()
|
|
{
|
|
invulnerableUntil = -1f; // i-frame 무효화
|
|
if (currentHP > 0)
|
|
{
|
|
currentHP = 0;
|
|
var ev = Schedule<HealthIsZero>();
|
|
ev.health = this;
|
|
}
|
|
}
|
|
|
|
void Awake()
|
|
{
|
|
// maxHearts가 설정되어 있고 maxHP가 구식 값(1)으로 남아있는 프리팹 호환 처리.
|
|
// 정상 흐름: maxHP = maxHearts * 4, currentHP = maxHP로 초기화.
|
|
if (maxHearts <= 0) maxHearts = 1;
|
|
int expected = maxHearts * QuartersPerHeart;
|
|
if (maxHP != expected) maxHP = expected;
|
|
currentHP = maxHP;
|
|
}
|
|
}
|
|
}
|