Setting up the basic game objects

The majority of the work that went into creating Alien Attack was done in the object classes, while almost everything else was already being handled by manager classes in the framework. Here are the most important changes:

GameObject revamped

The GameObject base class has a lot more to it than it previously did.

class GameObject
{
public:
  // base class needs virtual destructor
  virtual ~GameObject() {}
  // load from file 
  virtual void load(std::unique_ptr<LoaderParams> const &pParams)=0;
  // draw the object
  virtual void draw()=0;
  // do update stuff
  virtual void update()=0;
  // remove anything that needs to be deleted
  virtual void clean()=0;
  // object has collided, handle accordingly
  virtual void collision() = 0;
  // get the type of the object
  virtual std::string type() = 0;
  // getters for common variables
  Vector2D& getPosition() { return m_position; }
  int getWidth() { return m_width; }
  int getHeight() { return m_height; }
  // scroll along with tile map
  void scroll(float scrollSpeed) { m_position.setX(m_position.getX() - 
  scrollSpeed); }
  // is the object currently being updated?
  bool updating() { return m_bUpdating; }
  // is the object dead?
  bool dead() { return m_bDead; }
  // is the object doing a death animation?
  bool dying() { return m_bDying; }
  // set whether to update the object or not
  void setUpdating(bool updating) { m_bUpdating = updating; }

protected:

  // constructor with default initialisation list
  GameObject() :  m_position(0,0),
  m_velocity(0,0),
  m_acceleration(0,0),
  m_width(0),
  m_height(0),
  m_currentRow(0),
  m_currentFrame(0),
  m_bUpdating(false),
  m_bDead(false),
  m_bDying(false),
  m_angle(0),
  m_alpha(255)
  {
  }
  // movement variables
  Vector2D m_position;
  Vector2D m_velocity;
  Vector2D m_acceleration;
  // size variables
  int m_width;
  int m_height;
  // animation variables
  int m_currentRow;
  int m_currentFrame;
  int m_numFrames;
  std::string m_textureID;
  // common boolean variables
  bool m_bUpdating;
  bool m_bDead;
  bool m_bDying;
  // rotation
  double m_angle;
  // blending
  int m_alpha;
};

This class now has a lot of the member variables that used to be in SDLGameObject. New variables for checking whether an object is updating, doing the death animation, or is dead, have been added. Updating is set to true when an object is within the game screen after scrolling with the game level.

In place of a regular pointer to LoaderParams in the load function, an std::unique_ptr pointer is now used; this is part of the new C++11 standard and ensures that the pointer is deleted after going out of scope.

virtual void load(std::unique_ptr<LoaderParams> const &pParams)=0;

There are two new functions that each derived object must now implement (whether it's owned or inherited):

 // object has collided, handle accordingly
virtual void collision() = 0;

 // get the type of the object
virtual std::string type() = 0;

SDLGameObject is now ShooterObject

The SDLGameObject class has now been renamed to ShooterObject and is a lot more specific to this type of game:

class ShooterObject : public GameObject
{
public:

  virtual ~ShooterObject() {}// for polymorphism
  virtual void load(std::unique_ptr<LoaderParams> const
  &pParams);
  virtual void draw();
  virtual void update();
  virtual void clean() {}// not implemented in this class
  virtual void collision() {}//not implemented in this class
  virtual std::string type() { return "SDLGameObject"; }

protected:

  // we won't directly create ShooterObject's
  ShooterObject();

  // draw the animation for the object being destroyed
  void doDyingAnimation();

  // how fast will this object fire bullets? with a counter
  int m_bulletFiringSpeed;
  int m_bulletCounter;

  // how fast will this object move?
  int m_moveSpeed;

  // how long will the death animation takes? with a counter
  int m_dyingTime;
  int m_dyingCounter;

  // has the explosion sound played?
  bool m_bPlayedDeathSound;
};

This class has default implementations for draw and update that can be used in derived classes; they are essentially the same as the previous SDLGameObject class, so we will not cover them here. A new function that has been added is doDyingAnimation. This function is responsible for updating the animation when enemies explode and then setting them to dead so that they can be removed from the game.

void ShooterObject::doDyingAnimation()
{
  // keep scrolling with the map
  scroll(TheGame::Instance()->getScrollSpeed());

  m_currentFrame = int(((SDL_GetTicks() / (1000 / 3)) % 
  m_numFrames));

  if(m_dyingCounter == m_dyingTime)
  {
    m_bDead = true;
  }
  m_dyingCounter++; //simple counter, fine with fixed frame rate
}

Player inherits from ShooterObject

The Player object now inherits from the new ShooterObject class and implements its own update function. Some new game-specific functions and variables have been added:

private:

  // bring the player back if there are lives left
  void ressurect();

  // handle any input from the keyboard, mouse, or joystick
  void handleInput();

  // handle any animation for the player
  void handleAnimation();

  // player can be invulnerable for a time
  int m_invulnerable;
  int m_invulnerableTime;
  int m_invulnerableCounter;
};

