// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Tags { using UnityEngine; /// /// Base class for authoring custom effect behaviours that respond to the lifecycle of an active . /// /// /// /// Each behaviour asset is cloned per , which means derived classes can safely store mutable state /// between calls to , , /// , and . /// /// /// Attach behaviour assets to to augment the data-driven attribute pipeline with bespoke /// gameplay logic, visual or audio feedback, or integration hooks into other game systems. /// /// /// All callbacks are synchronously invoked by on the main thread, ensuring safe interaction with Unity APIs. /// /// /// /// /// using UnityEngine; /// using WallstopStudios.UnityHelpers.Tags; /// /// [CreateAssetMenu(menuName = "Game/Effects/Burning Behaviour")] /// public sealed class BurningBehavior : EffectBehavior /// { /// [SerializeField] /// private GameObject flamePrefab; /// /// private GameObject spawnedInstance; /// /// public override void OnApply(EffectBehaviorContext context) /// { /// if (flamePrefab == null) /// { /// return; /// } /// /// Transform parent = context.Target.transform; /// spawnedInstance = Object.Instantiate(flamePrefab, parent.position, parent.rotation, parent); /// } /// /// public override void OnPeriodicTick(EffectBehaviorContext context, PeriodicEffectTickContext tickContext) /// { /// // Cancel the effect early once the periodic bundle has executed three times. /// if (tickContext.executedTicks >= 3) /// { /// context.handler.RemoveEffect(context.handle); /// } /// } /// /// public override void OnRemove(EffectBehaviorContext context) /// { /// if (spawnedInstance != null) /// { /// Object.Destroy(spawnedInstance); /// spawnedInstance = null; /// } /// } /// } /// /// // Assign the behaviour asset to an AttributeEffect so it is cloned per application. /// AttributeEffect burnEffect = ScriptableObject.CreateInstance<AttributeEffect>(); /// burnEffect.behaviors.Add(burningBehaviorAsset); /// /// public abstract class EffectBehavior : ScriptableObject { /// /// Invoked once when the effect handle becomes active. /// /// Runtime context for the effect instance. public virtual void OnApply(EffectBehaviorContext context) { } /// /// Invoked every frame while the effect remains active. /// /// Runtime context for the effect instance. public virtual void OnTick(EffectBehaviorContext context) { } /// /// Invoked after a periodic tick has been processed for the owning effect. /// /// Runtime context for the effect instance. /// Information about the specific periodic tick. public virtual void OnPeriodicTick( EffectBehaviorContext context, PeriodicEffectTickContext tickContext ) { } /// /// Invoked when the effect handle is removed or expires. /// /// Runtime context for the effect instance. public virtual void OnRemove(EffectBehaviorContext context) { } } /// /// Immutable runtime data passed to behaviour callbacks. /// public readonly struct EffectBehaviorContext { /// /// Gets the effect asset backing the handle. /// public AttributeEffect Effect => handle.effect; /// /// Gets the GameObject targeted by the effect handler. /// public GameObject Target => handler.gameObject; /// /// Gets the deltaTime used for the current invocation. For , /// this value is zero. /// public readonly float deltaTime; /// /// Gets the handler managing the effect. /// public readonly EffectHandler handler; /// /// Gets the handle associated with this behaviour invocation. /// public readonly EffectHandle handle; public EffectBehaviorContext(EffectHandler handler, EffectHandle handle, float deltaTime) { this.handler = handler; this.handle = handle; this.deltaTime = deltaTime; } } /// /// Details about a specific periodic tick that just executed. /// public readonly struct PeriodicEffectTickContext { /// /// Gets the periodic definition that produced this tick. /// public readonly PeriodicEffectDefinition definition; /// /// Gets the number of ticks executed so far, including this one. /// public readonly int executedTicks; /// /// Gets the timestamp when the tick occurred. /// public readonly float currentTime; public PeriodicEffectTickContext( PeriodicEffectDefinition definition, int executedTicks, float currentTime ) { this.definition = definition; this.executedTicks = executedTicks; this.currentTime = currentTime; } } }