mirror of
https://github.com/TheRavenwoodArts/LunaLightXMG.git
synced 2026-07-30 22:55:03 -07:00
019b35dee4
* 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>
457 lines
17 KiB
C#
457 lines
17 KiB
C#
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
|
|
{
|
|
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
|
|
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
|
|
private float hspWalk; // Maximum horizontal speed
|
|
private float grv; // Gravity
|
|
private float vspMax; // Maximum vertical speed
|
|
private float grvWall; // Gravity when wall sliding
|
|
private float vspMaxWall; // Maximum vertical speed when wall sliding
|
|
private float vspJump; // Jump speed
|
|
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 ;)
|
|
|
|
|
|
|
|
public Player(Vector2 initPosition)
|
|
{
|
|
hsp = 0;
|
|
vsp = 0;
|
|
hsp_frac = 0;
|
|
vsp_frac = 0;
|
|
wallJumpDelay = 0;
|
|
grounded = false;
|
|
walled = false;
|
|
wasWalled = false;
|
|
spriteFacingLeft = false;
|
|
stopped = true;
|
|
hspAcc = 0.2f;
|
|
hspFrictionGround = 0.3f;
|
|
hspFrictionAir = 0.3f;
|
|
hspWalk = 2.5f;
|
|
grv = 0.2f;
|
|
vspMax = 10f;
|
|
grvWall = 0.1f;
|
|
vspMaxWall = 5f;
|
|
vspJump = -4f;
|
|
position = initPosition;
|
|
boundingBox = new BoundingBox(new Rectangle((int)position.X, (int)position.Y, 8, 16));
|
|
collisionManager = new CollisionManager();
|
|
}
|
|
|
|
public void LoadContent(ContentManager content)
|
|
{
|
|
// 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) + 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);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (input.KeyJump && grounded)
|
|
{
|
|
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;
|
|
hsp += moveH * hspAcc;
|
|
|
|
if (moveH == 0)
|
|
{
|
|
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;
|
|
vspMaxFinal = vspMaxWall;
|
|
}
|
|
vsp += vsp == 0 ? grvFinal * 2 : grvFinal;
|
|
vsp = MathHelper.Clamp(vsp, vspJump, vspMaxFinal);
|
|
}
|
|
|
|
private void CollisionChecks(Platform[] platforms)
|
|
{
|
|
// Horizontal collision prediction
|
|
float onePixh = Math.Sign(hsp);
|
|
if (CollisionManager.IsColliding(position + new Vector2(hsp, 0), boundingBox, platforms))
|
|
{
|
|
while (!CollisionManager.IsColliding(position + new Vector2(onePixh, 0), boundingBox, platforms))
|
|
{
|
|
position.X += onePixh;
|
|
}
|
|
hsp = 0;
|
|
}
|
|
position.X += hsp; // Horizontal move
|
|
|
|
// Vertical collision prediction
|
|
float onePixv = Math.Sign(vsp);
|
|
if (CollisionManager.IsColliding(position + new Vector2(0, vsp), boundingBox, platforms))
|
|
{
|
|
while (!CollisionManager.IsColliding(position + new Vector2(0, onePixv), boundingBox, platforms))
|
|
{
|
|
position.Y += onePixv;
|
|
}
|
|
vsp = 0;
|
|
}
|
|
position.Y += vsp; // Vertical move
|
|
|
|
// Grounded?
|
|
grounded = CollisionManager.IsColliding(position + new Vector2(0, 1), boundingBox, platforms);
|
|
|
|
// Walled?
|
|
walled = CollisionManager.WallCheck(position, boundingBox, platforms);
|
|
if (walled)
|
|
wasWalled = walled;
|
|
|
|
collisionSide = CollisionManager.GetCollisionSide(position, boundingBox, platforms);
|
|
}
|
|
|
|
|
|
// TODO: Move below to utilities class
|
|
private static float Approach(float start, float end, float shift)
|
|
{
|
|
if (start < end)
|
|
{
|
|
start += shift;
|
|
if (start > end)
|
|
return end;
|
|
}
|
|
else
|
|
{
|
|
start -= shift;
|
|
if (start < end)
|
|
return end;
|
|
}
|
|
return start;
|
|
}
|
|
|
|
private void DumpFractions()
|
|
{
|
|
if (grounded)
|
|
{
|
|
if (collisionSide == CollisionManager.CollisionSide.Right)
|
|
{
|
|
position.X = (float)Math.Floor(position.X);
|
|
}
|
|
else if (collisionSide == CollisionManager.CollisionSide.Left)
|
|
{
|
|
position.X = (float)Math.Ceiling(position.X);
|
|
}
|
|
else
|
|
{
|
|
position.X = (float)Math.Round(position.X);
|
|
}
|
|
position.Y = (float)Math.Round(position.Y);
|
|
}
|
|
|
|
if (vsp >= 0)
|
|
{
|
|
hsp += hsp_frac;
|
|
vsp += vsp_frac;
|
|
hsp_frac = hsp - (float)Math.Floor(hsp);
|
|
vsp_frac = vsp - (float)Math.Floor(vsp);
|
|
hsp -= hsp_frac;
|
|
vsp -= vsp_frac;
|
|
}
|
|
}
|
|
}
|
|
}
|