Implementing menu states

We will now move on to creating a simple menu state with visuals and mouse handling. We will use two new screenshots for our buttons, which are available with the source code downloads:

Implementing menu states

The following screenshot shows the exit feature:

Implementing menu states

These are essentially sprite sheets with the three states of our button. Let's create a new class for these buttons, which we will call MenuButton. Go ahead and create MenuButton.h and MenuButton.cpp. We will start with the header file:

class MenuButton : public SDLGameObject
{
public:

  MenuButton(const LoaderParams* pParams);

  virtual void draw();
  virtual void update();
  virtual void clean();
};

By now this should look very familiar and it should feel straightforward to create new types. We will also define our button states as an enumerated type so that our code becomes more readable; put this in the header file under private:

enum button_state
{
  MOUSE_OUT = 0,
  MOUSE_OVER = 1,
  CLICKED = 2
};

Open up the MenuButton.cpp file and we can start to flesh out our MenuButton class:

MenuButton::MenuButton(const LoaderParams* pParams) : SDLGameObject(pParams)
{
  m_currentFrame = MOUSE_OUT; // start at frame 0
}

void MenuButton::draw()
{
  SDLGameObject::draw(); // use the base class drawing
}

void MenuButton::update()
{
  Vector2D* pMousePos = TheInputHandler::Instance()
  ->getMousePosition();

  if(pMousePos->getX() < (m_position.getX() + m_width) 
  && pMousePos->getX() > m_position.getX()
  && pMousePos->getY() < (m_position.getY() + m_height) 
  && pMousePos->getY() > m_position.getY())
  {
    m_currentFrame = MOUSE_OVER;

    if(TheInputHandler::Instance()->getMouseButtonState(LEFT))
    {
      m_currentFrame = CLICKED;
    }
  }
  else
  {
    m_currentFrame = MOUSE_OUT;
  }
}

void MenuButton::clean()
{
  SDLGameObject::clean();
}

The only thing really new in this class is the update function. Next, we will go through each step of this function:

  • First, we get the coordinates of the mouse pointer and store them in a pointer to a Vector2D object:
    Vector2D* pMousePos = TheInputHandler::Instance()->getMousePosition();
  • Now, check whether the mouse is over the button or not. We do this by first checking whether the mouse position is less than the position of the right-hand side of the button (x position + width). We then check if the mouse position is greater than the position of the left-hand side of the button (x position). The y-position check is essentially the same with y position + height and y position for bottom and top respectively:
    if(pMousePos->getX() < (m_position.getX() + m_width) 
    && pMousePos->getX() > m_position.getX()
    && pMousePos->getY() < (m_position.getY() + m_height) 
    && pMousePos->getY() > m_position.getY())
  • If the previous check is true, we know that the mouse is hovering over our button; we set its frame to MOUSE_OVER (1):
    m_currentFrame = MOUSE_OVER;
  • We can then check whether the mouse has been clicked; if it has, then we set the current frame to CLICKED(2):
    if(TheInputHandler::Instance()->getMouseButtonState(LEFT))
    {
      m_currentFrame = CLICKED;
    }
  • If the check is not true, then we know the mouse is outside the button and we set the frame to MOUSE_OUT (0):
    else
    {
      m_currentFrame = MOUSE_OUT;
    }

We can now test out our reusable button class. Open up our previously created MenuState.hand, which we will implement for real. First, we are going to need a vector of GameObject* to store our menu items:

std::vector<GameObject*> m_gameObjects;

Inside the MenuState.cpp file, we can now start handling our menu items:

void MenuState::update()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->update();
  }
}
void MenuState::render()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->draw();
  }
}

The onExit and onEnter functions can be defined as follows:

