## March 31st, 2025 - JME **Animated Sprites** We are going to use the MonoGame.Extended framework for sprite animation. I have set up the project to use the extended frmework and implemented a simple test in the player class. I learned that we should be creating sprite sheets for each character that include the animations for each state of the character, e.g. "run", "jump", "attack", etc. The sprite sheet can be thought of as a single dimension array represented as a matrix. Each index in the array represents a frame. Lets say our run animation is 6 frames, our jump animation is 4 frames and our attack animation is 10 frames and we draw each frame in the sprite sheet whereeach frame is 18px by 18px and our sprite sheet is 90px by 72px. Then our frames can be defined as follows [00][01][02][03][04] [05][06][07][08][09] [10][11][12][13][14] [15][16][17][18][19] Where "run" is index 00 to 05, "jump" is index 06 to 09 and "attack" is index 10 to 19. Then from our single sprite sheet we can define the different animations for each state. Texture2D texture2D = content.Load("Path/To/SpriteSheet"); Texture2DAtlas atlas = Texture2DAtlas.Create("Atlas/SpriteSheet", texture2D, 18, 18); SpriteSheet spriteSheet = new SpriteSheet("SpriteSheet/SpriteSheet", atlas); spriteSheet.DefineAnimation("run", builder => { builder.IsLooping(true) .AddFrame(regionIndex: 0, duration: TimeSpan.FromSeconds(0.05)) .AddFrame(1, TimeSpan.FromSeconds(0.05)) .AddFrame(2, TimeSpan.FromSeconds(0.05)) .AddFrame(3, TimeSpan.FromSeconds(0.05)) .AddFrame(4, TimeSpan.FromSeconds(0.05)) .AddFrame(5, TimeSpan.FromSeconds(0.05)); }); spriteSheet.DefineAnimation("jump", builder => { builder.IsLooping(true) .AddFrame(regionIndex: 0, duration: TimeSpan.FromSeconds(0.05)) .AddFrame(6, TimeSpan.FromSeconds(0.05)) .AddFrame(7, TimeSpan.FromSeconds(0.05)) .AddFrame(8, TimeSpan.FromSeconds(0.05)) .AddFrame(9, TimeSpan.FromSeconds(0.05)); }); spriteSheet.DefineAnimation("attack", builder => { builder.IsLooping(true) .AddFrame(regionIndex: 0, duration: TimeSpan.FromSeconds(0.05)) .AddFrame(10, TimeSpan.FromSeconds(0.05)) .AddFrame(11, TimeSpan.FromSeconds(0.05)) .AddFrame(12, TimeSpan.FromSeconds(0.05)) .AddFrame(13, TimeSpan.FromSeconds(0.05)) .AddFrame(14, TimeSpan.FromSeconds(0.05)) .AddFrame(15, TimeSpan.FromSeconds(0.05)) .AddFrame(16, TimeSpan.FromSeconds(0.05)) .AddFrame(17, TimeSpan.FromSeconds(0.05)) .AddFrame(18, TimeSpan.FromSeconds(0.05)) .AddFrame(19, TimeSpan.FromSeconds(0.05)); }); ## April 1st, 2025 - JME **More sprite animation** Added RoSpriteSheet containing animations for run. jump, climbLedge, climb, wallStatic, edge and idle states. Added a drawOffset to align the 18px by 18px player sprites with the 8px by 16px bounding box. public void Draw(SpriteBatch spriteBatch) { // Adjust sprite draw position to align with bounding box Vector2 drawOffset = new Vector2(-4, -1); // Snap position to integer values Vector2 drawPosition = new Vector2((int)position.X, (int)position.Y) + drawOffset; //spriteBatch.Draw(texture, drawPosition, Color.White); spriteBatch.Draw(animatedSprite, drawPosition); } ## April 2nd, 2025 - JME **Handling player states and animations** Created initial sprite animation handling. We have an UpdateAnimation method that detects player states and updates the animation based on the state. private void UpdateAnimation() { if (grounded) { if (Math.Abs(hsp) > 0.1f) { HandleSetAnimation(PlayerState.Run); } else { HandleSetAnimation(PlayerState.Idle); } } else if (vsp < 0) { HandleSetAnimation(PlayerState.Jump); } } We still need logic for stop, ready, wallSlide, climbing, climgLedge, edge, attacks We will need a timer to track idle time, as well as some other things probably. The HandleSetAnimation method switches the animation based on state as well as other variables that might need to be set during an animation switch. private void HandleSetAnimation(PlayerState newState) { if (currentState == newState) return; currentState = newState; switch (currentState) { case PlayerState.Idle: animatedSprite.SetAnimation("idle"); break; case PlayerState.Run: animatedSprite.SetAnimation("run"); break; case PlayerState.Jump: animatedSprite.SetAnimation("jump"); break; case PlayerState.Stop: animatedSprite.SetAnimation("stop"); break; case PlayerState.Ready: animatedSprite.SetAnimation("ready"); break; case PlayerState.WallSlide: animatedSprite.SetAnimation("wallSlide"); break; case PlayerState.Climb: animatedSprite.SetAnimation("climb"); break; case PlayerState.ClimbLedge: animatedSprite.SetAnimation("climbLedge"); break; } } ## April 3rd, 2025 - JME Figured out how to flip our player sprites when moving left by creating a boolean **spriteFacingLeft** that ses to true when player.hsp is less than zero and false when player.hp is greater than zero. Then we can create a sprite efect using SpriteEffects that changes fom FlipHorizontally to None depending on spriteFacingLeft. The correct overload for a static sprite with a sprite effect was tricky to figure out, the best I could find is the following, spriteBatch.Draw(wallSlide.TextureRegion.Texture, drawPosition, wallSlide.TextureRegion.Bounds, Color.White, 0f, Vector2.Zero, 1f, flipEffect, 0f); So for further static sprite effects we'll probably want to use this overload. Here is the full updated draw method for the player class public void Draw(SpriteBatch spriteBatch) { // Adjust sprite draw position to align with bounding box Vector2 drawOffset = new Vector2(-5, -1); // Snap position to integer values Vector2 drawPosition = new Vector2((int)position.X, (int)position.Y) + drawOffset; // Flip sprites when moving left. SpriteEffects flipEffect = spriteFacingLeft ? SpriteEffects.FlipHorizontally : SpriteEffects.None; // Draw sprite if (currentState == PlayerState.WallSlide) { spriteBatch.Draw(wallSlide.TextureRegion.Texture, drawPosition, wallSlide.TextureRegion.Bounds, Color.White, 0f, Vector2.Zero, 1f, flipEffect, 0f); } else { animatedSprite.Effect = flipEffect; spriteBatch.Draw(animatedSprite, drawPosition); } }