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 { /// /// Returns the animation that is currently playing. /// public SpriteAnimation CurrentAnimation { get { this.SortAnimationsIfDirty(true); return this.currentAnimation?.Animation; } } /// /// Returns the frame that is displaying. /// public AnimationFrame CurrentFrame => this.CurrentAnimation?.CurrentFrame; /// /// Returns the 's . /// public TextureRegion CurrentRegion => this.CurrentAnimation?.CurrentRegion; /// /// Returns the 's . /// public IList CurrentRegions => this.CurrentAnimation?.CurrentRegions; /// public float SpeedMultiplier { set { foreach (var anim in this.animations) anim.Animation.SpeedMultiplier = value; } } /// /// 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 readonly List animations = new List(); private ConditionedAnimation currentAnimation; private bool animationsDirty; /// /// 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.animationsDirty = true; return this; } /// public void Update(GameTime time) { this.Update(time.ElapsedGameTime); } /// public void Update(TimeSpan elapsed) { this.FindAnimationToPlay(); if (this.CurrentAnimation != null) this.CurrentAnimation.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() { this.SortAnimationsIfDirty(false); ConditionedAnimation animToPlay = null; if (this.currentAnimation != null && this.currentAnimation.ShouldPlay()) animToPlay = this.currentAnimation; foreach (var anim in this.animations) { // if we find an animation with a lower priority then it means we can break since the list is sorted by priority if (animToPlay != null && anim.Priority <= animToPlay.Priority) break; if (anim.ShouldPlay()) animToPlay = anim; } if (animToPlay != this.currentAnimation) { this.OnAnimationChanged?.Invoke(this.currentAnimation?.Animation, animToPlay?.Animation); this.currentAnimation = animToPlay; if (animToPlay != null) animToPlay.Animation.Restart(); } } private void SortAnimationsIfDirty(bool findAnimationToPlay) { if (this.animationsDirty) { this.animationsDirty = false; this.animations.Sort((a1, a2) => a2.Priority.CompareTo(a1.Priority)); if (findAnimationToPlay) this.FindAnimationToPlay(); } } /// /// 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; } } }