bool MenuState::onEnter()
{
  if(!TheTextureManager::Instance()->load("assets/button.png", 
  "playbutton", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  if(!TheTextureManager::Instance()->load("assets/exit.png", 
  "exitbutton", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  GameObject* button1 = new MenuButton(new LoaderParams(100, 100, 
  400, 100, "playbutton"));
  GameObject* button2 = new MenuButton(new LoaderParams(100, 300, 
  400, 100, "exitbutton"));

  m_gameObjects.push_back(button1);
  m_gameObjects.push_back(button2);

  std::cout << "entering MenuState
";
  return true;
}

bool MenuState::onExit()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->clean();
  }
  m_gameObjects.clear();
  TheTextureManager::Instance()
  ->clearFromTextureMap("playbutton");
  TheTextureManager::Instance()
  ->clearFromTextureMap("exitbutton");

  std::cout << "exiting MenuState
";
  return true;
}

We use TextureManager to load our new images and then assign these textures to two buttons. The TextureManager class also has a new function called clearFromTextureMap, which takes the ID of the texture we want to remove; it is defined as follows:

void TextureManager::clearFromTextureMap(std::string id)
{
  m_textureMap.erase(id);
}

This function enables us to clear only the textures from the current state, not the entire texture map. This is essential when we push states and then pop them, as we do not want the popped state to clear the original state's textures.

Everything else is essentially identical to how we handle objects in the Game class. Run the project and we will have buttons that react to mouse events. The window will look like the following screenshot (go ahead and test it out):

Implementing menu states

Function pointers and callback functions

Our buttons react to rollovers and clicks but do not actually do anything yet. What we really want to achieve is the ability to create MenuButton and pass in the function we want it to call once it is clicked; we can achieve this through the use of function pointers. Function pointers do exactly as they say: they point to a function. We can use classic C style function pointers for the moment, as we are only going to use functions that do not take any parameters and always have a return type of void (therefore, we do not need to make them generic at this point).

The syntax for a function pointer is like this:

returnType (*functionName)(parameters);

We declare our function pointer as a private member in MenuButton.h as follows:

void (*m_callback)();

We also add a new member variable to handle clicking better:

bool m_bReleased;

Now we can alter the constructor to allow us to pass in our function:

MenuButton(const LoaderParams* pParams, void (*callback)());

In our MenuButton.cpp file, we can now alter the constructor and initialize our pointer with the initialization list:

MenuButton::MenuButton(const LoaderParams* pParams, void (*callback)() ) : SDLGameObject(pParams), m_callback(callback)

The update function can now call this function:

void MenuButton::update()
{
  Vector2D* pMousePos = TheInputHandler::Instance()
  ->getMousePosition();

  if(pMousePos->getX() < (m_position.getX() + m_width) 
  && pMousePos->getX() > m_position.getX()
  && pMousePos->getY() < (m_position.getY() + m_height) 
  && pMousePos->getY() > m_position.getY())
  {
    if(TheInputHandler::Instance()->getMouseButtonState(LEFT) 
    && m_bReleased)
    {
      m_currentFrame = CLICKED;

      m_callback(); // call our callback function

      m_bReleased = false;
    }
    else if(!TheInputHandler::Instance()
    ->getMouseButtonState(LEFT))
    {
      m_bReleased = true;
      m_currentFrame = MOUSE_OVER;
    }
  }
  else
  {
    m_currentFrame = MOUSE_OUT;
  }
}

Note that this update function now uses the m_bReleased value to ensure we release the mouse button before doing the callback again; this is how we want our clicking to behave.

In our MenuState.h object, we can declare some functions that we will pass into the constructors of our MenuButton objects:

private:
// call back functions for menu items
static void s_menuToPlay();
static void s_exitFromMenu();

We have declared these functions as static; this is because our callback functionality will only support static functions. It is a little more complicated to handle regular member functions as function pointers, so we will avoid this and stick to static functions. We can define these functions in the MenuState.cpp file:

void MenuState::s_menuToPlay()
{
  std::cout << "Play button clicked
";
}

void MenuState::s_exitFromMenu()
{
  std::cout << "Exit button clicked
";
}

We can pass these functions into the constructors of our buttons:

GameObject* button1 = new MenuButton(new LoaderParams(100, 100, 400, 100, "playbutton"), s_menuToPlay);
GameObject* button2 = new MenuButton(new LoaderParams(100, 300, 400, 100, "exitbutton"), s_exitFromMenu);

Test our project and you will see our functions printing to the console. We are now passing in the function we want our button to call once it is clicked; this functionality is great for our buttons. Let's test the exit button with some real functionality:

void MenuState::s_exitFromMenu()
{
  TheGame::Instance()->quit();
}

Now clicking on our exit button will exit the game. The next step is to allow the s_menuToPlay function to move to PlayState. We first need to add a getter to the Game.h file to allow us to access the state machine:

GameStateMachine* getStateMachine(){ return m_pGameStateMachine; }

We can now use this to change states in MenuState:

void MenuState::s_menuToPlay()
{
  TheGame::Instance()->getStateMachine()->changeState(new 
  PlayState());
}

Go ahead and test; PlayState does not do anything yet, but our console output should show the movement between states.

Implementing the temporary play state

We have created MenuState; next, we need to create PlayState so that we can visually see the change in our states. For PlayState we will create a player object that uses our helicopter.png image and follows the mouse around. We will start with the Player.cpp file and add the code to make the Player object follow the mouse position:

void Player::handleInput()
{
  Vector2D* target = TheInputHandler::Instance()
  ->getMousePosition();

  m_velocity = *target - m_position;

  m_velocity /= 50;
}

First, we get the current mouse location; we can then get a vector that leads from the current position to the mouse position by subtracting the current position from the mouse position. We then divide the velocity by a scalar to slow us down a little and allow us to see our helicopter catch up to the mouse rather than stick to it. Our PlayState.h file will now need its own vector of GameObject*:

class GameObject;

class PlayState : public GameState
{
public:

  virtual void update();
  virtual void render();

  virtual bool onEnter();
  virtual bool onExit();

  virtual std::string getStateID() const { return s_playID; }

private:

  static const std::string s_playID;

  std::vector<GameObject*> m_gameObjects;
};

Finally, we must update the PlayState.cpp implementation file to use our Player object:

const std::string PlayState::s_playID = "PLAY";

void PlayState::update()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->update();
  }
}

