Initil Sprite Animation with the player class. (#4)

* Added RoSpriteSheet and defined animation states.

* Added methods o handle sprite animation states.

* Figured out how to flip sprites when moving left.

* Bug Fix: Ready state wasn't animating, now it is ;)

---------

Co-authored-by: jme9 <jme9@pdx.edu>
This commit is contained in:
J. M. Ellis
2025-05-21 16:21:37 -07:00
committed by GitHub
parent 6b03d0a3f4
commit 019b35dee4
8 changed files with 494 additions and 34 deletions
+266 -17
View File
@@ -1,20 +1,26 @@
using System;
using System.Reflection.Metadata;
using System.Transactions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended.Animations;
using MonoGame.Extended.Graphics;
namespace LunaLightXMG
{
public class Player
{
private float hsp; // Horizontal speed
public float hsp; // Horizontal speed
public float vsp; // Vertical speed
private float hsp_frac; // Hold fractional position data
private float vsp_frac; // Hold fractional position data
private float wallJumpDelay; // Delay for wall jumping
public bool grounded; // Is the player grounded?
public bool walled; // Is the player wall sliding?
private bool wasWalled; // Remember is the player was on a wall or ground.
public bool walled; // Is the player wall sliding
public bool stopped; // animation control for when player has stopped moving.
private bool spriteFacingLeft;
private bool wasWalled; // Remember if the player was on a wall or ground.
private float hspAcc; // Horizontal acceleration
private float hspFrictionGround; // Ground friction
private float hspFrictionAir; // Air friction
@@ -24,11 +30,32 @@ namespace LunaLightXMG
private float grvWall; // Gravity when wall sliding
private float vspMaxWall; // Maximum vertical speed when wall sliding
private float vspJump; // Jump speed
private Texture2D texture;
public Vector2 position;
public BoundingBox boundingBox { get; private set; }
public CollisionManager.CollisionSide collisionSide { get; private set; }
private CollisionManager collisionManager;
// Sprites
private Sprite wallSlide;
private AnimatedSprite animatedSprite;
public enum PlayerState
{
Idle,
Run,
Jump,
WallSlide,
Climb,
ClimbLedge,
Edge,
Stop,
Ready,
Attack1,
Attack2,
Attack3
}
public PlayerState currentState; // make private before release ;)
@@ -42,10 +69,12 @@ namespace LunaLightXMG
grounded = false;
walled = false;
wasWalled = false;
spriteFacingLeft = false;
stopped = true;
hspAcc = 0.2f;
hspFrictionGround = 0.3f;
hspFrictionAir = 0.3f;
hspWalk = 3f;
hspWalk = 2.5f;
grv = 0.2f;
vspMax = 10f;
grvWall = 0.1f;
@@ -58,30 +87,240 @@ namespace LunaLightXMG
public void LoadContent(ContentManager content)
{
texture = content.Load<Texture2D>("player");
}
// Load Player SpriteSheet
Texture2D texture2D = content.Load<Texture2D>("Ro_Sprites/RoSpriteSheet");
Texture2DAtlas atlas = Texture2DAtlas.Create("Atlas/RoSpriteSheet", texture2D, 18, 18);
SpriteSheet spriteSheet = new SpriteSheet("SpriteSheet/RoSpriteSheet", atlas);
// Single Sprites
wallSlide = atlas.CreateSprite(regionIndex: 47);
// Animated Sprites
spriteSheet.DefineAnimation("climbLedge", 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))
.AddFrame(6, TimeSpan.FromSeconds(0.05))
.AddFrame(7, TimeSpan.FromSeconds(0.05));
});
spriteSheet.DefineAnimation("edge", builder =>
{
builder.IsLooping(true)
.AddFrame(8, TimeSpan.FromSeconds(0.05))
.AddFrame(9, 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));
});
spriteSheet.DefineAnimation("jump", builder =>
{
builder.IsLooping(false)
.AddFrame(16, TimeSpan.FromSeconds(0.05))
.AddFrame(17, TimeSpan.FromSeconds(0.05))
.AddFrame(18, TimeSpan.FromSeconds(0.05))
.AddFrame(19, TimeSpan.FromSeconds(0.05))
.AddFrame(20, TimeSpan.FromSeconds(0.05))
.AddFrame(21, TimeSpan.FromSeconds(0.05))
.AddFrame(22, TimeSpan.FromSeconds(0.05))
.AddFrame(23, TimeSpan.FromSeconds(0.05));
});
spriteSheet.DefineAnimation("run", builder =>
{
builder.IsLooping(true)
.AddFrame(24, TimeSpan.FromSeconds(0.05))
.AddFrame(25, TimeSpan.FromSeconds(0.05))
.AddFrame(26, TimeSpan.FromSeconds(0.05))
.AddFrame(27, TimeSpan.FromSeconds(0.05))
.AddFrame(28, TimeSpan.FromSeconds(0.05))
.AddFrame(29, TimeSpan.FromSeconds(0.05))
.AddFrame(30, TimeSpan.FromSeconds(0.05))
.AddFrame(31, TimeSpan.FromSeconds(0.05))
.AddFrame(32, TimeSpan.FromSeconds(0.05))
.AddFrame(33, TimeSpan.FromSeconds(0.05))
.AddFrame(34, TimeSpan.FromSeconds(0.05))
.AddFrame(35, TimeSpan.FromSeconds(0.05));
});
spriteSheet.DefineAnimation("stop", builder =>
{
builder.IsLooping(false)
.AddFrame(36, TimeSpan.FromSeconds(0.05))
.AddFrame(37, TimeSpan.FromSeconds(0.05))
.AddFrame(38, TimeSpan.FromSeconds(0.05))
.AddFrame(39, TimeSpan.FromSeconds(0.05));
});
spriteSheet.DefineAnimation("ready", builder =>
{
builder.IsLooping(true)
.AddFrame(40, TimeSpan.FromSeconds(0.2))
.AddFrame(41, TimeSpan.FromSeconds(0.2))
.AddFrame(42, TimeSpan.FromSeconds(0.2))
.AddFrame(43, TimeSpan.FromSeconds(0.2))
.AddFrame(44, TimeSpan.FromSeconds(0.2))
.AddFrame(45, TimeSpan.FromSeconds(0.2))
.AddFrame(46, TimeSpan.FromSeconds(0.2));
});
spriteSheet.DefineAnimation("climb", builder =>
{
builder.IsLooping(true)
.AddFrame(48, TimeSpan.FromSeconds(0.2))
.AddFrame(49, TimeSpan.FromSeconds(0.2))
.AddFrame(50, TimeSpan.FromSeconds(0.2))
.AddFrame(51, TimeSpan.FromSeconds(0.2))
.AddFrame(52, TimeSpan.FromSeconds(0.2))
.AddFrame(53, TimeSpan.FromSeconds(0.2))
.AddFrame(54, TimeSpan.FromSeconds(0.2))
.AddFrame(55, TimeSpan.FromSeconds(0.2))
.AddFrame(56, TimeSpan.FromSeconds(0.2))
.AddFrame(57, TimeSpan.FromSeconds(0.2));
});
spriteSheet.DefineAnimation("idle", builder =>
{
builder.IsLooping(true)
.AddFrame(58, TimeSpan.FromSeconds(0.2))
.AddFrame(59, TimeSpan.FromSeconds(0.2))
.AddFrame(60, TimeSpan.FromSeconds(0.2))
.AddFrame(61, TimeSpan.FromSeconds(0.2));
});
animatedSprite = new AnimatedSprite(spriteSheet, "idle");
}
public void Update(GameTime gameTime, PlayerInput input, Platform[] platforms)
{
animatedSprite.Update(gameTime);
Movement(input);
boundingBox.Update(position, boundingBox.Bounds.Width, boundingBox.Bounds.Height);
DumpFractions();
CollisionChecks(platforms);
UpdateAnimation();
}
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);
spriteBatch.Draw(texture, drawPosition, Color.White);
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);
}
}
// Method Implementation -------------------------------------------------------------------------------
public void Reset(Vector2 initialPosition)
{
position = initialPosition;
// Reset other player-specific states later
}
private void UpdateAnimation()
{
if (walled && !grounded)
{
currentState = PlayerState.WallSlide;
stopped = false;
}
else if (grounded)
{
if (hsp == 0.0f)
{
if (!stopped)
{
stopped = true;
HandleSetAnimation(PlayerState.Stop);
}
}
else
{
HandleSetAnimation(PlayerState.Run);
stopped = false;
}
}
else
{
HandleSetAnimation(PlayerState.Jump);
stopped = false;
}
// TODO: Need logic for climbing, climgLedge, edge, attacks
// TODO: Need timer to track idle time, then move to idle state after some amount of time
}
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").OnAnimationEvent += (sender, trigger) =>
{
if (trigger == AnimationEventTrigger.AnimationCompleted)
{
HandleSetAnimation(PlayerState.Ready);
}
};
break;
case PlayerState.Ready:
animatedSprite.SetAnimation("ready");
break;
case PlayerState.WallSlide:
// not sure yet
break;
case PlayerState.Climb:
animatedSprite.SetAnimation("climb");
break;
case PlayerState.ClimbLedge:
animatedSprite.SetAnimation("climbLedge");
break;
}
}
private void Movement(PlayerInput input)
{
@@ -89,10 +328,10 @@ namespace LunaLightXMG
{
vsp = vspJump;
}
// Horizontal movement with acceleration + wall jump delay
wallJumpDelay = Math.Max(wallJumpDelay - 1, 0); // counts down every frame
if (wallJumpDelay == 0) // no left-right control until delay is 0, prevents rapid wall climb
{
float moveH = input.KeyRight - input.KeyLeft;
@@ -103,14 +342,22 @@ namespace LunaLightXMG
float hspFrictionFinal = grounded ? hspFrictionGround : hspFrictionAir;
hsp = Approach(hsp, 0, hspFrictionFinal);
}
hsp = MathHelper.Clamp(hsp, -hspWalk, hspWalk);
if (hsp < 0)
{
spriteFacingLeft = true;
}
else if (hsp > 0)
{
spriteFacingLeft = false;
}
}
// Calculate vertical movement
float grvFinal = grv;
float vspMaxFinal = vspMax;
if (walled && vsp > 0)
{
grvFinal = grvWall;
@@ -156,8 +403,10 @@ namespace LunaLightXMG
collisionSide = CollisionManager.GetCollisionSide(position, boundingBox, platforms);
}
private float Approach(float start, float end, float shift)
// TODO: Move below to utilities class
private static float Approach(float start, float end, float shift)
{
if (start < end)
{