The ressurect function resets the player back to the center of the screen and temporarily makes the Player object invulnerable; this is visualized using alpha of the texture. This function is also responsible for resetting the size value of the texture which is changed in doDyingAnimation to accommodate for the explosion texture:

void Player::ressurect()
{
  TheGame::Instance()->setPlayerLives(TheGame::Instance()
  ->getPlayerLives() - 1);

  m_position.setX(10);
  m_position.setY(200);
  m_bDying = false;

  m_textureID = "player";

  m_currentFrame = 0;
  m_numFrames = 5;
  m_width = 101;
  m_height = 46;

  m_dyingCounter = 0;
  m_invulnerable = true;
}

Animation is a big part of the feel of the Player object; from flashing (when invulnerable), to rotating (when moving in a forward or backward direction). This has led to there being a separate function dedicated to handling animation:

void Player::handleAnimation()
{
  // if the player is invulnerable we can flash its alpha to let 
  people know
  if(m_invulnerable)
  {
    // invulnerability is finished, set values back
    if(m_invulnerableCounter == m_invulnerableTime)
    {
      m_invulnerable = false;
      m_invulnerableCounter = 0;
      m_alpha = 255;
    }
    else// otherwise, flash the alpha on and off
    {
      if(m_alpha == 255)
      {
        m_alpha = 0;
      }
      else
      {
        m_alpha = 255;
      }
    }

    // increment our counter
    m_invulnerableCounter++;
  }

  // if the player is not dead then we can change the angle with 
  the velocity to give the impression of a moving helicopter
  if(!m_bDead)
  {
    if(m_velocity.getX() < 0)
    {
      m_angle = -10.0;
    }
    else if(m_velocity.getX() > 0)
    {
      m_angle = 10.0;
    }
    else
    {
      m_angle = 0.0;
    }
  }

  // our standard animation code - for helicopter propellors
  m_currentFrame = int(((SDL_GetTicks() / (100)) % m_numFrames));
}

The angle and alpha of an object are changed using new parameters to the drawFrame function of TextureManager:

void TextureManager::drawFrame(std::string id, int x, int y, int 
width, int height, int currentRow, int currentFrame, SDL_Renderer 
*pRenderer, double angle, int alpha, SDL_RendererFlip flip)
{
  SDL_Rect srcRect;
  SDL_Rect destRect;
  srcRect.x = width * currentFrame;
  srcRect.y = height * currentRow;
  srcRect.w = destRect.w = width;
  srcRect.h = destRect.h = height;
  destRect.x = x;
  destRect.y = y;

  // set the alpha of the texture and pass in the angle
  SDL_SetTextureAlphaMod(m_textureMap[id], alpha);
  SDL_RenderCopyEx(pRenderer, m_textureMap[id], &srcRect, 
  &destRect, angle, 0, flip);
}

Finally the Player::update function ties this all together while also having extra logic to handle when a level is complete:

void Player::update()
{
  // if the level is complete then fly off the screen
  if(TheGame::Instance()->getLevelComplete())
  {
    if(m_position.getX() >= TheGame::Instance()->getGameWidth())
    {
      TheGame::Instance()->setCurrentLevel(TheGame::Instance()
      ->getCurrentLevel() + 1);
    }
    else
    {
      m_velocity.setY(0);
      m_velocity.setX(3);
      ShooterObject::update();
      handleAnimation();
    }
  }
  else
  {
    // if the player is not doing its death animation then update 
    it normally
    if(!m_bDying)
    {
      // reset velocity
      m_velocity.setX(0);
      m_velocity.setY(0);

      // get input
      handleInput();
      // do normal position += velocity update
      ShooterObject::update();

      // update the animation
      handleAnimation();
    }
    else // if the player is doing the death animation
    {
      m_currentFrame = int(((SDL_GetTicks() / (100)) % 
      m_numFrames));

      // if the death animation has completed
      if(m_dyingCounter == m_dyingTime)
      {
        // ressurect the player
        ressurect();
      }

      m_dyingCounter++;
    }
  }
}

