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("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; } } } }