using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; namespace V3.Objects.Sprite { public abstract class AbstractSpriteCreature : ISpriteCreature { // How many frames are there for each animation type? protected virtual int IdleFrames { get; } = 4; protected virtual int MovingFrames { get; } = 8; protected virtual int AttackingFrames { get; } = 4; protected virtual int DyingFrames { get; } = 6; protected virtual int SpecialFrames { get; } = 4; /// The row of the specific textures in the texture file. protected virtual int IdleTextureIndex { get; } = 0; protected virtual int MovingTextureIndex { get; } = 4; protected virtual int AttackingTextureIndex { get; } = 12; protected virtual int DyingTextureIndex { get; } = 18; protected virtual int SpecialTextureIndex { get; } = 24; // Specify the size of a single animation frame in pixel. private const int SpriteWidth = 128; private const int SpriteHeight = 128; private readonly Point mSpriteShift = new Point(-SpriteWidth / 2, -SpriteHeight * 3 / 4); private readonly Point mSpriteSize = new Point(SpriteWidth, SpriteHeight); private double mTimeSinceUpdate; private Texture2D mTexture; // How much time until animation frame is changed (in milliseconds). private const int UpdatesPerMSec = 125; // 8 FPS private MovementState mLastMovementState = MovementState.Idle; private MovementState mCurrentMovementState = MovementState.Idle; private int mMaxAnimationSteps; private int mMovementStateRow; private bool mIdleBackwardsLoop; private int mAnimationState; // Fields for PlayOnce method. private bool mPriorityAnimation; private UpdatesPerSecond mUpS; protected abstract string TextureFile { get; } /// /// Loads the texture file and prepares animations. /// /// public void Load(ContentManager contentManager) { mTexture = contentManager.Load("Sprites/" + TextureFile); mMaxAnimationSteps = IdleFrames; } /// /// Draws the sprite on the screen. /// /// Sprite batch used for drawing. /// Position on the screen where sprite is drawn to. /// What moveset will be used? (Moving, Attacking...) /// Where does the sprite face? public void Draw(SpriteBatch spriteBatch, Vector2 position, MovementState movementState, MovementDirection movementDirection) { mCurrentMovementState = movementState; Point sourceLocation = new Point((mMovementStateRow + mAnimationState) * SpriteWidth, (int) movementDirection * SpriteHeight); Rectangle sourceRectangle = new Rectangle(sourceLocation, mSpriteSize); spriteBatch.Draw(mTexture, position + new Vector2(mSpriteShift.X, mSpriteShift.Y), sourceRectangle, Color.White); } public void DrawStatic(SpriteBatch spriteBatch, Point position, MovementState movementState, MovementDirection movementDirection) { Point sourceLocation = new Point((int)movementState * SpriteWidth, (int)movementDirection * SpriteHeight); Rectangle destinationRectangle = new Rectangle(position + mSpriteShift, mSpriteSize); Rectangle sourceRectangle = new Rectangle(sourceLocation, mSpriteSize); spriteBatch.Draw(mTexture, destinationRectangle, sourceRectangle, Color.White); } /// /// Change the sprite to show an animation. /// /// Elapsed game time is used for calculating FPS. public void PlayAnimation(GameTime gameTime) { // Playing a single animation cycle just once with higher priority. if (mPriorityAnimation) { if (mAnimationState < mMaxAnimationSteps - 1) { if (mUpS.IsItTime(gameTime)) { mAnimationState++; } return; } mAnimationState = 0; mCurrentMovementState = mLastMovementState; SelectFrames(mCurrentMovementState); mPriorityAnimation = false; return; } // Change texture for showing animations according to elapsed game time. mTimeSinceUpdate += gameTime.ElapsedGameTime.TotalMilliseconds; if (mTimeSinceUpdate < UpdatesPerMSec) { return; } mTimeSinceUpdate = 0; // Check which specific animation sprites need to be used. // Only check if movement state is changed. if (mCurrentMovementState != mLastMovementState) { SelectFrames(mCurrentMovementState); mAnimationState = 0; mLastMovementState = mCurrentMovementState; } // Idle animation is looped back and forth. if (mIdleBackwardsLoop) { mAnimationState--; if (mAnimationState <= 0) { mIdleBackwardsLoop = false; } return; } if (mAnimationState < mMaxAnimationSteps - 1) { mAnimationState++; } else { if (mLastMovementState == MovementState.Idle) { mIdleBackwardsLoop = true; mAnimationState--; } else if (mLastMovementState == MovementState.Dying) { } else { mAnimationState = 0; } } } private void SelectFrames(MovementState currentMovementState) { switch (currentMovementState) { case MovementState.Idle: mMaxAnimationSteps = IdleFrames; mMovementStateRow = IdleTextureIndex; mIdleBackwardsLoop = false; break; case MovementState.Moving: mMaxAnimationSteps = MovingFrames; mMovementStateRow = MovingTextureIndex; break; case MovementState.Attacking: mMaxAnimationSteps = AttackingFrames; mMovementStateRow = AttackingTextureIndex; break; case MovementState.Dying: mMaxAnimationSteps = DyingFrames; mMovementStateRow = DyingTextureIndex; break; case MovementState.Special: mMaxAnimationSteps = SpecialFrames; mMovementStateRow = SpecialTextureIndex; break; default: mMaxAnimationSteps = 1; // No Animation if default case is reached. break; } } /// /// Plays the specified animation fully, but only once. /// /// For which movement state the animation should be played. /// How long (or how slow) should the animation be? public void PlayOnce(MovementState animation, TimeSpan duration) { mLastMovementState = mCurrentMovementState; mCurrentMovementState = animation; mPriorityAnimation = true; SelectFrames(animation); mAnimationState = 0; mUpS = new UpdatesPerSecond(1d / (duration.TotalSeconds / mMaxAnimationSteps)); } } }