Created
September 21, 2025 11:43
-
-
Save adammyhre/9efde70222ecdb2254b08bae9ca60ade to your computer and use it in GitHub Desktop.
Effects Over Time Example
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 characters
| 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 characters
| 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 characters
| 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); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment