using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using MLEM.Misc; using MLEM.Textures; namespace MLEM.Animations { /// /// Represents a list of objects with a condition and priority attached to them. /// Sprite animation groups can be used if any single entity should have multiple animations (like up, down, left, right standing and running animations) that should be automatically managed. /// public class SpriteAnimationGroup : GenericDataHolder { private readonly List animations = new List(); private ConditionedAnimation currAnimation; private ConditionedAnimation CurrAnimation { get { if (this.isDirty) { this.isDirty = false; this.animations.Sort((a1, a2) => a2.Priority.CompareTo(a1.Priority)); this.FindAnimationToPlay(); } return this.currAnimation; } set => this.currAnimation = value; } /// /// The animation that is currently playing /// public SpriteAnimation CurrentAnimation => this.CurrAnimation?.Animation; /// /// The frame that is displaying /// public AnimationFrame CurrentFrame => this.CurrentAnimation?.CurrentFrame; /// /// The region that has /// public TextureRegion CurrentRegion => this.CurrentAnimation?.CurrentRegion; /// /// A callback for when the currently displaying animation has changed due to a condition with a higher priority being met. /// public event AnimationChanged OnAnimationChanged; private bool isDirty; /// public float SpeedMultiplier { set { foreach (var anim in this.animations) anim.Animation.SpeedMultiplier = value; } } /// /// Adds a to this group. /// /// The animation to add /// The condition that needs to be met for this animation to play /// The priority of this animation. The higher the priority, the earlier it is picked for playing. /// This group, for chaining public SpriteAnimationGroup Add(SpriteAnimation anim, Func condition, int priority = 0) { this.animations.Add(new ConditionedAnimation(anim, condition, priority)); this.isDirty = true; return this; } /// public void Update(GameTime time) { this.Update(time.ElapsedGameTime); } /// public void Update(TimeSpan elapsed) { this.FindAnimationToPlay(); if (this.CurrAnimation != null) this.CurrAnimation.Animation.Update(elapsed); } /// /// Find an animation in this group by name and returns it. /// /// The of the animation /// The animation by that name, or null if there is none public SpriteAnimation ByName(string name) { return this.animations.Find(anim => anim.Animation.Name == name)?.Animation; } private void FindAnimationToPlay() { ConditionedAnimation animToPlay = null; if (this.CurrAnimation != null && this.CurrAnimation.ShouldPlay()) animToPlay = this.CurrAnimation; foreach (var anim in this.animations) { // if we find an animation with a lower priority then it means we can break // because the list is sorted by priority if (animToPlay != null && anim.Priority < animToPlay.Priority) break; if (anim.ShouldPlay()) animToPlay = anim; } if (animToPlay != this.CurrAnimation) { this.OnAnimationChanged?.Invoke(this.CurrAnimation?.Animation, animToPlay?.Animation); this.CurrAnimation = animToPlay; if (animToPlay != null) animToPlay.Animation.Restart(); } } /// /// A callback delegate for when a 's current animation changed. /// /// The previous animation /// The new animation public delegate void AnimationChanged(SpriteAnimation oldAnim, SpriteAnimation newAnim); } internal class ConditionedAnimation { public readonly SpriteAnimation Animation; public readonly Func ShouldPlay; public readonly int Priority; public ConditionedAnimation(SpriteAnimation animation, Func shouldPlay, int priority) { this.Animation = animation; this.ShouldPlay = shouldPlay; this.Priority = priority; } } }