Files
Northbound/Assets/Scripts/DamageableNetworkBehaviour.cs

163 lines
4.9 KiB
C#

using System;
using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
public abstract class DamageableNetworkBehaviour : NetworkBehaviour, IDamageable
{
[Header("Health Settings")]
[SerializeField] protected int maxHealth = 100;
[SerializeField] protected bool showHealthBar = true;
[Header("Visual Effects")]
[SerializeField] protected GameObject damageEffectPrefab;
[SerializeField] protected GameObject destroyEffectPrefab;
[SerializeField] protected Transform effectSpawnPoint;
protected NetworkVariable<int> _currentHealth = new NetworkVariable<int>(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
public event Action<int, int> OnHealthChanged;
public event Action OnDestroyed;
protected void InvokeOnHealthChanged(int currentHealth, int maxHealth)
{
OnHealthChanged?.Invoke(currentHealth, maxHealth);
}
protected void InvokeOnDestroyed()
{
OnDestroyed?.Invoke();
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
if (IsOwner)
{
InitializeHealthServerRpc(maxHealth);
}
_currentHealth.OnValueChanged += OnHealthValueChanged;
InitializeHealthBar();
UpdateHealthUI();
}
[ServerRpc]
private void InitializeHealthServerRpc(int health)
{
if (_currentHealth.Value == 0)
_currentHealth.Value = health;
}
public override void OnNetworkDespawn()
{
_currentHealth.OnValueChanged -= OnHealthValueChanged;
base.OnNetworkDespawn();
}
protected virtual void InitializeHealthBar()
{
}
protected virtual void UpdateHealthUI()
{
}
public virtual void TakeDamage(int damage, ulong attackerId)
{
if (!IsOwner)
{
TakeDamageServerRpc(damage, attackerId);
return;
}
TakeDamageServerRpc(damage, attackerId);
}
[ServerRpc]
private void TakeDamageServerRpc(int damage, ulong attackerId)
{
if (_currentHealth.Value <= 0)
return;
int actualDamage = Mathf.Min(damage, _currentHealth.Value);
_currentHealth.Value -= actualDamage;
Debug.Log($"<color=red>[{GetType().Name}] {gameObject.name} received {actualDamage} damage. Health: {_currentHealth.Value}/{maxHealth}</color>");
ShowDamageEffectClientRpc();
if (_currentHealth.Value <= 0)
{
Die(attackerId);
}
}
protected virtual void Die(ulong killerId)
{
Debug.Log($"<color=red>[{GetType().Name}] {gameObject.name} destroyed! Killer: {killerId}</color>");
OnDestroyed?.Invoke();
ShowDeathEffectClientRpc();
}
[ClientRpc]
protected void ShowDamageEffectClientRpc()
{
if (damageEffectPrefab != null)
{
Transform spawnPoint = effectSpawnPoint != null ? effectSpawnPoint : transform;
GameObject effect = Instantiate(damageEffectPrefab, spawnPoint.position + Vector3.up, Quaternion.identity);
Destroy(effect, 2f);
}
}
[ClientRpc]
protected void ShowDeathEffectClientRpc()
{
if (destroyEffectPrefab != null)
{
Transform spawnPoint = effectSpawnPoint != null ? effectSpawnPoint : transform;
GameObject effect = Instantiate(destroyEffectPrefab, spawnPoint.position, Quaternion.identity);
Destroy(effect, 3f);
}
}
protected virtual void OnHealthValueChanged(int previousValue, int newValue)
{
OnHealthChanged?.Invoke(newValue, maxHealth);
UpdateHealthUI();
}
public int GetCurrentHealth() => _currentHealth.Value;
public int GetMaxHealth() => maxHealth;
public float GetHealthPercentage() => maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f;
public bool IsDead() => _currentHealth.Value <= 0;
public virtual void Heal(int amount)
{
if (!IsOwner)
{
HealServerRpc(amount);
return;
}
HealServerRpc(amount);
}
[ServerRpc]
private void HealServerRpc(int amount)
{
int healAmount = Mathf.Min(amount, maxHealth - _currentHealth.Value);
_currentHealth.Value += healAmount;
Debug.Log($"<color=green>[{GetType().Name}] {gameObject.name} healed {healAmount}. Health: {_currentHealth.Value}/{maxHealth}</color>");
}
}
}