mirror of
https://github.com/borgesdan/xn65
synced 2024-12-29 21:54:47 +01:00
229 lines
8.4 KiB
C++
229 lines
8.4 KiB
C++
#include "player.hpp"
|
|
#include "level.hpp"
|
|
#include "enemy.hpp"
|
|
#include "extensions.hpp"
|
|
|
|
namespace PlatformerStarterKit {
|
|
Player::Player(xna::sptr<PlatformerStarterKit::Level> const& level, xna::Vector2 const& position)
|
|
: level(level)
|
|
{
|
|
LoadContent();
|
|
Reset(position);
|
|
}
|
|
|
|
void Player::LoadContent()
|
|
{
|
|
idleAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Idle"), 0.1f, true);
|
|
runAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Run"), 0.1f, true);
|
|
jumpAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Jump"), 0.1f, false);
|
|
celebrateAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Celebrate"), 0.1f, false);
|
|
dieAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Die"), 0.1f, false);
|
|
|
|
const auto width = static_cast<int>(idleAnimation->FrameWidth() * 0.4);
|
|
const auto left = (idleAnimation->FrameWidth() - width) / 2;
|
|
const auto height = static_cast<int>(idleAnimation->FrameWidth() * 0.8);
|
|
const auto top = idleAnimation->FrameHeight() - height;
|
|
localBounds = xna::Rectangle(left, top, width, height);
|
|
|
|
killedSound = level->Content()->Load<xna::PSoundEffect>("Sounds/PlayerKilled");
|
|
jumpSound = level->Content()->Load<xna::PSoundEffect>("Sounds/PlayerJump");
|
|
fallSound = level->Content()->Load<xna::PSoundEffect>("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<float>(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<Enemy>& 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<float>(gameTime.ElapsedGameTime.TotalSeconds());
|
|
sprite.PlayAnimation(jumpAnimation);
|
|
}
|
|
|
|
if (0.0f < jumpTime && jumpTime <= MaxJumpTime) {
|
|
velocityY = JumpLaunchVelocity * (1.0f - static_cast<float>(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<int>(std::floor(static_cast<float>(bounds.Left()) / Tile::Width));
|
|
auto rightTile = static_cast<int>(std::ceil((static_cast<float>(bounds.Right()) / Tile::Width))) - 1;
|
|
auto topTile = static_cast<int>(std::floor(static_cast<float>(bounds.Top()) / Tile::Height));
|
|
auto bottomTile = static_cast<int>(std::ceil((static_cast<float>(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<PlatformerStarterKit::Level> 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<int>(left),
|
|
static_cast<int>(top),
|
|
localBounds.Width,
|
|
localBounds.Height);
|
|
}
|
|
} |