2026-02-13 09:11:54 +00:00
|
|
|
|
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
|
|
|
|
|
|
using UnityEngine.UI; // UI 기능을 불러올거에요 -> UnityEngine.UI를
|
2026-02-21 06:14:24 +00:00
|
|
|
|
using TMPro; // TextMeshPro를 사용할거에요 -> TMPro를
|
2026-02-13 09:11:54 +00:00
|
|
|
|
|
2026-02-21 06:14:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 플레이어 체력바 UI — PlayerHealth 이벤트 연동
|
|
|
|
|
|
/// HP_Filled (Image Fill) + Text (TMP) 실시간 갱신
|
|
|
|
|
|
/// </summary>
|
2026-02-13 09:11:54 +00:00
|
|
|
|
public class HPUibar : MonoBehaviour // 클래스를 선언할거에요 -> 체력바 UI를
|
|
|
|
|
|
{
|
2026-02-21 06:14:24 +00:00
|
|
|
|
[Header("--- 타겟 ---")] // 인스펙터 제목을 달거에요 -> 타겟 설정을
|
|
|
|
|
|
[SerializeField] private GameObject targetObject; // 변수를 선언할거에요 -> 타겟 오브젝트를 (Player)
|
2026-02-13 09:11:54 +00:00
|
|
|
|
|
2026-02-21 06:14:24 +00:00
|
|
|
|
[Header("--- UI 요소 ---")] // 인스펙터 제목을 달거에요 -> UI 요소를
|
|
|
|
|
|
[Tooltip("HP_Filled (Image, Fill 방식)")]
|
|
|
|
|
|
[SerializeField] private Image hpFilledImage; // 변수를 선언할거에요 -> 체력바 Fill 이미지를
|
|
|
|
|
|
|
|
|
|
|
|
[Tooltip("Text (TMP) — '현재HP / 최대HP' 표시")]
|
|
|
|
|
|
[SerializeField] private TextMeshProUGUI hpText; // 변수를 선언할거에요 -> 체력 텍스트를
|
|
|
|
|
|
|
|
|
|
|
|
// 캐싱된 참조
|
|
|
|
|
|
private PlayerHealth _playerHealth; // 변수를 선언할거에요 -> 플레이어 체력 스크립트를
|
|
|
|
|
|
private Stats _playerStats; // 변수를 선언할거에요 -> 플레이어 스탯 스크립트를
|
2026-02-13 09:11:54 +00:00
|
|
|
|
|
|
|
|
|
|
private void Awake() // 함수를 실행할거에요 -> 초기화 Awake를
|
|
|
|
|
|
{
|
|
|
|
|
|
if (targetObject != null) // 조건이 맞으면 실행할거에요 -> 타겟이 있다면
|
|
|
|
|
|
{
|
2026-02-21 06:14:24 +00:00
|
|
|
|
_playerHealth = targetObject.GetComponent<PlayerHealth>(); // 가져올거에요 -> PlayerHealth를
|
|
|
|
|
|
_playerStats = targetObject.GetComponent<Stats>(); // 가져올거에요 -> Stats를
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnEnable() // 함수를 실행할거에요 -> 활성화 시 OnEnable을
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_playerHealth != null) // 조건이 맞으면 실행할거에요 -> 체력 스크립트가 있다면
|
|
|
|
|
|
{
|
|
|
|
|
|
_playerHealth.OnHealthChanged.AddListener(OnHealthRatioChanged); // 등록할거에요 -> 체력 변경 이벤트 리스너를
|
2026-02-13 09:11:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-21 06:14:24 +00:00
|
|
|
|
private void OnDisable() // 함수를 실행할거에요 -> 비활성화 시 OnDisable을
|
2026-02-13 09:11:54 +00:00
|
|
|
|
{
|
2026-02-21 06:14:24 +00:00
|
|
|
|
if (_playerHealth != null) // 조건이 맞으면 실행할거에요 -> 체력 스크립트가 있다면
|
2026-02-13 09:11:54 +00:00
|
|
|
|
{
|
2026-02-21 06:14:24 +00:00
|
|
|
|
_playerHealth.OnHealthChanged.RemoveListener(OnHealthRatioChanged); // 해제할거에요 -> 체력 변경 이벤트 리스너를
|
2026-02-13 09:11:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-21 06:14:24 +00:00
|
|
|
|
private void Start() // 함수를 실행할거에요 -> 시작 시 Start를
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateUI(); // 실행할거에요 -> 초기 UI 갱신을 (시작하자마자 올바른 값 표시)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// PlayerHealth.RefreshHealthUI()에서 ratio(0~1)를 받아 호출됨
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void OnHealthRatioChanged(float ratio) // 함수를 선언할거에요 -> 체력 비율 변경 콜백을
|
|
|
|
|
|
{
|
|
|
|
|
|
// Fill Amount 갱신
|
|
|
|
|
|
if (hpFilledImage != null) // 조건이 맞으면 실행할거에요 -> Fill 이미지가 있다면
|
|
|
|
|
|
{
|
|
|
|
|
|
hpFilledImage.fillAmount = Mathf.Clamp01(ratio); // 값을 설정할거에요 -> 0~1 사이로 Fill Amount를
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 텍스트 갱신
|
|
|
|
|
|
UpdateHPText(); // 실행할거에요 -> 텍스트 갱신 함수를
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 텍스트를 "현재HP / 최대HP" 형식으로 갱신
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void UpdateHPText() // 함수를 선언할거에요 -> HP 텍스트를 갱신하는 UpdateHPText를
|
|
|
|
|
|
{
|
|
|
|
|
|
if (hpText == null || _playerHealth == null || _playerStats == null) return; // 조건이 맞으면 중단할거에요 -> 필수 참조가 없으면
|
|
|
|
|
|
|
|
|
|
|
|
float current = _playerHealth.CurrentHP; // 값을 가져올거에요 -> 현재 체력을
|
|
|
|
|
|
float max = _playerStats.MaxHealth; // 값을 가져올거에요 -> 최대 체력을
|
|
|
|
|
|
hpText.text = $"{current:F0} / {max:F0}"; // 텍스트를 설정할거에요 -> "현재 / 최대" 형식으로
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 이벤트 없이 직접 UI를 갱신 (Start, 수동 호출용)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void UpdateUI() // 함수를 선언할거에요 -> 전체 UI 갱신을
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_playerHealth == null || _playerStats == null) return; // 조건이 맞으면 중단할거에요 -> 참조가 없으면
|
|
|
|
|
|
|
|
|
|
|
|
float max = _playerStats.MaxHealth; // 값을 가져올거에요 -> 최대 체력을
|
|
|
|
|
|
float current = _playerHealth.CurrentHP; // 값을 가져올거에요 -> 현재 체력을
|
|
|
|
|
|
float ratio = (max > 0) ? current / max : 0f; // 비율을 계산할거에요 -> 현재/최대로
|
|
|
|
|
|
|
|
|
|
|
|
if (hpFilledImage != null) // 조건이 맞으면 실행할거에요 -> Fill 이미지가 있다면
|
|
|
|
|
|
{
|
|
|
|
|
|
hpFilledImage.fillAmount = Mathf.Clamp01(ratio); // 값을 설정할거에요 -> Fill Amount를
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UpdateHPText(); // 실행할거에요 -> 텍스트 갱신을
|
|
|
|
|
|
}
|
2026-03-22 03:31:16 +00:00
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
// 외부 연결 API (DungeonSceneSetup 폴백 모드용)
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 런타임에서 타겟 오브젝트(플레이어)를 설정하고 컴포넌트를 재연결합니다.
|
|
|
|
|
|
/// DungeonSceneSetup의 폴백 모드에서 호출됩니다.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void SetTargetObject(GameObject newTarget) // 함수를 정의할거에요 -> 타겟 오브젝트 런타임 설정을
|
|
|
|
|
|
{
|
|
|
|
|
|
if (newTarget == null) return; // 방어할거에요 -> null이면 중단
|
|
|
|
|
|
|
|
|
|
|
|
// 기존 이벤트 해제 (중복 방지)
|
|
|
|
|
|
if (_playerHealth != null) // 조건이 맞으면 실행할거에요 -> 기존 연결이 있으면
|
|
|
|
|
|
{
|
|
|
|
|
|
_playerHealth.OnHealthChanged.RemoveListener(OnHealthRatioChanged); // 해제할거에요 -> 기존 리스너를
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
targetObject = newTarget; // 설정할거에요 -> 새 타겟 오브젝트를
|
|
|
|
|
|
_playerHealth = newTarget.GetComponent<PlayerHealth>(); // 가져올거에요 -> 새 타겟의 PlayerHealth를
|
|
|
|
|
|
_playerStats = newTarget.GetComponent<Stats>(); // 가져올거에요 -> 새 타겟의 Stats를
|
|
|
|
|
|
|
|
|
|
|
|
// 새 이벤트 등록
|
|
|
|
|
|
if (_playerHealth != null) // 조건이 맞으면 실행할거에요 -> 체력 스크립트가 있으면
|
|
|
|
|
|
{
|
|
|
|
|
|
_playerHealth.OnHealthChanged.AddListener(OnHealthRatioChanged); // 등록할거에요 -> 체력 변경 리스너를
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UpdateUI(); // 실행할거에요 -> UI 즉시 갱신을
|
|
|
|
|
|
Debug.Log($"[HPUibar] 타겟 변경 완료: {newTarget.name}"); // 로그를 찍을거에요
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 외부에서 강제로 UI를 갱신합니다.
|
|
|
|
|
|
/// DungeonSceneSetup의 지연 갱신(DelayedUIRefresh)에서 호출됩니다.
|
|
|
|
|
|
/// 모든 Start()가 끝난 뒤 호출되므로 CurrentHP가 확실히 초기화된 상태입니다.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void ForceRefresh() // 함수를 정의할거에요 -> 강제 UI 갱신을
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateUI(); // 실행할거에요 -> Fill + 텍스트 전체 갱신을
|
|
|
|
|
|
Debug.Log("[HPUibar] ForceRefresh 완료"); // 로그를 찍을거에요
|
|
|
|
|
|
}
|
2026-02-13 09:11:54 +00:00
|
|
|
|
}
|