2026-05-21 보스추가

This commit is contained in:
2026-05-21 12:12:17 +09:00
parent 7d87b6e007
commit 0f35455ad7
22 changed files with 821 additions and 81 deletions

View File

@@ -4,26 +4,30 @@
// HpBar
// ----------------------------------------------------------------------------
// SpriteRenderer 기반 HP바. Canvas보다 가벼움 (Canvas Rebuild 비용 없음).
// 부모(또는 Inspector 할당)의 Health 컴포넌트 이벤트 OnHealthChanged 구독.
//
// 동작:
// - Background와 Fill 두 SpriteRenderer로 구성
// - Fill의 스프라이트 피벗은 LEFT (0, 0.5) 여야 X 스케일 변경 시 왼쪽부터 줄어듦
// - HP 비율에 따라 _fill.localScale.x 조정
// - 임계값 기반으로 색상도 자동 변경 (높음/중간/낮음)
// 폴링 방식:
// 이벤트 구독 대신 매 프레임 Update에서 Health.Ratio를 직접 읽어 반영한다.
// - 구독/해제 생명주기가 없어 OnEnable 타이밍 버그가 원천적으로 없음
// - Update는 항상 모든 Awake 뒤에 실행되므로 Health는 늘 초기화된 상태
// - 비용: 매 프레임 float 비교 1회 + "비율이 바뀐 프레임"에만 시각 갱신
//
// 구성:
// - Background와 Fill 두 SpriteRenderer
// - Fill 스프라이트 피벗은 LEFT (0, 0.5) 여야 X 스케일 변경 시 왼쪽부터 줄어듦
// - HP 비율에 따라 _fill.localScale.x 조정 + 임계값별 색상 변경
// ============================================================================
public class HpBar : MonoBehaviour
{
[SerializeField] private Health _health; // 비워두면 _autoFindHealthInParent로 자동 검색
[SerializeField] private Transform _fill; // 채움 sprite transform (스케일 조정 대상)
[SerializeField] private bool _autoFindHealthInParent = true; // _health가 null이면 부모에서 Health 자동 검색
[SerializeField] private bool _hideWhenFull = true; // HP 풀이거나 0일 때 HpBar 자동 숨김
[SerializeField] private float _smoothSpeed = 0f; // 0이면 즉시 반영, 0보다 크면 보간 (units/sec)
[SerializeField] private bool _hideWhenFull = true; // HP 풀이거나 0일 때 HP바 숨김 (렌더러만 끔)
[SerializeField] private float _smoothSpeed = 0f; // 0이면 즉시 반영, 0보다 크면 보간 (ratio/sec)
// ─── 임계값별 색상 ──────────────────────────────────────────────────
// ratio > _midThreshold → _highColor (정상)
// _lowThreshold < ratio <= _midThreshold → _midColor (주의)
// ratio <= _lowThreshold → _lowColor (위험)
// ratio > _midThreshold → _highColor (정상)
// _lowThreshold < ratio <= _midThreshold → _midColor (주의)
// ratio <= _lowThreshold → _lowColor (위험)
[Header("Color Thresholds")]
[SerializeField] private Color _highColor = new Color(0.2f, 0.9f, 0.3f, 1f);
[SerializeField] private Color _midColor = new Color(1f, 0.85f, 0.2f, 1f);
@@ -31,10 +35,11 @@ public class HpBar : MonoBehaviour
[SerializeField, Range(0f, 1f)] private float _midThreshold = 0.5f;
[SerializeField, Range(0f, 1f)] private float _lowThreshold = 0.2f;
private Vector3 _baseFillScale; // 풀체력 시 Fill 스케일 (Awake에 캐싱, 이후 ratio 곱해서 사용)
private SpriteRenderer _fillRenderer; // Fill 색상 변경용 SpriteRenderer
private float _currentRatio = 1f; // 현재 표시된 HP 비율 (보간 진행 시 _targetRatio로 수렴)
private float _targetRatio = 1f; // 도달해야 할 HP 비율
private Vector3 _baseFillScale; // 풀체력 시 Fill 스케일 (Awake에 캐싱, 이후 ratio 곱해서 사용)
private SpriteRenderer _fillRenderer; // Fill 색상 변경용
private SpriteRenderer[] _renderers; // 숨김 처리용 — Background + Fill 전부
private float _currentRatio = 1f; // 현재 표시 중인 HP 비율 (보간 시 점진적으로 수렴)
private float _appliedRatio = -1f; // 마지막으로 시각에 반영한 비율 (중복 갱신 방지, -1이면 첫 프레임 강제 갱신)
private void Awake()
{
@@ -46,55 +51,30 @@ private void Awake()
_baseFillScale = _fill.localScale;
_fillRenderer = _fill.GetComponent<SpriteRenderer>();
}
// 숨김 토글 대상: 이 HP바 계층의 모든 SpriteRenderer (Background + Fill).
_renderers = GetComponentsInChildren<SpriteRenderer>(true);
}
// 활성/비활성 토글 시 자동 구독·해제 (메모리 누수 방지).
// 활성 시 즉시 한 번 갱신해서 현재 HP 상태 반영.
private void OnEnable()
// 매 프레임 Health 상태를 폴링해서 반영.
// 비율이 실제로 바뀐 프레임에만 transform/color를 건드린다 (정지 시 비용 거의 0).
private void Update()
{
if (_health == null) return;
_health.OnHealthChanged += HandleHealthChanged;
HandleHealthChanged(_health.CurrentHealth, _health.MaxHealth);
}
float target = _health.Ratio;
private void OnDisable()
{
if (_health != null)
_health.OnHealthChanged -= HandleHealthChanged;
}
if (_smoothSpeed > 0f)
_currentRatio = Mathf.MoveTowards(_currentRatio, target, _smoothSpeed * Time.deltaTime);
else
_currentRatio = target;
// 보간 모드일 때만 매 프레임 스케일 갱신.
private void Update()
{
if (_smoothSpeed <= 0f) return;
if (Mathf.Approximately(_currentRatio, _targetRatio)) return;
if (Mathf.Approximately(_currentRatio, _appliedRatio)) return;
_appliedRatio = _currentRatio;
_currentRatio = Mathf.MoveTowards(_currentRatio, _targetRatio, _smoothSpeed * Time.deltaTime);
ApplyScale();
}
// Health.OnHealthChanged 이벤트 콜백. ratio 계산 → 스케일/색상 갱신 → 숨김 처리.
private void HandleHealthChanged(int current, int max)
{
_targetRatio = max > 0 ? (float)current / max : 0f;
// 즉시 모드면 바로 스케일 반영. 보간 모드면 Update에서 점진적으로.
if (_smoothSpeed <= 0f)
{
_currentRatio = _targetRatio;
ApplyScale();
}
ApplyColor(_targetRatio);
// 풀체력(1)이거나 사망(0)이면 HpBar 자체를 숨김 (UI 정리 + GameObject 부하 감소).
if (_hideWhenFull && _fill != null)
{
bool shouldShow = _targetRatio < 1f && _targetRatio > 0f;
if (gameObject.activeSelf != shouldShow)
gameObject.SetActive(shouldShow);
}
ApplyColor(_currentRatio);
ApplyVisibility();
}
// Fill의 X 스케일 = baseScale.x × ratio. 피벗이 LEFT여야 왼쪽부터 채워짐.
@@ -112,14 +92,26 @@ private void ApplyColor(float ratio)
{
if (_fillRenderer == null) return;
Color color;
if (ratio <= _lowThreshold)
color = _lowColor;
_fillRenderer.color = _lowColor;
else if (ratio <= _midThreshold)
color = _midColor;
_fillRenderer.color = _midColor;
else
color = _highColor;
_fillRenderer.color = _highColor;
}
_fillRenderer.color = color;
// HP가 풀(1)이거나 사망(0)이면 HP바를 숨김.
// GameObject가 아니라 SpriteRenderer의 enabled만 끈다 — GameObject를 끄면
// Update가 멈춰 폴링이 중단되고 다시 켤 수 없게 되기 때문.
private void ApplyVisibility()
{
if (!_hideWhenFull || _renderers == null) return;
bool visible = _currentRatio > 0f && _currentRatio < 1f;
for (int i = 0; i < _renderers.Length; i++)
{
if (_renderers[i] != null)
_renderers[i].enabled = visible;
}
}
}