void PlayState::render()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->draw();
  }
}

bool PlayState::onEnter()
{
  if(!TheTextureManager::Instance()->load("assets/helicopter.png", 
  "helicopter", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  GameObject* player = new Player(new LoaderParams(100, 100, 128, 
  55, "helicopter");

  m_gameObjects.push_back(player);

  std::cout << "entering PlayState
";
  return true;
}

bool PlayState::onExit()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->clean();
  }
  m_gameObjects.clear();
  TheTextureManager::Instance()
  ->clearFromTextureMap("helicopter");

  std::cout << "exiting PlayState
";
  return true;
}

This file is very similar to the MenuState.cpp file, but this time we are using a Player object rather than the two MenuButton objects. There is one adjustment to our SDLGameObject.cpp file that will make PlayState look even better; we are going to flip the image file depending on the velocity of the object:

void SDLGameObject::draw()
{
  if(m_velocity.getX() > 0)
  {
    TextureManager::Instance()->drawFrame(m_textureID, 
    (Uint32)m_position.getX(), (Uint32)m_position.getY(),
    m_width, m_height, m_currentRow, m_currentFrame, 
    TheGame::Instance()->getRenderer(),SDL_FLIP_HORIZONTAL);
  }
  else
  {
    TextureManager::Instance()->drawFrame(m_textureID, 
    (Uint32)m_position.getX(), (Uint32)m_position.getY(),
    m_width, m_height, m_currentRow, m_currentFrame, 
    TheGame::Instance()->getRenderer());
  }
}

We check whether the object's velocity is more than 0 (moving to the right-hand side) and flip the image accordingly. Run our game and you will now have the ability to move between MenuState and PlayState each with their own functionality and objects. The following screenshot shows our project so far:

Implementing the temporary play state

Pausing the game

Another very important state for our games is the pause state. Once paused, the game could have all kinds of options. Our PauseState class will be very similar to the MenuState, but with different button visuals and callbacks. Here are our two new screenshots (again available in the source code download):

Pausing the game

The following screenshot shows the resume functionality:

Pausing the game

Let's start by creating our PauseState.h file in the project:

class GameObject;

class PauseState : public GameState
{
public:

  virtual void update();
  virtual void render();

  virtual bool onEnter();
  virtual bool onExit();

  virtual std::string getStateID() const { return s_pauseID; }

private:

  static void s_pauseToMain();
  static void s_resumePlay();

  static const std::string s_pauseID;

  std::vector<GameObject*> m_gameObjects;
};

Next, create our PauseState.cpp file:

const std::string PauseState::s_pauseID = "PAUSE";

void PauseState::s_pauseToMain()
{
  TheGame::Instance()->getStateMachine()->changeState(new 
  MenuState());
}

void PauseState::s_resumePlay()
{
  TheGame::Instance()->getStateMachine()->popState();
}

void PauseState::update()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->update();
  }
}

void PauseState::render()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->draw();
  }
}

