using System; using UnityEngine; namespace Core.Health { /// /// Damageable class for handling health using events /// Could be used on Players or enemies or even destructable world objects /// [Serializable] public class Damageable { /// /// The max health of this instance /// public float maxHealth; public float startingHealth; /// /// The alignment of the damager /// public SerializableIAlignmentProvider alignment; // events public event Action reachedMaxHealth; // 当前的Entity用于响应各种血量相关的事件。 public event Action damaged, healed, died, healthChanged; /// /// Gets the current health. /// public float currentHealth { protected set; get; } /// /// 设置无敌状态. /// public bool bInvincible { get; set; } /// /// Gets the normalised health. /// public float normalisedHealth { get { if (Math.Abs(maxHealth) <= Mathf.Epsilon) { Debug.LogError("Max Health is 0"); maxHealth = 1f; } return currentHealth / maxHealth; } } /// /// Gets the of this instance /// public IAlignmentProvider alignmentProvider { get { return alignment != null ? alignment.GetInterface() : null; } } /// /// Gets whether this instance is dead. /// public bool isDead { get { return currentHealth < 1.0f; } } /// /// Gets whether this instance is at max health. /// public bool isAtMaxHealth { get { return Mathf.Approximately(currentHealth, maxHealth); } } /// /// Init this instance /// public virtual void Init() { currentHealth = startingHealth; } /// /// Sets the max health and starting health to the same value /// public void SetMaxHealth(float health) { if (health <= 0) { return; } maxHealth = startingHealth = health; } /// /// Sets the max health and starting health separately /// public void SetMaxHealth(float health, float startingHealth) { if (health <= 0) { return; } maxHealth = health; this.startingHealth = startingHealth; } /// /// Sets this instance's health directly. /// /// /// The value to set to /// public void SetHealth(float health) { var info = new HealthChangeInfo { damageable = this, newHealth = health, oldHealth = currentHealth }; currentHealth = health; info.oldHealth = health; if (healthChanged != null) { healthChanged(info); } } /// /// Use the alignment to see if taking damage is a valid action /// /// /// The damage to take /// /// /// The alignment of the other combatant /// /// /// The output data if there is damage taken /// /// /// true if this instance took damage /// false if this instance was already dead, or the alignment did not allow the damage /// public bool TakeDamage(float damage, IAlignmentProvider damageAlignment, ref HealthChangeInfo output) { /*output = new HealthChangeInfo { damageAlignment = damageAlignment, damageable = this, newHealth = currentHealth, oldHealth = currentHealth };*/ output.damageAlignment = damageAlignment; output.damageable = this; output.newHealth = currentHealth; output.oldHealth = currentHealth; // 无敌状态下,不可伤害. if (this.bInvincible) return false; bool canDamage = damageAlignment == null || alignmentProvider == null || damageAlignment.CanHarm(alignmentProvider); if (isDead || !canDamage) { return false; } ChangeHealth(-damage, ref output); SafelyDoAction(damaged, output); if (isDead) { SafelyDoAction(died, output); } return true; } /// /// Logic for increasing the health. /// /// Health. public HealthChangeInfo IncreaseHealth(float health) { var info = new HealthChangeInfo {damageable = this}; ChangeHealth(health, ref info); SafelyDoAction(healed, info); if (isAtMaxHealth) { SafelyDoAction(reachedMaxHealth); } return info; } /// /// Changes the health. /// /// Health increment. /// HealthChangeInfo for this change protected void ChangeHealth(float healthIncrement, ref HealthChangeInfo info) { info.oldHealth = currentHealth; currentHealth += healthIncrement; currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth); info.newHealth = currentHealth; if (healthChanged != null) { healthChanged(info); } } /// /// A helper method for null checking actions /// /// Action to be done protected void SafelyDoAction(Action action) { if (action != null) { action(); } } /// /// A helper method for null checking actions /// /// Action to be done /// The HealthChangeInfo to be passed to the Action protected void SafelyDoAction(Action action, HealthChangeInfo info) { if (action != null) { action(info); } } } }