Código Caótico

Recreating Super Mario Bros: Player and Enemy

Recreating Super Mario Bros: Player and Enemy

Recreating Super Mario Bros from Scratch Using C++ and SFML: Player and Enemy

In this post, I will walk you through the process of recreating Super Mario Bros from scratch using C++ and SFML. We'll focus specifically on implementing the player (Mario) and enemy (Goomba and Koopa Troopa) behaviors. This is part of my broader journey into game development, and I'll include code snippets to help illustrate the concepts.

The Player Class

First, let's start by discussing the Player class, which handles all the logic related to Mario's movement, animations, and death sequence.

#include "Player.hpp"

void Player::CheckXMovement_() {
  if (!sf::Keyboard::isKeyPressed(sf::Keyboard::Left) && !sf::Keyboard::isKeyPressed(sf::Keyboard::Right) && sf::Keyboard::isKeyPressed(sf::Keyboard::X))
    body_.x_speed = 0;
  if (body_.is_accelerating && !sf::Keyboard::isKeyPressed(sf::Keyboard::X))
    body_.is_accelerating = false;
  if (!body_.is_accelerating)
    body_.x_speed = 0;
}

This function checks whether Mario is moving left or right and whether the X key (which typically represents running or dashing) is pressed.

Player's Death Animation

The PlayDeathAnimation_ function handles what happens when Mario dies. After Mario dies, his vertical speed is reversed, simulating a jump before falling down.

void Player::PlayDeathAnimation_(float delta_time) {
  if (!has_died_ && jump_dead_) {
    has_died_ = true;
    body_.x_speed = 0;
    body_.y_speed = -body_.jump_speed;
  }

  body_.y_speed += body_.gravity * delta_time;
  if (body_.y_speed >= body_.terminal_velocity) {
    body_.y_speed = body_.terminal_velocity;
  }
  player_sprite_.move(0.0f, body_.y_speed * delta_time);
}

Player Constructor

In the constructor, we load the textures and initialize the player states, including normal Mario, Super Mario, and Fire Mario.

Player::Player(const std::string& texture_file) : is_dead_(false), has_died_(false), grace_time_(0.0f), jump_dead_(true), status_(Status::NORMAL) {
  if (!player_texture_.loadFromFile(texture_file)) {
    // Handle error
  }

  // Load sprites for small Mario
  for (int i = 0; i < 6; i++) {
    sf::Sprite sprite;
    sprite.setTexture(player_texture_);
    sprite.setTextureRect(sf::IntRect(i * 12, 0 * 16, 12, 16));
    player_sprites_[Status::NORMAL].push_back(sprite);
  }

  // Load sprites for Super Mario
  for (int i = 0; i < 7; i++) {
    sf::Sprite sprite;
    sprite.setTexture(player_texture_);
    sprite.setTextureRect(sf::IntRect(i * 16, 1 * 32 - 16, 16, 32));
    player_sprites_[Status::BIG].push_back(sprite);
  }

  // Load sprites for Fire Mario
  for (int i = 0; i < 7; i++) {
    sf::Sprite sprite;
    sprite.setTexture(player_texture_);
    sprite.setTextureRect(sf::IntRect(i * 16, 2 * 32 - 16, 16, 32));
    player_sprites_[Status::FIRE].push_back(sprite);
  }

  body_.gravity = 1500.0f;
  body_.terminal_velocity = 2240.0f;
  player_sprite_ = player_sprites_[Status::NORMAL][0];
  player_sprite_.setPosition(10, 50);
}

The Player class handles Mario’s different forms (normal, big, and fire), and the game will switch between these forms based on power-ups and player actions.


The Enemy Class

Now, let's move on to the Enemy class, which handles the logic for enemies like Goombas and Koopa Troopas.

Goomba

Here’s the Goomba class, which defines how the enemy behaves when it collides with Mario or the environment:

#include "Enemy.hpp"
#include 

Goomba::Goomba(const std::string& texture_file) : Enemy(texture_file) {}

void Goomba::OnJumpedOn() {
  is_dead_ = true;

  sf::FloatRect bounds = sprite_.getGlobalBounds();
  sprite_.setOrigin(bounds.width / 2.0f, bounds.height);
  sprite_.setScale(1.0f, 0.2f);

  if (!death_timer_started_) {
    death_timer_.restart();
    death_timer_started_ = true;
  }
}

In this method, the Goomba gets flattened when Mario jumps on it, triggering a death animation.

Koopa Troopa

Here’s the Koopa Troopa class, which behaves differently from Goombas. Koopa Troopas retreat into their shells when Mario jumps on them, and the player can kick the shell to defeat other enemies.

KoopaTroopa::KoopaTroopa(const std::string& texture_file)
: Enemy(texture_file) {}

void KoopaTroopa::OnJumpedOn() {
  if (!in_shell_) {
    // Go into shell if not already
    in_shell_ = true;
  } else if (in_shell_ && !is_shell_moving_) {
    // Start moving the shell
    is_shell_moving_ = true;
    body_.x_speed = 200.0f; // Shell speed
  } else {
    // Stop the shell if it is moving
    is_shell_moving_ = false;
    body_.x_speed = 0;
  }
}

Koopa Troopas enter a defensive shell state when jumped on and can be used strategically to attack other enemies.


Conclusion

Recreating Super Mario Bros using C++ and SFML is a rewarding project that teaches valuable skills in game development, including sprite management, animation, and collision detection. This post only covers the player and enemy mechanics, but you can build upon this foundation to create a fully functioning game.

Happy coding, and stay tuned for more!


References:

0 Comments
Comments Reply