Once a level is complete and the player has flown offscreen, the Player::update function also tells the game to increment the current level:

TheGame::Instance()->setCurrentLevel(TheGame::Instance()->getCurrentLevel() + 1);

The Game::setCurrentLevel function changes the state to BetweenLevelState:

void Game::setCurrentLevel(int currentLevel)
{
  m_currentLevel = currentLevel;
  m_pGameStateMachine->changeState(new BetweenLevelState());
  m_bLevelComplete = false;
}

Lots of enemy types

A game such as Alien Attack needs a lot of enemy types to keep things interesting; each with its own behavior. Enemies should be easy to create and automatically added to the collision detection list. With this in mind, the Enemy class has now become a base class:

// Enemy base class
class Enemy : public ShooterObject
{
public:
  virtual std::string type() { return"Enemy"; }

protected:
  int m_health;

  Enemy() : ShooterObject() {}
  virtual ~Enemy() {} // for polymorphism

};

All enemy types will derive from this class, but it is important that they do not override the type method. The reason for this will become clear once we move onto our games collision detection classes. Go ahead and take a look at the enemy types in the Alien Attack source code to see how simple they are to create.

Lots of enemy types

Adding a scrolling background

Scrolling backgrounds are important to 2D games like this; they help give an illusion of depth and movement. This ScrollingBackground class uses two destination rectangles and two source rectangles; one expands while the other contracts. Once the expanding rectangle has reached its full width, both rectangles are reset and the loop continues:

void ScrollingBackground::load(std::unique_ptr<LoaderParams> const &pParams)
{
  ShooterObject::load(std::move(pParams));
  m_scrollSpeed = pParams->getAnimSpeed();

  m_scrollSpeed = 1;

  m_srcRect1.x = 0;
  m_destRect1.x = m_position.getX();
  m_srcRect1.y = 0;
  m_destRect1.y = m_position.getY();

  m_srcRect1.w = m_destRect1.w = m_srcRect2Width = 
  m_destRect1Width = m_width;
  m_srcRect1.h = m_destRect1.h = m_height;

  m_srcRect2.x = 0;
  m_destRect2.x = m_position.getX() + m_width;
  m_srcRect2.y = 0;
  m_destRect2.y = m_position.getY();

  m_srcRect2.w = m_destRect2.w = m_srcRect2Width = 
  m_destRect2Width = 0;
  m_srcRect2.h = m_destRect2.h = m_height;
}

void ScrollingBackground::draw()
{
  // draw first rect
  SDL_RenderCopyEx(TheGame::Instance()->getRenderer(), 
  TheTextureManager::Instance()->getTextureMap()[m_textureID], 
  &m_srcRect1, &m_destRect1, 0, 0, SDL_FLIP_NONE);

  // draw second rect
  SDL_RenderCopyEx(TheGame::Instance()->getRenderer(), 
  TheTextureManager::Instance()->getTextureMap()[m_textureID], 
  &m_srcRect2, &m_destRect2, 0, 0, SDL_FLIP_NONE);

}

void ScrollingBackground::update()
{
  if(count == maxcount)
  {
    // make first rectangle smaller
    m_srcRect1.x += m_scrollSpeed;
    m_srcRect1.w -= m_scrollSpeed;
    m_destRect1.w -= m_scrollSpeed;

    // make second rectangle bigger
    m_srcRect2.w += m_scrollSpeed;
    m_destRect2.w += m_scrollSpeed;
    m_destRect2.x -= m_scrollSpeed;

    // reset and start again
    if(m_destRect2.w >= m_width)
    {
      m_srcRect1.x = 0;
      m_destRect1.x = m_position.getX();
      m_srcRect1.y = 0;
      m_destRect1.y = m_position.getY();

      m_srcRect1.w = m_destRect1.w = m_srcRect2Width = 
      m_destRect1Width = m_width;
      m_srcRect1.h = m_destRect1.h = m_height;

      m_srcRect2.x = 0;
      m_destRect2.x = m_position.getX() + m_width;
      m_srcRect2.y = 0;
      m_destRect2.y = m_position.getY();

      m_srcRect2.w = m_destRect2.w = m_srcRect2Width = 
      m_destRect2Width = 0;
      m_srcRect2.h = m_destRect2.h = m_height;
    }
    count = 0;
  }

  count++;
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.50.222