Projext/Assets/Scripts/UI/HUD/HP Ui bar.cs
2026-02-13 00:23:25 +09:00

93 lines
7.8 KiB
C#

using UnityEngine; // 유니티 기본 기능(카메라, Mathf 등)을 쓰기 위해 불러올거에요 -> UnityEngine를
using UnityEngine.UI; // Image 컴포넌트를 쓰기 위해 불러올거에요 -> UnityEngine.UI를
using TMPro; // TextMeshProUGUI를 쓰기 위해 불러올거에요 -> TMPro를
public class HPUibar : MonoBehaviour // 클래스를 선언할거에요 -> 체력바 UI를 갱신하는 HPUibar를
{ // 코드 블록을 시작할거에요 -> HPUibar 범위를
[Header("--- 참조 ---")] // 인스펙터에 제목을 표시할거에요 -> 참조 섹션을
[SerializeField] private MonoBehaviour healthSource; // 변수를 선언할거에요 -> 체력 소스(플레이어/몹 등)를 healthSource에
[Header("--- UI ---")] // 인스펙터에 제목을 표시할거에요 -> UI 섹션을
[SerializeField] private Image hpFillImage; // 변수를 선언할거에요 -> 체력 게이지 채움(Image.fillAmount)용 hpFillImage를
[SerializeField] private TextMeshProUGUI hpText; // 변수를 선언할거에요 -> 체력 텍스트 표시용 hpText를
private PlayerHealth _playerHealth; // 변수를 선언할거에요 -> PlayerHealth를 캐싱할 _playerHealth를
private Stats _playerStats; // 변수를 선언할거에요 -> PlayerHealth의 Stats를 캐싱할 _playerStats를
private void Start() // 함수를 선언할거에요 -> 시작 시 1회 실행되는 Start를
{ // 코드 블록을 시작할거에요 -> Start 범위를
// ✅ PlayerHealth는 UnityEvent<float> (ratio만 줌)이므로 AddListener 방식으로 구독할거에요 -> UnityEvent 전용 처리
if (healthSource is PlayerHealth ph) // 조건을 검사할거에요 -> healthSource가 PlayerHealth인지
{ // 코드 블록을 시작할거에요 -> PlayerHealth 처리 범위를
_playerHealth = ph; // 값을 저장할거에요 -> PlayerHealth 캐싱
_playerStats = ph.GetComponent<Stats>(); // 컴포넌트를 가져올거에요 -> 같은 오브젝트의 Stats를 캐싱
ph.OnHealthChanged.AddListener(UpdateUIFromRatio); // 이벤트를 구독할거에요 -> UnityEvent<float>라 AddListener 사용
ForceRefreshPlayerUI(); // 시작 시 UI를 강제로 한 번 맞출거에요 -> CurrentHP 기반으로
} // 코드 블록을 끝낼거에요 -> PlayerHealth 처리
// ✅ 나머지는 (current,max) Action 이벤트라 기존처럼 += 구독할거에요 -> 기존 코드 유지
else if (healthSource is TrainingDummy td) td.OnHealthChanged += UpdateUI; // 더미면 (current,max) 이벤트 구독할거에요 -> UpdateUI로
else if (healthSource is EnemyHealth eh) eh.OnHealthChanged += UpdateUI; // 적이면 (current,max) 이벤트 구독할거에요 -> UpdateUI로
else if (healthSource is MonsterClass mc) mc.OnHealthChanged += UpdateUI; // 몬스터면 (current,max) 이벤트 구독할거에요 -> UpdateUI로
if (hpFillImage != null) hpFillImage.fillAmount = 1f; // 조건이 맞으면 실행할거에요 -> 시작 시 게이지를 풀로 표시
} // 코드 블록을 끝낼거에요 -> Start를
private void ForceRefreshPlayerUI() // 함수를 선언할거에요 -> PlayerHealth용 UI 강제 갱신 함수 ForceRefreshPlayerUI를
{ // 코드 블록을 시작할거에요 -> ForceRefreshPlayerUI 범위를
if (_playerHealth == null) return; // 조건이 맞으면 중단할거에요 -> 플레이어가 없으면
float max = (_playerStats != null) ? _playerStats.MaxHealth : 0f; // 값을 구할거에요 -> Stats가 있으면 MaxHealth를, 없으면 0을
UpdateUI(_playerHealth.CurrentHP, max); // 함수를 실행할거에요 -> current/max 형태로 UI를 통일해서 갱신
} // 코드 블록을 끝낼거에요 -> ForceRefreshPlayerUI를
private void UpdateUIFromRatio(float ratio) // 함수를 선언할거에요 -> PlayerHealth(UnityEvent<float>)가 보내는 ratio를 받는 UpdateUIFromRatio를
{ // 코드 블록을 시작할거에요 -> UpdateUIFromRatio 범위를
ratio = Mathf.Clamp01(ratio); // 값을 보정할거에요 -> ratio를 0~1로 고정
if (hpFillImage != null) hpFillImage.fillAmount = ratio; // 조건이 맞으면 실행할거에요 -> 게이지를 ratio만큼 채우기
// 텍스트는 ratio만으론 current/max를 모름 -> PlayerHealth/Stats에서 직접 읽어서 찍을거에요 -> 코드 영향 최소
if (_playerHealth != null && hpText != null) // 조건을 검사할거에요 -> PlayerHealth와 텍스트가 있는지
{ // 코드 블록을 시작할거에요 -> 텍스트 갱신 범위를
float max = (_playerStats != null) ? _playerStats.MaxHealth : 0f; // 값을 구할거에요 -> Stats가 있으면 MaxHealth를
float cur = _playerHealth.CurrentHP; // 값을 구할거에요 -> PlayerHealth의 현재 체력을
hpText.text = (max > 0f) // 조건을 검사할거에요 -> max가 0보다 큰지
? $"{Mathf.CeilToInt(cur)} / {Mathf.CeilToInt(max)}" // 조건이 맞으면 실행할거에요 -> current/max로 표시
: $"{Mathf.RoundToInt(ratio * 100f)}%"; // 조건이 틀리면 실행할거에요 -> max를 못 구하면 퍼센트로 표시
} // 코드 블록을 끝낼거에요 -> 텍스트 갱신
} // 코드 블록을 끝낼거에요 -> UpdateUIFromRatio를
private void UpdateUI(float current, float max) // 함수를 선언할거에요 -> (current,max) 이벤트용 UI 갱신 함수 UpdateUI를
{ // 코드 블록을 시작할거에요 -> UpdateUI 범위를
if (hpFillImage != null && max > 0f) // 조건을 검사할거에요 -> 이미지가 있고 max가 0보다 큰지
{ // 코드 블록을 시작할거에요 -> 게이지 갱신 범위를
hpFillImage.fillAmount = current / max; // 값을 넣을거에요 -> 체력 비율로 게이지 채우기
} // 코드 블록을 끝낼거에요 -> 게이지 갱신
if (hpText != null) // 조건을 검사할거에요 -> 텍스트가 있는지
{ // 코드 블록을 시작할거에요 -> 텍스트 갱신 범위를
hpText.text = $"{Mathf.CeilToInt(current)} / {Mathf.CeilToInt(max)}"; // 값을 넣을거에요 -> current/max 텍스트 표시
} // 코드 블록을 끝낼거에요 -> 텍스트 갱신
} // 코드 블록을 끝낼거에요 -> UpdateUI를
private void LateUpdate() // 함수를 선언할거에요 -> 모든 Update 후 실행되는 LateUpdate를
{ // 코드 블록을 시작할거에요 -> LateUpdate 범위를
if (Camera.main != null) // 조건을 검사할거에요 -> 메인 카메라가 있는지
transform.LookAt(transform.position + Camera.main.transform.forward); // 회전을 맞출거에요 -> 카메라 전방을 바라보게(빌보드)
} // 코드 블록을 끝낼거에요 -> LateUpdate를
private void OnDestroy() // 함수를 선언할거에요 -> 오브젝트가 파괴될 때 호출되는 OnDestroy를
{ // 코드 블록을 시작할거에요 -> OnDestroy 범위를
// ✅ PlayerHealth는 UnityEvent라 RemoveListener로 해제할거에요 -> 누수 방지
if (_playerHealth != null) _playerHealth.OnHealthChanged.RemoveListener(UpdateUIFromRatio); // 조건이 맞으면 실행할거에요 -> UnityEvent 구독 해제
// ✅ 나머지는 Action 이벤트라 기존처럼 -= 해제할거에요 -> 기존 코드 유지
if (healthSource is TrainingDummy td) td.OnHealthChanged -= UpdateUI; // 더미면 구독 해제할거에요 -> UpdateUI를
else if (healthSource is EnemyHealth eh) eh.OnHealthChanged -= UpdateUI; // 적이면 구독 해제할거에요 -> UpdateUI를
else if (healthSource is MonsterClass mc) mc.OnHealthChanged -= UpdateUI; // 몬스터면 구독 해제할거에요 -> UpdateUI를
} // 코드 블록을 끝낼거에요 -> OnDestroy를
} // 코드 블록을 끝낼거에요 -> HPUibar를