Damager / Damageable
Basic Damager/Damageable aspects. You can include generic animations on hit or death or a resurection system.
Damageable aspect
namespace MyProject {
[AddComponentMenu("")]
[RequireComponent(typeof(HealthStat))]
public class Damageable : GameEntityAspect {
public float InvincibilityDuration = 2f;
public bool DestroyOnDeath = true;
protected HealthStat _health;
public bool IsInvincible { get; set; }
/// <summary>
/// Invoked when damage is taken (nb of damages)
/// </summary>
public System.Action<int> OnTakeDamage = delegate { };
/// <summary>
/// Invoked when the entity's health reaches zero
/// </summary>
public System.Action OnDie = delegate { };
protected virtual void Awake() {
_health = GetComponent<HealthStat>();
}
void Start() {
OnDie += () => {
enabled = false;
if (this != null && DestroyOnDeath) {
gameObject.SafeDestroy();
}
};
_health.ValueChanged += (current, old) => {
if (current <= 0f && old > 0f) {
OnDie.Invoke();
}
};
}
public void TakeDamage(int damage, bool ignoreInvincibility = false) {
if (_health.Value <= 0f || (IsInvincible && !ignoreInvincibility && damage >= 0f)) return;
// apply damage (or heal)
_health.Value -= damage;
if (damage <= 0f) return; // heal or miss
// Notify
OnTakeDamage.Invoke(damage, this);
// Health reached zero. The entity is dead.
if (_health.Value <= 0f) {
IsInvincible = true;
if (DestroyOnDeath) {
StartCoroutine(DestroyAfter());
}
}
else if (InvincibilityDuration > 0f) {
IsInvincible = true;
StartCoroutine(InvicibilityTimeRoutine());
}
}
protected virtual IEnumerator DestroyAfter() {
yield return new WaitForEndOfFrame();
Destroy(gameObject);
}
protected virtual IEnumerator InvicibilityTimeRoutine() {
yield return new WaitForSeconds(InvincibilityDuration);
IsInvincible = false;
}
}
}
Damager aspect
namespace MyProject {
[AddComponentMenu("")]
[RequireComponent(typeof(GameEntitySensor), typeof(PowerStat))]
public class Damager : GameEntityAspect {
public float DamageTick = 0.1f;
public bool DisableOnHit = false;
[Tooltip("Repulse entity on hit")] public float Repulse = 0f;
[Tooltip("Repulse based on its Owner")] public bool RepulseFromOwner = false;
TeamStat _team;
PowerStat _power; // example: a stat based on level and weapon
Dictionary<GameEntity, Coroutine> _routines;
public virtual void Start() {
_team = Owner.GetComponent<TeamStat>();
_power = GetComponent<PowerStat>();
var sensor = GetComponent<GameEntitySensor>();
sensor.OnEnter += ge => {
if (!enabled) return;
if (_routines.ContainsKey(ge)) StopCoroutine(_routines[ge]);
_routine[ge] = StartCoroutine(Tick(ge));
};
sensor.OnExit += ge => {
if (_routines.ContainsKey(ge)) {
StopCoroutine(_routines[ge]);
_routines.Remove(ge);
}
};
}
protected virtual void OnDisable() {
StopAllCoroutines();
}
IEnumerator Tick(GameEntity ge) {
var damageable = ge.GetComponent<Damageable>();
if (damageable == null) yield break;
if (Owner == damageable.Owner) yield break; // ignore itself
if (_team != null && _team.IsEqual(damageable.GameEntity)) yield break; // check team (or ignore if not defined)
if (DisableOnHit) {
enabled = false;
HitAction(damageable);
yield break;
}
while (true) {
if (enabled) {
if (damageable == null) yield break;
if (damageable.enabled) {
HitAction(damageable);
}
else yield break;
}
yield return new WaitForSeconds(DamageTick);
}
}
protected virtual void HitAction(Damageable damageable) {
damageable.TakeDamage(_power.Value);
if (Repulse > 0f) {
var direction = (RepulseFromOwner ? damageable.transform.position - Owner.transform.position : damageable.transform.position - transform.position).normalized;
//CallAnApplyForceMethod(direction * Repulse);
}
}
}
}