// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Tags { using System.Collections.Generic; using Core.Extension; using UnityEngine; /// /// Abstract base class for cosmetic effect behaviors that provide visual or audio feedback for effects. /// CosmeticEffectComponents are attached to CosmeticEffectData and are invoked when effects are applied or removed. /// /// /// /// Cosmetic effects can be: /// - Shared across all effect applications (RequiresInstance = false) /// - Instanced per application (RequiresInstance = true) for independent control /// /// /// Example implementations: /// - Particle effects that play when an effect is active /// - Audio clips that trigger on effect application /// - Visual indicators like status icons or color tints /// - Animation triggers /// /// /// Example usage: /// /// public class PoisonVisuals : CosmeticEffectComponent /// { /// public override bool RequiresInstance => true; /// public ParticleSystem poisonParticles; /// /// public override void OnApplyEffect(GameObject target) /// { /// base.OnApplyEffect(target); /// poisonParticles.Play(); /// } /// /// public override void OnRemoveEffect(GameObject target) /// { /// base.OnRemoveEffect(target); /// poisonParticles.Stop(); /// } /// } /// /// /// [RequireComponent(typeof(CosmeticEffectData))] public abstract class CosmeticEffectComponent : MonoBehaviour { /// /// If true, this cosmetic effect requires a new instance to be created for each effect application. /// If false, the same instance is shared across all applications. /// public virtual bool RequiresInstance => false; /// /// If true, this component handles its own cleanup (e.g., via delayed destruction or animations). /// If false, the GameObject will be destroyed immediately when the effect is removed. /// public virtual bool CleansUpSelf => false; /// /// Tracks all GameObjects this cosmetic effect has been applied to. /// protected readonly List _appliedTargets = new(); /// /// Cleanup method that removes the effect from all targets when this component is destroyed. /// protected virtual void OnDestroy() { if (_appliedTargets.Count <= 0) { return; } foreach (GameObject appliedTarget in _appliedTargets.ToArray()) { if (appliedTarget == null) { continue; } OnRemoveEffect(appliedTarget); } } /// /// Called when the associated effect is applied to a target GameObject. /// Override this to implement custom behavior (e.g., play particles, show UI). /// /// The GameObject the effect was applied to. public virtual void OnApplyEffect(GameObject target) { _appliedTargets.Add(target); } /// /// Called when the associated effect is removed from a target GameObject. /// Only invoked for non-instant effects. Override this to implement cleanup behavior. /// /// The GameObject the effect was removed from. public virtual void OnRemoveEffect(GameObject target) { int appliedIndex = _appliedTargets.IndexOf(target); if (0 <= appliedIndex) { _appliedTargets.RemoveAtSwapBack(appliedIndex); } } } }