using UnityEngine; using UnityEngine.UI; using TMPro; namespace Northbound { /// /// 유닛(플레이어, 적, 건물 등) 위에 표시되는 체력바 /// IHealthProvider 인터페이스를 구현하는 모든 유닛에서 사용 가능 /// public class UnitHealthBar : MonoBehaviour { [Header("UI References")] [Tooltip("체력바 채우기 이미지 (Filled 타입 권장)")] public Image fillImage; [Tooltip("체력 텍스트 (선택사항)")] public TextMeshProUGUI healthText; [Tooltip("체력바 전체 컨테이너")] public GameObject barContainer; [Header("Settings")] [Tooltip("유닛 위쪽으로의 높이 오프셋")] public float heightOffset = 2f; [Tooltip("체력이 가득 찼을 때 체력바 숨김 여부")] public bool hideWhenFull = true; [Tooltip("체력이 가득 찬 후 숨기까지의 지연 시간")] public float hideDelay = 3f; [Tooltip("초기 표시 지속 시간")] public float initialShowDuration = 2f; [Header("Colors")] public Color fullHealthColor = Color.green; public Color mediumHealthColor = Color.yellow; public Color lowHealthColor = Color.red; [Range(0f, 1f)] public float mediumHealthThreshold = 0.6f; [Range(0f, 1f)] public float lowHealthThreshold = 0.3f; private IHealthProvider _healthProvider; private Camera _mainCamera; private float _lastShowTime; private Canvas _canvas; private bool _initialized = false; private Transform _targetTransform; // 따라갈 타겟 private void Awake() { // Canvas 설정 _canvas = GetComponent(); if (_canvas == null) { _canvas = gameObject.AddComponent(); } _canvas.renderMode = RenderMode.WorldSpace; // Canvas Scaler 설정 var scaler = GetComponent(); if (scaler == null) { scaler = gameObject.AddComponent(); } scaler.dynamicPixelsPerUnit = 10f; } private void Start() { _mainCamera = Camera.main; } /// /// 체력바 초기화 /// /// 체력 정보를 제공하는 유닛 public void Initialize(IHealthProvider healthProvider) { _healthProvider = healthProvider; _targetTransform = (healthProvider as Component)?.transform; // 부모와의 관계를 끊고 독립적으로 이동 transform.SetParent(null); // 카메라 방향으로 한 번만 회전 설정 if (_mainCamera == null) { _mainCamera = Camera.main; } if (_mainCamera != null) { // 카메라의 회전을 가져와서 체력바가 카메라를 향하도록 설정 // Y축 회전만 사용하고, X축 90도로 Canvas가 수평이 되도록 함 float cameraYRotation = _mainCamera.transform.eulerAngles.y; transform.rotation = Quaternion.Euler(90, cameraYRotation, 0); } // 초기 체력바 표시 ShowBar(initialShowDuration); _initialized = true; // 초기 체력 업데이트 UpdateHealth(); } /// /// 체력바를 지정된 시간 동안 표시 /// public void ShowBar(float duration = 0f) { if (barContainer != null) { barContainer.SetActive(true); _lastShowTime = Time.time; // duration이 0보다 크면 그 시간 동안만 표시 if (duration > 0) { _lastShowTime = Time.time + duration - hideDelay; } } } /// /// 체력바 즉시 숨김 /// public void HideBar() { if (barContainer != null) { barContainer.SetActive(false); } } /// /// 체력 정보 업데이트 (IHealthProvider 사용) /// public void UpdateHealth() { if (_healthProvider == null) return; int currentHealth = _healthProvider.GetCurrentHealth(); int maxHealth = _healthProvider.GetMaxHealth(); float healthPercentage = _healthProvider.GetHealthPercentage(); UpdateHealthDisplay(currentHealth, maxHealth, healthPercentage); } /// /// 체력 정보 업데이트 (직접 값 전달) /// public void UpdateHealth(int currentHealth, int maxHealth) { float healthPercentage = maxHealth > 0 ? (float)currentHealth / maxHealth : 0f; UpdateHealthDisplay(currentHealth, maxHealth, healthPercentage); } private void UpdateHealthDisplay(int currentHealth, int maxHealth, float healthPercentage) { if (fillImage != null) { fillImage.fillAmount = healthPercentage; fillImage.color = GetHealthColor(healthPercentage); } if (healthText != null) { healthText.text = $"{currentHealth}/{maxHealth}"; } // 체력 변경 시 체력바 표시 ShowBar(); } private void LateUpdate() { // 타겟을 따라 이동 (회전은 하지 않음) if (_targetTransform != null) { transform.position = _targetTransform.position + Vector3.up * heightOffset; } // 체력이 가득 차면 숨김 if (hideWhenFull && barContainer != null && _healthProvider != null && _initialized) { float healthPercentage = _healthProvider.GetHealthPercentage(); if (healthPercentage >= 1f && Time.time - _lastShowTime > hideDelay) { barContainer.SetActive(false); } } } private Color GetHealthColor(float healthPercentage) { if (healthPercentage <= lowHealthThreshold) return lowHealthColor; else if (healthPercentage <= mediumHealthThreshold) return mediumHealthColor; else return fullHealthColor; } } }