diff --git a/Assets/Scripts/Colosseum.Game.asmdef b/Assets/Scripts/Colosseum.Game.asmdef
index 320bcfdb..9f849cf1 100644
--- a/Assets/Scripts/Colosseum.Game.asmdef
+++ b/Assets/Scripts/Colosseum.Game.asmdef
@@ -5,7 +5,8 @@
"Unity.Netcode.Runtime",
"Unity.Networking.Transport",
"Unity.Transport",
- "Unity.InputSystem"
+ "Unity.InputSystem",
+ "Unity.TextMeshPro"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Assets/Scripts/UI/PlayerHUD.cs b/Assets/Scripts/UI/PlayerHUD.cs
new file mode 100644
index 00000000..522c1a1a
--- /dev/null
+++ b/Assets/Scripts/UI/PlayerHUD.cs
@@ -0,0 +1,76 @@
+using UnityEngine;
+using Colosseum.Player;
+
+namespace Colosseum.UI
+{
+ ///
+ /// 플레이어 HUD - 체력/마나 바 관리
+ ///
+ public class PlayerHUD : MonoBehaviour
+ {
+ [Header("Stat Bars")]
+ [SerializeField] private StatBar healthBar;
+ [SerializeField] private StatBar manaBar;
+
+ [Header("Target")]
+ [Tooltip("자동으로 로컬 플레이어 찾기")]
+ [SerializeField] private bool autoFindPlayer = true;
+
+ private PlayerNetworkController targetPlayer;
+
+ private void Start()
+ {
+ if (autoFindPlayer)
+ {
+ FindLocalPlayer();
+ }
+ }
+
+ private void Update()
+ {
+ if (targetPlayer == null && autoFindPlayer)
+ {
+ FindLocalPlayer();
+ }
+
+ if (targetPlayer != null)
+ {
+ UpdateStatBars();
+ }
+ }
+
+ private void FindLocalPlayer()
+ {
+ var players = FindObjectsByType(FindObjectsSortMode.None);
+ foreach (var player in players)
+ {
+ if (player.IsOwner)
+ {
+ SetTarget(player);
+ break;
+ }
+ }
+ }
+
+ ///
+ /// 추적할 플레이어 설정
+ ///
+ public void SetTarget(PlayerNetworkController player)
+ {
+ targetPlayer = player;
+ }
+
+ private void UpdateStatBars()
+ {
+ if (healthBar != null)
+ {
+ healthBar.SetValue(targetPlayer.Health, targetPlayer.MaxHealth);
+ }
+
+ if (manaBar != null)
+ {
+ manaBar.SetValue(targetPlayer.Mana, targetPlayer.MaxMana);
+ }
+ }
+ }
+}
diff --git a/Assets/Scripts/UI/PlayerHUD.cs.meta b/Assets/Scripts/UI/PlayerHUD.cs.meta
new file mode 100644
index 00000000..1308d51b
--- /dev/null
+++ b/Assets/Scripts/UI/PlayerHUD.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e956d7232e6a45246ac4d8079f12f9c3
\ No newline at end of file
diff --git a/Assets/Scripts/UI/StatBar.cs b/Assets/Scripts/UI/StatBar.cs
new file mode 100644
index 00000000..f1fca4e7
--- /dev/null
+++ b/Assets/Scripts/UI/StatBar.cs
@@ -0,0 +1,111 @@
+using UnityEngine;
+using UnityEngine.UI;
+using TMPro;
+
+namespace Colosseum.UI
+{
+ ///
+ /// 체력/마나 바 UI 컴포넌트
+ /// Slider 또는 Image.Fill 방식 지원
+ ///
+ public class StatBar : MonoBehaviour
+ {
+ [Header("References - Slider 방식")]
+ [SerializeField] private Slider slider;
+
+ [Header("References - Fill Image 방식")]
+ [SerializeField] private Image fillImage;
+
+ [Header("References - 텍스트")]
+ [SerializeField] private TMP_Text valueText;
+
+ [Header("Colors")]
+ [SerializeField] private Color fullColor = Color.green;
+ [SerializeField] private Color lowColor = Color.red;
+ [SerializeField] private float lowThreshold = 0.3f;
+ [SerializeField] private bool useColorTransition = true;
+
+ [Header("Animation")]
+ [SerializeField] private bool smoothTransition = true;
+ [SerializeField] private float lerpSpeed = 5f;
+
+ private float currentValue;
+ private float maxValue;
+ private float displayValue;
+
+ ///
+ /// 바 값 설정
+ ///
+ public void SetValue(float current, float max)
+ {
+ currentValue = current;
+ maxValue = max;
+
+ if (!smoothTransition)
+ {
+ displayValue = currentValue;
+ UpdateVisuals();
+ }
+ }
+
+ private void Update()
+ {
+ if (smoothTransition && !Mathf.Approximately(displayValue, currentValue))
+ {
+ displayValue = Mathf.Lerp(displayValue, currentValue, lerpSpeed * Time.deltaTime);
+
+ if (Mathf.Abs(displayValue - currentValue) < 0.01f)
+ displayValue = currentValue;
+
+ UpdateVisuals();
+ }
+ }
+
+ private void UpdateVisuals()
+ {
+ if (maxValue <= 0f) return;
+
+ float ratio = Mathf.Clamp01(displayValue / maxValue);
+
+ // Slider 방식
+ if (slider != null)
+ {
+ slider.value = ratio;
+
+ // Slider 내부 Fill 이미지 색상 변경
+ if (useColorTransition && slider.fillRect != null)
+ {
+ var fillImg = slider.fillRect.GetComponent();
+ if (fillImg != null)
+ {
+ fillImg.color = ratio <= lowThreshold ? lowColor : fullColor;
+ }
+ }
+ }
+ // Image.Fill 방식
+ else if (fillImage != null)
+ {
+ fillImage.fillAmount = ratio;
+
+ if (useColorTransition)
+ {
+ fillImage.color = ratio <= lowThreshold ? lowColor : fullColor;
+ }
+ }
+
+ // 텍스트
+ if (valueText != null)
+ {
+ valueText.text = $"{Mathf.CeilToInt(displayValue)} / {Mathf.CeilToInt(maxValue)}";
+ }
+ }
+
+ private void OnValidate()
+ {
+ if (slider == null)
+ slider = GetComponentInChildren();
+ if (fillImage == null && slider == null)
+ fillImage = GetComponentInChildren();
+ }
+ }
+}
diff --git a/Assets/Scripts/UI/StatBar.cs.meta b/Assets/Scripts/UI/StatBar.cs.meta
new file mode 100644
index 00000000..a1edc161
--- /dev/null
+++ b/Assets/Scripts/UI/StatBar.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: b5c5d0fa667f83d4399abb45ffcaea31
\ No newline at end of file