226 lines
6.5 KiB
C#
226 lines
6.5 KiB
C#
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
namespace Northbound
|
|
{
|
|
/// <summary>
|
|
/// 적대 유닛 (적대세력 또는 몬스터)
|
|
/// </summary>
|
|
[RequireComponent(typeof(Collider))]
|
|
public class EnemyUnit : NetworkBehaviour, IDamageable, ITeamMember, IHealthProvider
|
|
{
|
|
[Header("Team Settings")]
|
|
[Tooltip("이 유닛의 팀 (Hostile = 적대세력, Monster = 몬스터)")]
|
|
public TeamType enemyTeam = TeamType.Hostile;
|
|
|
|
[Header("Combat")]
|
|
public int maxHealth = 100;
|
|
|
|
[Header("Visual")]
|
|
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,
|
|
NetworkVariableWritePermission.Server
|
|
);
|
|
|
|
private NetworkVariable<TeamType> _team = new NetworkVariable<TeamType>(
|
|
TeamType.Neutral,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server
|
|
);
|
|
|
|
/// <summary>
|
|
/// 사망 시 발생하는 이벤트 (매개변수: killerId)
|
|
/// </summary>
|
|
public event System.Action<ulong> OnDeath;
|
|
|
|
/// <summary>
|
|
/// 데미지를 받았을 때 발생하는 이벤트 (매개변수: attackerId, damage)
|
|
/// </summary>
|
|
public event System.Action<ulong, int> OnDamageTaken;
|
|
|
|
private UnitHealthBar _healthBar;
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
base.OnNetworkSpawn();
|
|
|
|
if (IsServer)
|
|
{
|
|
_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)
|
|
{
|
|
if (!IsServer) return;
|
|
if (_currentHealth.Value <= 0) return;
|
|
|
|
// 공격자의 팀 확인
|
|
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(attackerId, out NetworkObject attackerObj))
|
|
{
|
|
var attackerTeamMember = attackerObj.GetComponent<ITeamMember>();
|
|
if (attackerTeamMember != null)
|
|
{
|
|
if (!TeamManager.CanAttack(attackerTeamMember, this))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
int actualDamage = Mathf.Min(damage, _currentHealth.Value);
|
|
_currentHealth.Value -= actualDamage;
|
|
|
|
// 데미지 이펙트
|
|
ShowDamageEffectClientRpc();
|
|
|
|
// 데미지 받음 이벤트 발생 (AI 어그로 시스템용)
|
|
OnDamageTaken?.Invoke(attackerId, actualDamage);
|
|
|
|
// 체력이 0이 되면 파괴
|
|
if (_currentHealth.Value <= 0)
|
|
{
|
|
DestroyUnit(attackerId);
|
|
}
|
|
}
|
|
|
|
private void DestroyUnit(ulong attackerId)
|
|
{
|
|
if (!IsServer) return;
|
|
|
|
// 사망 이벤트 발생 (애니메이션 등)
|
|
OnDeath?.Invoke(attackerId);
|
|
|
|
// 파괴 이펙트
|
|
ShowDestroyEffectClientRpc();
|
|
|
|
// 네트워크 오브젝트 파괴
|
|
Invoke(nameof(DespawnUnit), 3.0f);
|
|
}
|
|
|
|
private void DespawnUnit()
|
|
{
|
|
if (IsServer && NetworkObject != null)
|
|
{
|
|
NetworkObject.Despawn(true);
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.ClientsAndHost)]
|
|
private void ShowDamageEffectClientRpc()
|
|
{
|
|
if (damageEffectPrefab != null)
|
|
{
|
|
GameObject effect = Instantiate(damageEffectPrefab, transform.position, Quaternion.identity);
|
|
Destroy(effect, 2f);
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.ClientsAndHost)]
|
|
private void ShowDestroyEffectClientRpc()
|
|
{
|
|
if (destroyEffectPrefab != null)
|
|
{
|
|
GameObject effect = Instantiate(destroyEffectPrefab, transform.position, Quaternion.identity);
|
|
Destroy(effect, 3f);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ITeamMember Implementation
|
|
|
|
public bool IsDead() => _currentHealth.Value <= 0;
|
|
|
|
public TeamType GetTeam() => _team.Value;
|
|
|
|
public void SetTeam(TeamType team)
|
|
{
|
|
if (!IsServer) return;
|
|
_team.Value = team;
|
|
}
|
|
|
|
#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
|
|
if (Application.isPlaying)
|
|
{
|
|
UnityEditor.Handles.Label(transform.position + Vector3.up * 2f,
|
|
$"Team: {TeamManager.GetTeamName(_team.Value)}\nHP: {_currentHealth.Value}/{maxHealth}");
|
|
}
|
|
else
|
|
{
|
|
UnityEditor.Handles.Label(transform.position + Vector3.up * 2f,
|
|
$"Team: {TeamManager.GetTeamName(enemyTeam)}");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
} |