bool PauseState::onEnter()
{
  if(!TheTextureManager::Instance()->load("assets/resume.png", 
  "resumebutton", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  if(!TheTextureManager::Instance()->load("assets/main.png", 
  "mainbutton", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  GameObject* button1 = new MenuButton(new LoaderParams(200, 100, 
  200, 80, "mainbutton"), s_pauseToMain);
  GameObject* button2 = new MenuButton(new LoaderParams(200, 300, 
  200, 80, "resumebutton"), s_resumePlay);

  m_gameObjects.push_back(button1);
  m_gameObjects.push_back(button2);

  std::cout << "entering PauseState
";
  return true;
}

bool PauseState::onExit()
{
  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->clean();
  }
  m_gameObjects.clear();
  TheTextureManager::Instance()
  ->clearFromTextureMap("resumebutton");
  TheTextureManager::Instance()
  ->clearFromTextureMap("mainbutton");
  // reset the mouse button states to false
  TheInputHandler::Instance()->reset();

  std::cout << "exiting PauseState
";
  return true;
}

In our PlayState.cpp file, we can now use our new PauseState class:

void PlayState::update()
{
  if(TheInputHandler::Instance()->isKeyDown(SDL_SCANCODE_ESCAPE))
  {
    TheGame::Instance()->getStateMachine()->pushState(new 
    PauseState());
  }

  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->update();
  }
}

This function listens for the Esc key being pressed, and once it has been pressed, it then pushes a new PauseState class onto the state array in FSM. Remember that pushState does not remove the old state; it merely stops using it and uses the new state. Once we are done with the pushed state, we remove it from the state array and the game continues to use the previous state. We remove the pause state using the resume button's callback:

void PauseState::s_resumePlay()
{
  TheGame::Instance()->getStateMachine()->popState();
}

The main menu button takes us back to the main menu and completely removes any other states:

void PauseState::s_pauseToMain()
{
  TheGame::Instance()->getStateMachine()->changeState(new 
  MenuState());
}

Creating the game over state

We are going to create one final state, GameOverState. To get to this state, we will use collision detection and a new Enemy object in the PlayState class. We will check whether the Player object has hit the Enemy object, and if so, we will change to our GameOverState class. Our Enemy object will use a new image helicopter2.png:

Creating the game over state

We will make our Enemy object's helicopter move up and down the screen just to keep things interesting. In our Enemy.cpp file, we will add this functionality:

Enemy::Enemy(const LoaderParams* pParams) : SDLGameObject(pParams)
{
  m_velocity.setY(2);
  m_velocity.setX(0.001);
}

void Enemy::draw()
{
  SDLGameObject::draw();
}

void Enemy::update()
{
  m_currentFrame = int(((SDL_GetTicks() / 100) % m_numFrames));

  if(m_position.getY() < 0)
  {
    m_velocity.setY(2);
  }
  else if(m_position.getY() > 400)
  {
    m_velocity.setY(-2);
  }

  SDLGameObject::update();
}

We can now add an Enemy object to our PlayState class:

bool PlayState::onEnter()
{
  if(!TheTextureManager::Instance()->load("assets/helicopter.png", 
  "helicopter", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  if(!TheTextureManager::Instance()
  ->load("assets/helicopter2.png", "helicopter2", 
  TheGame::Instance()->getRenderer()))
  {
    return false;
  }


  GameObject* player = new Player(new LoaderParams(500, 100, 128, 
  55, "helicopter"));
  GameObject* enemy = new Enemy(new LoaderParams(100, 100, 128, 
  55, "helicopter2"));

  m_gameObjects.push_back(player);
  m_gameObjects.push_back(enemy);

  std::cout << "entering PlayState
";
  return true;
}

Running the game will allow us to see our two helicopters:

Creating the game over state

Before we cover collision detection, we are going to create our GameOverState class. We will be using two new images for this state, one for new MenuButton and one for a new type, which we will call AnimatedGraphic:

Creating the game over state

The following screenshot shows the game over functionality:

Creating the game over state

AnimatedGraphic is very similar to other types, so I will not go into too much detail here; however, what is important is the added value in the constructor that controls the speed of the animation, which sets the private member variable m_animSpeed:

AnimatedGraphic::AnimatedGraphic(const LoaderParams* pParams, int animSpeed) : SDLGameObject(pParams), m_animSpeed(animSpeed)
{

}

The update function will use this value to set the speed of the animation:

void AnimatedGraphic::update()
{
  m_currentFrame = int(((SDL_GetTicks() / (1000 / m_animSpeed)) % 
  m_numFrames));
}

Now that we have the AnimatedGraphic class, we can implement our GameOverState class. Create GameOverState.h and GameOverState.cpp in our project; the header file we will create should look very familiar, as given in the following code:

class GameObject;

class GameOverState : public GameState
{
public:

  virtual void update();
  virtual void render();

  virtual bool onEnter();
  virtual bool onExit();

  virtual std::string getStateID() const {return s_gameOverID;}

private:

  static void s_gameOverToMain();
  static void s_restartPlay();

  static const std::string s_gameOverID;

  std::vector<GameObject*> m_gameObjects;
};

Our implementation file is also very similar to other files already covered, so again I will only cover the parts that are different. First, we define our static variables and functions:

const std::string GameOverState::s_gameOverID = "GAMEOVER";

void GameOverState::s_gameOverToMain()
{
  TheGame::Instance()->getStateMachine()->changeState(new 
  MenuState());
}

void GameOverState::s_restartPlay()
{
  TheGame::Instance()->getStateMachine()->changeState(new 
  PlayState());
}

The onEnter function will create three new objects along with their textures:

bool GameOverState::onEnter()
{
  if(!TheTextureManager::Instance()->load("assets/gameover.png", 
  "gameovertext", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  if(!TheTextureManager::Instance()->load("assets/main.png", 
  "mainbutton", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  if(!TheTextureManager::Instance()->load("assets/restart.png", 
  "restartbutton", TheGame::Instance()->getRenderer()))
  {
    return false;
  }

  GameObject* gameOverText = new AnimatedGraphic(new 
  LoaderParams(200, 100, 190, 30, "gameovertext", 2), 2);
  GameObject* button1 = new MenuButton(new LoaderParams(200, 200, 
  200, 80, "mainbutton"), s_gameOverToMain);
  GameObject* button2 = new MenuButton(new LoaderParams(200, 300, 
  200, 80, "restartbutton"), s_restartPlay);

  m_gameObjects.push_back(gameOverText);
  m_gameObjects.push_back(button1);
  m_gameObjects.push_back(button2);

  std::cout << "entering PauseState
";
  return true;
}

That is pretty much it for our GameOverState class, but we must now create a condition that creates this state. Move to our PlayState.h file and we will create a new function to allow us to check for collisions:

bool checkCollision(SDLGameObject* p1, SDLGameObject* p2);

We will define this function in PlayState.cpp:

bool PlayState::checkCollision(SDLGameObject* p1, SDLGameObject* 
p2)
{
  int leftA, leftB;
  int rightA, rightB;
  int topA, topB;
  int bottomA, bottomB;

  leftA = p1->getPosition().getX();
  rightA = p1->getPosition().getX() + p1->getWidth();
  topA = p1->getPosition().getY();
  bottomA = p1->getPosition().getY() + p1->getHeight();

  //Calculate the sides of rect B
  leftB = p2->getPosition().getX();
  rightB = p2->getPosition().getX() + p2->getWidth();
  topB = p2->getPosition().getY();
  bottomB = p2->getPosition().getY() + p2->getHeight();

  //If any of the sides from A are outside of B
  if( bottomA <= topB ){return false;} 
  if( topA >= bottomB ){return false; }
  if( rightA <= leftB ){return false; }
  if( leftA >= rightB ){return false;}

  return true;
}

This function checks for collisions between two SDLGameObject types. For the function to work, we need to add three new functions to our SDLGameObject class:

Vector2D& getPosition() { return m_position; }
int getWidth() { return m_width; }
int getHeight() { return m_height; }

The next chapter will deal with how this function works, but for now, it is enough to know that it does. Our PlayState class will now utilize this collision detection in its update function:

void PlayState::update()
{
  if(TheInputHandler::Instance()->isKeyDown(SDL_SCANCODE_ESCAPE))
  {
    TheGame::Instance()->getStateMachine()->pushState(new 
    PauseState());
  }

  for(int i = 0; i < m_gameObjects.size(); i++)
  {
    m_gameObjects[i]->update();
  }

  if(checkCollision(dynamic_cast<SDLGameObject*>
  (m_gameObjects[0]), dynamic_cast<SDLGameObject*>
  (m_gameObjects[1])))
  {
    TheGame::Instance()->getStateMachine()->pushState(new 
    GameOverState());
  }
}

We have to use a dynamic_cast object to cast our GameObject* class to an SDLGameObject* class. If checkCollision returns true, then we add the GameOverState class. The following screenshot shows the GameOver state:

Creating the game over state
..................Content has been hidden....................

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