체력바 추가
플레이어는 상시 표시, 나머지는 체력 변경 시 표시
This commit is contained in:
@@ -1370,14 +1370,14 @@
|
||||
<HintPath>Library\ScriptAssemblies\Unity.2D.Tilemap.Editor.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.InputSystem.ForUI">
|
||||
<HintPath>Library\ScriptAssemblies\Unity.InputSystem.ForUI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Profiling.Core">
|
||||
<HintPath>Library\ScriptAssemblies\Unity.Profiling.Core.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.InputSystem.ForUI">
|
||||
<HintPath>Library\ScriptAssemblies\Unity.InputSystem.ForUI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.RenderPipelines.GPUDriven.Runtime">
|
||||
<HintPath>Library\ScriptAssemblies\Unity.RenderPipelines.GPUDriven.Runtime.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<Compile Include="Assets\Scripts\TeamManager.cs" />
|
||||
<Compile Include="Assets\Scripts\TeamGate.cs" />
|
||||
<Compile Include="Assets\Scripts\Worker.cs" />
|
||||
<Compile Include="Assets\Scripts\IHealthProvider.cs" />
|
||||
<Compile Include="Assets\Scripts\WorkerSpawner.cs" />
|
||||
<Compile Include="Assets\Scripts\Resource.cs" />
|
||||
<Compile Include="Assets\Data\Scripts\DataClasses\UpgradeData.cs" />
|
||||
@@ -88,6 +89,7 @@
|
||||
<Compile Include="Assets\Scripts\PlayerStats.cs" />
|
||||
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Scripts\Buoyancy.cs" />
|
||||
<Compile Include="Assets\Scripts\ObstacleSpawner.cs" />
|
||||
<Compile Include="Assets\Scripts\UnitHealthBar.cs" />
|
||||
<Compile Include="Assets\Scripts\BuildingFoundation.cs" />
|
||||
<Compile Include="Assets\Scripts\AttackAction.cs" />
|
||||
<Compile Include="Assets\Scripts\IInteractable.cs" />
|
||||
@@ -1416,14 +1418,14 @@
|
||||
<HintPath>Library\ScriptAssemblies\Unity.2D.Tilemap.Editor.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.InputSystem.ForUI">
|
||||
<HintPath>Library\ScriptAssemblies\Unity.InputSystem.ForUI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Profiling.Core">
|
||||
<HintPath>Library\ScriptAssemblies\Unity.Profiling.Core.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.InputSystem.ForUI">
|
||||
<HintPath>Library\ScriptAssemblies\Unity.InputSystem.ForUI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.RenderPipelines.GPUDriven.Runtime">
|
||||
<HintPath>Library\ScriptAssemblies\Unity.RenderPipelines.GPUDriven.Runtime.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
||||
@@ -106,6 +106,8 @@ MonoBehaviour:
|
||||
maxHealth: 100
|
||||
damageEffectPrefab: {fileID: 4021103657954561961, guid: 5c755f9bc5253ea418e919994537dcc7, type: 3}
|
||||
destroyEffectPrefab: {fileID: 141433446842962269, guid: 9fe8f8b3288e45a44af36ff8aa04486e, type: 3}
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!195 &4485945348237935463
|
||||
NavMeshAgent:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -106,6 +106,8 @@ MonoBehaviour:
|
||||
maxHealth: 100
|
||||
damageEffectPrefab: {fileID: 4021103657954561961, guid: 5c755f9bc5253ea418e919994537dcc7, type: 3}
|
||||
destroyEffectPrefab: {fileID: 141433446842962269, guid: 9fe8f8b3288e45a44af36ff8aa04486e, type: 3}
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!195 &4485945348237935463
|
||||
NavMeshAgent:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -983,6 +983,8 @@ MonoBehaviour:
|
||||
maxHealth: 20
|
||||
damageEffectPrefab: {fileID: 4021103657954561961, guid: 5c755f9bc5253ea418e919994537dcc7, type: 3}
|
||||
destroyEffectPrefab: {fileID: 141433446842962269, guid: 9fe8f8b3288e45a44af36ff8aa04486e, type: 3}
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!195 &2894690479083926678
|
||||
NavMeshAgent:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -1317,6 +1317,8 @@ MonoBehaviour:
|
||||
maxHealth: 75
|
||||
damageEffectPrefab: {fileID: 4021103657954561961, guid: 5c755f9bc5253ea418e919994537dcc7, type: 3}
|
||||
destroyEffectPrefab: {fileID: 141433446842962269, guid: 9fe8f8b3288e45a44af36ff8aa04486e, type: 3}
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!195 &3634736894319727576
|
||||
NavMeshAgent:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -1741,6 +1741,8 @@ MonoBehaviour:
|
||||
maxHealth: 125
|
||||
damageEffectPrefab: {fileID: 4021103657954561961, guid: 5c755f9bc5253ea418e919994537dcc7, type: 3}
|
||||
destroyEffectPrefab: {fileID: 141433446842962269, guid: 9fe8f8b3288e45a44af36ff8aa04486e, type: 3}
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!195 &2580823509700602203
|
||||
NavMeshAgent:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -663,6 +663,8 @@ MonoBehaviour:
|
||||
maxHealth: 65
|
||||
damageEffectPrefab: {fileID: 4021103657954561961, guid: 5c755f9bc5253ea418e919994537dcc7, type: 3}
|
||||
destroyEffectPrefab: {fileID: 141433446842962269, guid: 9fe8f8b3288e45a44af36ff8aa04486e, type: 3}
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!195 &7475699343328683952
|
||||
NavMeshAgent:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -1096,6 +1096,8 @@ MonoBehaviour:
|
||||
maxHealth: 45
|
||||
damageEffectPrefab: {fileID: 4021103657954561961, guid: 5c755f9bc5253ea418e919994537dcc7, type: 3}
|
||||
destroyEffectPrefab: {fileID: 141433446842962269, guid: 9fe8f8b3288e45a44af36ff8aa04486e, type: 3}
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!195 &5366269420367463183
|
||||
NavMeshAgent:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -1341,6 +1341,8 @@ MonoBehaviour:
|
||||
maxHealth: 100
|
||||
damageEffectPrefab: {fileID: 4021103657954561961, guid: 5c755f9bc5253ea418e919994537dcc7, type: 3}
|
||||
destroyEffectPrefab: {fileID: 141433446842962269, guid: 9fe8f8b3288e45a44af36ff8aa04486e, type: 3}
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!195 &1255224548206942124
|
||||
NavMeshAgent:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -93,6 +93,8 @@ MonoBehaviour:
|
||||
deathEffectPrefab: {fileID: 5642766282230003982, guid: b5c8ca7ed10b61e499cce8ec3b6e2e4c, type: 3}
|
||||
resourcePickupPrefab: {fileID: 1627676033990080135, guid: 8c45964a69bf8fa4ba461ed217bc052f, type: 3}
|
||||
respawnDelay: 10
|
||||
showHealthBar: 1
|
||||
healthBarPrefab: {fileID: 100000, guid: 8e7a5b12c9f8a4a5ba3c8d1f2e5a7b9c, type: 3}
|
||||
--- !u!143 &3007098678582223509
|
||||
CharacterController:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -727,8 +727,8 @@ Transform:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.38268346, y: -0.00000022436977, z: 0.000000092937015, w: 0.92387956}
|
||||
m_LocalPosition: {x: -16.14601, y: 18.99998, z: 514.7231}
|
||||
m_LocalRotation: {x: 0.60876137, y: 0, z: 0, w: 0.7933534}
|
||||
m_LocalPosition: {x: -16.14601, y: 18.99998, z: 529.7231}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
@@ -2100,13 +2100,13 @@ Transform:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1290143989}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.38268343, y: -0.00000022436976, z: 0.00000009293699, w: 0.92387956}
|
||||
m_LocalPosition: {x: -16.14601, y: 18.99998, z: 514.7231}
|
||||
m_LocalRotation: {x: 0.60876137, y: 0, z: 0, w: 0.7933534}
|
||||
m_LocalPosition: {x: -16.14601, y: 18.99998, z: 529.7231}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 61373299}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_LocalEulerAnglesHint: {x: 75, y: 0, z: 0}
|
||||
--- !u!114 &1290143992
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2125,7 +2125,7 @@ MonoBehaviour:
|
||||
AngularDampingMode: 0
|
||||
RotationDamping: {x: 1, y: 1, z: 1}
|
||||
QuaternionDamping: 1
|
||||
FollowOffset: {x: 0, y: 18, z: -18}
|
||||
FollowOffset: {x: 0, y: 18, z: -3}
|
||||
--- !u!114 &1290143993
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -5,7 +5,7 @@ using Northbound.Data;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
public class Building : NetworkBehaviour, IDamageable, IVisionProvider, ITeamMember
|
||||
public class Building : NetworkBehaviour, IDamageable, IVisionProvider, ITeamMember, IHealthProvider
|
||||
{
|
||||
[Header("References")]
|
||||
public TowerData buildingData;
|
||||
@@ -63,7 +63,7 @@ namespace Northbound
|
||||
public event Action OnDestroyed;
|
||||
public event Action<TeamType> OnTeamChanged;
|
||||
|
||||
private BuildingHealthBar _healthBar;
|
||||
private UnitHealthBar _healthBar;
|
||||
private float _lastRegenTime;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
@@ -390,7 +390,7 @@ namespace Northbound
|
||||
}
|
||||
|
||||
GameObject healthBarObj = Instantiate(healthBarPrefab, transform);
|
||||
_healthBar = healthBarObj.GetComponent<BuildingHealthBar>();
|
||||
_healthBar = healthBarObj.GetComponent<UnitHealthBar>();
|
||||
|
||||
if (_healthBar != null)
|
||||
{
|
||||
|
||||
@@ -6,133 +6,11 @@ namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 건물 위에 표시되는 체력바
|
||||
/// UnitHealthBar의 별칭 (하위 호환성 유지)
|
||||
/// </summary>
|
||||
public class BuildingHealthBar : MonoBehaviour
|
||||
[System.Obsolete("Use UnitHealthBar instead. This class is kept for backward compatibility with existing prefabs.")]
|
||||
public class BuildingHealthBar : UnitHealthBar
|
||||
{
|
||||
[Header("UI References")]
|
||||
public Image fillImage;
|
||||
public TextMeshProUGUI healthText;
|
||||
public GameObject barContainer;
|
||||
|
||||
[Header("Settings")]
|
||||
public float heightOffset = 3f;
|
||||
public bool hideWhenFull = true;
|
||||
public float hideDelay = 3f;
|
||||
public float initialShowDuration = 2f; // 건설 완료 시 체력바 표시 시간
|
||||
|
||||
[Header("Colors")]
|
||||
public Color fullHealthColor = Color.green;
|
||||
public Color mediumHealthColor = Color.yellow;
|
||||
public Color lowHealthColor = Color.red;
|
||||
public float mediumHealthThreshold = 0.6f;
|
||||
public float lowHealthThreshold = 0.3f;
|
||||
|
||||
private Building _building;
|
||||
private Camera _mainCamera;
|
||||
private float _lastShowTime;
|
||||
private Canvas _canvas;
|
||||
private bool _initialized = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Canvas 설정
|
||||
_canvas = GetComponent<Canvas>();
|
||||
if (_canvas == null)
|
||||
{
|
||||
_canvas = gameObject.AddComponent<Canvas>();
|
||||
}
|
||||
_canvas.renderMode = RenderMode.WorldSpace;
|
||||
|
||||
// Canvas Scaler 설정
|
||||
var scaler = GetComponent<CanvasScaler>();
|
||||
if (scaler == null)
|
||||
{
|
||||
scaler = gameObject.AddComponent<CanvasScaler>();
|
||||
}
|
||||
scaler.dynamicPixelsPerUnit = 10f;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_mainCamera = Camera.main;
|
||||
}
|
||||
|
||||
public void Initialize(Building building)
|
||||
{
|
||||
_building = building;
|
||||
transform.localPosition = Vector3.up * heightOffset;
|
||||
|
||||
// 건설 완료 시 체력바 표시
|
||||
ShowBar(initialShowDuration);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 체력바를 지정된 시간 동안 표시
|
||||
/// </summary>
|
||||
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 UpdateHealth(int currentHealth, int maxHealth)
|
||||
{
|
||||
if (fillImage != null)
|
||||
{
|
||||
float healthPercentage = maxHealth > 0 ? (float)currentHealth / maxHealth : 0f;
|
||||
fillImage.fillAmount = healthPercentage;
|
||||
|
||||
// 체력에 따른 색상 변경
|
||||
fillImage.color = GetHealthColor(healthPercentage);
|
||||
}
|
||||
|
||||
if (healthText != null)
|
||||
{
|
||||
healthText.text = $"{currentHealth}/{maxHealth}";
|
||||
}
|
||||
|
||||
// 체력 변경 시 체력바 표시
|
||||
ShowBar();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 카메라를 향하도록 회전
|
||||
if (_mainCamera != null)
|
||||
{
|
||||
transform.rotation = Quaternion.LookRotation(transform.position - _mainCamera.transform.position);
|
||||
}
|
||||
|
||||
// 체력이 가득 차면 숨김
|
||||
if (hideWhenFull && barContainer != null && _building != null && _initialized)
|
||||
{
|
||||
float healthPercentage = _building.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;
|
||||
}
|
||||
// UnitHealthBar를 상속받아 기존 프리팹과의 호환성 유지
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace Northbound
|
||||
/// 적대 유닛 (적대세력 또는 몬스터)
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class EnemyUnit : NetworkBehaviour, IDamageable, ITeamMember
|
||||
public class EnemyUnit : NetworkBehaviour, IDamageable, ITeamMember, IHealthProvider
|
||||
{
|
||||
[Header("Team Settings")]
|
||||
[Tooltip("이 유닛의 팀 (Hostile = 적대세력, Monster = 몬스터)")]
|
||||
@@ -20,6 +20,10 @@ namespace Northbound
|
||||
public GameObject damageEffectPrefab;
|
||||
public GameObject destroyEffectPrefab;
|
||||
|
||||
[Header("Health Bar")]
|
||||
public bool showHealthBar = true;
|
||||
public GameObject healthBarPrefab;
|
||||
|
||||
private NetworkVariable<int> _currentHealth = new NetworkVariable<int>(
|
||||
0,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
@@ -37,6 +41,8 @@ namespace Northbound
|
||||
/// </summary>
|
||||
public event System.Action<ulong> OnDeath;
|
||||
|
||||
private UnitHealthBar _healthBar;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
base.OnNetworkSpawn();
|
||||
@@ -46,13 +52,48 @@ namespace Northbound
|
||||
_currentHealth.Value = maxHealth;
|
||||
_team.Value = enemyTeam;
|
||||
}
|
||||
|
||||
// 체력 변경 이벤트 구독
|
||||
_currentHealth.OnValueChanged += OnHealthChanged;
|
||||
|
||||
// 체력바 생성
|
||||
if (showHealthBar && healthBarPrefab != null)
|
||||
{
|
||||
CreateHealthBar();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
_currentHealth.OnValueChanged -= OnHealthChanged;
|
||||
base.OnNetworkDespawn();
|
||||
}
|
||||
|
||||
private void OnHealthChanged(int previousValue, int newValue)
|
||||
{
|
||||
if (_healthBar != null)
|
||||
{
|
||||
_healthBar.UpdateHealth();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateHealthBar()
|
||||
{
|
||||
if (_healthBar != null)
|
||||
return;
|
||||
|
||||
if (healthBarPrefab == null)
|
||||
return;
|
||||
|
||||
GameObject healthBarObj = Instantiate(healthBarPrefab, transform);
|
||||
_healthBar = healthBarObj.GetComponent<UnitHealthBar>();
|
||||
|
||||
if (_healthBar != null)
|
||||
{
|
||||
_healthBar.Initialize(this);
|
||||
}
|
||||
}
|
||||
|
||||
#region IDamageable Implementation
|
||||
|
||||
public void TakeDamage(int damage, ulong attackerId)
|
||||
@@ -144,6 +185,20 @@ namespace Northbound
|
||||
|
||||
#endregion
|
||||
|
||||
#region IHealthProvider Implementation
|
||||
|
||||
public int GetCurrentHealth() => _currentHealth.Value;
|
||||
|
||||
public int GetMaxHealth() => maxHealth;
|
||||
|
||||
public float GetHealthPercentage()
|
||||
{
|
||||
int max = GetMaxHealth();
|
||||
return max > 0 ? (float)_currentHealth.Value / max : 0f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
24
Assets/Scripts/IHealthProvider.cs
Normal file
24
Assets/Scripts/IHealthProvider.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 체력 정보를 제공하는 인터페이스
|
||||
/// Building, Player, Enemy 등 체력바가 필요한 모든 유닛이 구현
|
||||
/// </summary>
|
||||
public interface IHealthProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 현재 체력
|
||||
/// </summary>
|
||||
int GetCurrentHealth();
|
||||
|
||||
/// <summary>
|
||||
/// 최대 체력
|
||||
/// </summary>
|
||||
int GetMaxHealth();
|
||||
|
||||
/// <summary>
|
||||
/// 체력 비율 (0.0 ~ 1.0)
|
||||
/// </summary>
|
||||
float GetHealthPercentage();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/IHealthProvider.cs.meta
Normal file
2
Assets/Scripts/IHealthProvider.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30fb4b69605e00a4eb6cac716420c414
|
||||
@@ -6,7 +6,7 @@ using UnityEngine.InputSystem;
|
||||
using Unity.Cinemachine;
|
||||
using Northbound;
|
||||
|
||||
public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageable
|
||||
public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageable, IHealthProvider
|
||||
{
|
||||
[Header("Movement Settings")]
|
||||
public float rotationSpeed = 10f;
|
||||
@@ -22,6 +22,10 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
[SerializeField] private GameObject resourcePickupPrefab; // 자원 드랍 프리팹
|
||||
[SerializeField] private float respawnDelay = 10f; // 부활 대기 시간 (초)
|
||||
|
||||
[Header("Health Bar")]
|
||||
[SerializeField] private bool showHealthBar = true;
|
||||
[SerializeField] private GameObject healthBarPrefab;
|
||||
|
||||
// 이 플레이어를 제어하는 클라이언트 ID (서버 소유권이지만 논리적 소유자)
|
||||
private NetworkVariable<ulong> _ownerPlayerId = new NetworkVariable<ulong>(
|
||||
ulong.MaxValue,
|
||||
@@ -47,6 +51,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
private Animator _animator;
|
||||
private NetworkAnimator _networkAnimator;
|
||||
private PlayerStats _playerStats;
|
||||
private UnitHealthBar _healthBar;
|
||||
|
||||
// 이 플레이어가 로컬 플레이어인지 확인
|
||||
|
||||
@@ -88,6 +93,12 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
_currentHealth.OnValueChanged += OnHealthChanged;
|
||||
_ownerPlayerId.OnValueChanged += OnOwnerPlayerIdChanged;
|
||||
|
||||
// 체력바 생성
|
||||
if (showHealthBar && healthBarPrefab != null)
|
||||
{
|
||||
CreateHealthBar();
|
||||
}
|
||||
|
||||
// 이미 로컬 플레이어로 설정되어 있으면 입력 초기화
|
||||
TryInitializeLocalPlayer();
|
||||
}
|
||||
@@ -443,6 +454,12 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
|
||||
private void OnHealthChanged(int previousValue, int newValue)
|
||||
{
|
||||
// 체력바 업데이트
|
||||
if (_healthBar != null)
|
||||
{
|
||||
_healthBar.UpdateHealth();
|
||||
}
|
||||
|
||||
if (IsLocalPlayer)
|
||||
{
|
||||
// UI 업데이트 등
|
||||
@@ -451,6 +468,29 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
|
||||
#endregion
|
||||
|
||||
#region Health Bar
|
||||
|
||||
private void CreateHealthBar()
|
||||
{
|
||||
if (_healthBar != null)
|
||||
return;
|
||||
|
||||
if (healthBarPrefab == null)
|
||||
return;
|
||||
|
||||
GameObject healthBarObj = Instantiate(healthBarPrefab, transform);
|
||||
_healthBar = healthBarObj.GetComponent<UnitHealthBar>();
|
||||
|
||||
if (_healthBar != null)
|
||||
{
|
||||
// 플레이어 체력바는 상시 표시
|
||||
_healthBar.hideWhenFull = false;
|
||||
_healthBar.Initialize(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Gizmos
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
|
||||
201
Assets/Scripts/UnitHealthBar.cs
Normal file
201
Assets/Scripts/UnitHealthBar.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 유닛(플레이어, 적, 건물 등) 위에 표시되는 체력바
|
||||
/// IHealthProvider 인터페이스를 구현하는 모든 유닛에서 사용 가능
|
||||
/// </summary>
|
||||
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<Canvas>();
|
||||
if (_canvas == null)
|
||||
{
|
||||
_canvas = gameObject.AddComponent<Canvas>();
|
||||
}
|
||||
_canvas.renderMode = RenderMode.WorldSpace;
|
||||
|
||||
// Canvas Scaler 설정
|
||||
var scaler = GetComponent<CanvasScaler>();
|
||||
if (scaler == null)
|
||||
{
|
||||
scaler = gameObject.AddComponent<CanvasScaler>();
|
||||
}
|
||||
scaler.dynamicPixelsPerUnit = 10f;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_mainCamera = Camera.main;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 체력바 초기화
|
||||
/// </summary>
|
||||
/// <param name="healthProvider">체력 정보를 제공하는 유닛</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 체력바를 지정된 시간 동안 표시
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 체력바 즉시 숨김
|
||||
/// </summary>
|
||||
public void HideBar()
|
||||
{
|
||||
if (barContainer != null)
|
||||
{
|
||||
barContainer.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 체력 정보 업데이트 (IHealthProvider 사용)
|
||||
/// </summary>
|
||||
public void UpdateHealth()
|
||||
{
|
||||
if (_healthProvider == null) return;
|
||||
|
||||
int currentHealth = _healthProvider.GetCurrentHealth();
|
||||
int maxHealth = _healthProvider.GetMaxHealth();
|
||||
float healthPercentage = _healthProvider.GetHealthPercentage();
|
||||
|
||||
UpdateHealthDisplay(currentHealth, maxHealth, healthPercentage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 체력 정보 업데이트 (직접 값 전달)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UnitHealthBar.cs.meta
Normal file
2
Assets/Scripts/UnitHealthBar.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 860b045e364e57547811662937cb3ac6
|
||||
@@ -14,7 +14,7 @@ GameObject:
|
||||
- component: {fileID: 100004}
|
||||
- component: {fileID: 100005}
|
||||
m_Layer: 5
|
||||
m_Name: BuildingHealthBar
|
||||
m_Name: HealthBar
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -85,7 +85,7 @@ MonoBehaviour:
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
m_DynamicPixelsPerUnit: 10
|
||||
m_PresetInfoIsWorld: 0
|
||||
m_PresetInfoIsWorld: 1
|
||||
--- !u!114 &100004
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
Reference in New Issue
Block a user