#include "player.hpp" #include "level.hpp" #include "enemy.hpp" #include "extensions.hpp" namespace PlatformerStarterKit { Player::Player(xna::sptr const& level, xna::Vector2 const& position) : level(level) { LoadContent(); Reset(position); } void Player::LoadContent() { idleAnimation = xna::snew(level->Content()->Load("Sprites/Player/Idle"), 0.1f, true); runAnimation = xna::snew(level->Content()->Load("Sprites/Player/Run"), 0.1f, true); jumpAnimation = xna::snew(level->Content()->Load("Sprites/Player/Jump"), 0.1f, false); celebrateAnimation = xna::snew(level->Content()->Load("Sprites/Player/Celebrate"), 0.1f, false); dieAnimation = xna::snew(level->Content()->Load("Sprites/Player/Die"), 0.1f, false); const auto width = static_cast(idleAnimation->FrameWidth() * 0.4); const auto left = (idleAnimation->FrameWidth() - width) / 2; const auto height = static_cast(idleAnimation->FrameWidth() * 0.8); const auto top = idleAnimation->FrameHeight() - height; localBounds = xna::Rectangle(left, top, width, height); killedSound = level->Content()->Load("Sounds/PlayerKilled"); jumpSound = level->Content()->Load("Sounds/PlayerJump"); fallSound = level->Content()->Load("Sounds/PlayerFall"); } void Player::Reset(xna::Vector2 const& position) { Position = position; Velocity = xna::Vector2::Zero(); isAlive = true; sprite.PlayAnimation(idleAnimation); } void Player::ApplyPhysics(xna::GameTime const& gameTime) { float elapsed = static_cast(gameTime.ElapsedGameTime.TotalSeconds()); auto previousPosition = Position; Velocity.X += movement * MoveAcceleration * elapsed; Velocity.Y = xna::MathHelper::Clamp(Velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed); Velocity.Y = DoJump(Velocity.Y, gameTime); if (IsOnGround()) Velocity.X *= GroundDragFactor; else Velocity.X *= AirDragFactor; Velocity.X = xna::MathHelper::Clamp(Velocity.X, -MaxMoveSpeed, MaxMoveSpeed); Position = Position + (Velocity * elapsed); Position = xna::Vector2(std::round(Position.X), std::round(Position.Y)); HandleCollisions(); if (Position.X == previousPosition.X) Velocity.X = 0; if (Position.Y == previousPosition.Y) Velocity.Y = 0; } void Player::Update(xna::GameTime const& gameTime) { GetInput(); ApplyPhysics(gameTime); if (IsAlive() && IsOnGround()) { if (std::abs(Velocity.X) - 0.02f > 0) { sprite.PlayAnimation(runAnimation); } else { sprite.PlayAnimation(idleAnimation); } } movement = 0.0f; isJumping = false; } void Player::GetInput() { auto gamePadState = xna::GamePad::GetState(xna::PlayerIndex::One); auto keyboardState = xna::Keyboard::GetState(); movement = gamePadState.ThumbSticks.Left().X * MoveStickScale; if (std::abs(movement) < 0.5f) movement = 0.0f; if (gamePadState.IsButtonDown(xna::Buttons::DPadLeft) || keyboardState.IsKeyDown(xna::Keys::Left) || keyboardState.IsKeyDown(xna::Keys::A)) { movement = -1.0f; } else if (gamePadState.IsButtonDown(xna::Buttons::DPadRight) || keyboardState.IsKeyDown(xna::Keys::Right) || keyboardState.IsKeyDown(xna::Keys::D)) { movement = 1.0f; } isJumping = gamePadState.IsButtonDown(JumpButton) || keyboardState.IsKeyDown(xna::Keys::Space) || keyboardState.IsKeyDown(xna::Keys::Up) || keyboardState.IsKeyDown(xna::Keys::W); } void Player::OnKilled(xna::sptr& killedBy) { isAlive = false; if (!killedBy) killedSound->Play(); else fallSound->Play(); sprite.PlayAnimation(dieAnimation); } void Player::Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch) { if (Velocity.X > 0) flip = xna::SpriteEffects::FlipHorizontally; else if (Velocity.X < 0) flip = xna::SpriteEffects::None; sprite.Draw(gameTime, spriteBatch, Position, flip); } void Player::OnReachedExit() { sprite.PlayAnimation(celebrateAnimation); } float Player::DoJump(float velocityY, xna::GameTime const& gameTime) { if (isJumping) { if ((!wasJumping && IsOnGround()) || jumpTime > 0.0f) { if (jumpTime == 0.0f) jumpSound->Play(); jumpTime += static_cast(gameTime.ElapsedGameTime.TotalSeconds()); sprite.PlayAnimation(jumpAnimation); } if (0.0f < jumpTime && jumpTime <= MaxJumpTime) { velocityY = JumpLaunchVelocity * (1.0f - static_cast(std::pow(jumpTime / MaxJumpTime, JumpControlPower))); } else { jumpTime = 0.0f; } } else { jumpTime = 0.0f; } wasJumping = isJumping; return velocityY; } void Player::HandleCollisions() { auto bounds = BoundingRectangle(); auto leftTile = static_cast(std::floor(static_cast(bounds.Left()) / Tile::Width)); auto rightTile = static_cast(std::ceil((static_cast(bounds.Right()) / Tile::Width))) - 1; auto topTile = static_cast(std::floor(static_cast(bounds.Top()) / Tile::Height)); auto bottomTile = static_cast(std::ceil((static_cast(bounds.Bottom()) / Tile::Height))) - 1; isOnGround = false; for (int y = topTile; y <= bottomTile; ++y) { for (int x = leftTile; x <= rightTile; ++x) { auto collision = level->GetCollision(x, y); if (collision != TileCollision::Passable) { auto tileBounds = level->GetBounds(x, y); auto depth = RectangleExtensions::GetIntersectionDepth(bounds, tileBounds); if (depth != xna::Vector2::Zero()) { auto absDepthX = std::abs(depth.X); auto absDepthY = std::abs(depth.Y); if (absDepthY < absDepthX || collision == TileCollision::Platform) { const auto tileBoundsTop = tileBounds.Top(); if (previousBottom <= tileBoundsTop) isOnGround = true; if (collision == TileCollision::Impassable || IsOnGround()) { Position = xna::Vector2(Position.X, Position.Y + depth.Y); bounds = BoundingRectangle(); } } else if (collision == TileCollision::Impassable) { Position = xna::Vector2(Position.X + depth.X, Position.Y); bounds = BoundingRectangle(); } } } } } previousBottom = bounds.Bottom(); } xna::sptr Player::Level() const { return level; } xna::Rectangle Player::BoundingRectangle() const { auto left = std::round(Position.X - sprite.Origin().X) + localBounds.X; auto top = std::round(Position.Y - sprite.Origin().Y) + localBounds.Y; return xna::Rectangle( static_cast(left), static_cast(top), localBounds.Width, localBounds.Height); } }