Created
          September 21, 2025 11:43 
        
      - 
      
- 
        Save adammyhre/9efde70222ecdb2254b08bae9ca60ade to your computer and use it in GitHub Desktop. 
Revisions
- 
        adammyhre created this gist Sep 21, 2025 .There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,83 @@ using System; using System.Collections.Generic; using ImprovedTimers; // https://github.com/adammyhre/Unity-Improved-Timers using UnityEngine; [Serializable] public class Ability { public AudioClip castSfx; public GameObject castVfx; public GameObject runningVfx; [SerializeReference] public List<IEffect<IDamageable>> effects = new(); public void Execute(IDamageable target) { foreach (var effect in effects) { if (target is Enemy enemy) { enemy.ApplyEffect(effect); } else { effect.Apply(target); } } } } public interface IDamageable { void TakeDamage(int amount); } public interface IEffect<TTarget> { void Apply(TTarget target); void Cancel(); event Action<IEffect<TTarget>> OnCompleted; } [Serializable] public class DamageEffect : IEffect<IDamageable> { public int damageAmount = 10; public event Action<IEffect<IDamageable>> OnCompleted; public void Apply(IDamageable target) { target.TakeDamage(damageAmount); OnCompleted?.Invoke(this); } public void Cancel() { OnCompleted?.Invoke(this); } } [Serializable] public class DamageOverTimeEffect : IEffect<IDamageable> { public float duration = 5f; public float tickInterval = 1f; public int damagePerTick; public event Action<IEffect<IDamageable>> OnCompleted; IntervalTimer timer; IDamageable currentTarget; public void Apply(IDamageable target) { currentTarget = target; timer = new IntervalTimer(duration, tickInterval); timer.OnInterval = OnInterval; timer.OnTimerStop = OnStop; timer.Start(); } void OnInterval() => currentTarget?.TakeDamage(damagePerTick); void OnStop() => Cleanup(); public void Cancel() { timer?.Stop(); Cleanup(); } void Cleanup() { timer = null; currentTarget = null; OnCompleted?.Invoke(this); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,42 @@ using System.Collections.Generic; using UnityEngine; public class Enemy : MonoBehaviour, IDamageable { public int health = 50; readonly List<IEffect<IDamageable>> activeEffects = new(); public void TakeDamage(int amount) { health -= amount; Debug.Log($"Enemy took {amount} damage. Health now {health}"); if (health <= 0) { Die(); } } public void ApplyEffect(IEffect<IDamageable> effect) { if (health <= 0) return; // Dead enemies should't receive effects effect.OnCompleted += RemoveEffect; activeEffects.Add(effect); effect.Apply(this); } void RemoveEffect(IEffect<IDamageable> effect) { effect.OnCompleted -= RemoveEffect; activeEffects.Remove(effect); } void Die() { Debug.Log("Enemy died."); for (int i = activeEffects.Count - 1; i >= 0; i--) { var effect = activeEffects[i]; effect.OnCompleted -= RemoveEffect; effect.Cancel(); } activeEffects.Clear(); Destroy(gameObject); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,34 @@ using System.Collections; using UnityEngine; using UnityUtils; // https://github.com/adammyhre/Unity-Utils public class PlayerAbilityCaster : MonoBehaviour { public Ability[] hotbar; void Update() { for (int i = 0; i < hotbar.Length; i++) { if (Input.GetKeyDown(KeyCode.Alpha1 + i)) { Cast(hotbar[i], FindFirstObjectByType<Enemy>()); } } } void Cast(Ability ability, IDamageable target) { ability.Execute(target); var targetMb = target as MonoBehaviour; if (ability.castVfx && targetMb) { Instantiate(ability.castVfx, targetMb.transform.position.Add(y:2), Quaternion.identity); } if (ability.runningVfx && targetMb) { var runningVfxInstance = Instantiate(ability.runningVfx, targetMb.transform); Destroy(runningVfxInstance, 3f); } if (ability.castSfx) { AudioSource.PlayClipAtPoint(ability.castSfx, transform.position); } } }