I started off the UI system by creating some simple assets to use.

ui sprites

After looking at and implementing the Game Menu example code that I found on Talkyard, I quickly realised that with the complexity of the UI that I wanted to implement I should create different classes for each scene in order to make the code more manageable. I also decided that since the UI scenes have similar elements, I should have a base Scene class.

ui uml

The Game will have a SceneManager class which will handle the rendering and updating of the multiple scenes in the game. The Scenes in the game are:

  • Main Menu
  • Shop Menu
  • Game Screen
  • Game Over/Win Menu


Each Scene will have a collection of Sprites to be rendered. The Scene’s Update function will be used to update the state of the buttons in the scene, determining whether the cursor is hovering over the sprite or not and updating the opacity. If a button is selected then the menuItem enum will be used to determine what button was pressed and the SceneManager will handle what to do next: whether to change the scene, or to pass the information on and update the Game class (e.g. exiting the game or adding a power up to the player).

The SceneManager will deal with the rendering of the objects and so in the Game’s render function only the map will need to be rendered along with calling SceneHandler.renderScene(). This will be done as seen below.

  if (scene_handler.screenOpen() == SceneManager::ScreenOpen::GAME)
  {
    map.render();
  }

  scene_handler.render();

This way the UI items needed in the Game Screen (such as health and coins) will be rendered on top of the Map.

The Input for the UI will be handled in the SceneManager. The player will use a controller to select UI items and move the cursor but will also be able to use the mouse if no controller is connected. Therefore, SceneManager will need to handle Mouse and Controller events.

A Point2D will be used to store the cursor position and update the cursor sprite rendered in the game and a boolean value will be used to determine if a button or click has occurred. The mouse input’s will always be setup and enabled, in case the controller is disconnected but will only be utilised if there is no controller.

  void SceneManager::mouseHandler(ASGE::SharedEventData data)
  {
    if (!controller_connected)
    {
      auto event = static_cast<const ASGE::MoveEvent*>(data.get());
      cursor_pos = { float(event->xpos), float(event->ypos) };

      cursor->xPos(cursor_pos.x - cursor->width() / 2);
      cursor->yPos(cursor_pos.y - cursor->height() / 2);
    }
  }

  void SceneManager::clickHandler(ASGE::SharedEventData data)
  {
    if (!controller_connected)
    {
      auto event = static_cast<const ASGE::ClickEvent*>(data.get());
      cursor_pos = { float(event->xpos), float(event->ypos) };

      if (event->action == ASGE::MOUSE::BUTTON_RELEASED)
      {
        selected_pressed = true;
      }
    }
  }

These functions are called automatically when a mouse event occurs, however the controllerHandler is called during the SceneManager Update. GamePadData is only used when there is a controller connected and the controller_connected boolean is updated in this function.

  void SceneManager::controllerHandler(double delta_time, ASGE::Input* input)
  {
    if (input->getGamePad(0).is_connected)
    {
      controller_connected = true;
      ASGE::GamePadData data = input->getGamePad(0);

      cursor_pos.x += data.axis[0] * delta_time * 300;
      cursor_pos.y -= data.axis[1] * delta_time * 300;

      cursor->xPos(cursor_pos.x);
      cursor->yPos(cursor_pos.y);

      if (!already_pressed && data.buttons[0])
      {
        selected_pressed = true;
        already_pressed = true;
      }

      if (!data.buttons[0])
      {
        selected_pressed = false;
        already_pressed = false;
      }
    }
    else
    {
      controller_connected = false;
    }
  }

In order to ensure the button press on the controller is only pressed once, a second boolean (already_pressed) is used. This boolean is set to true whenever the button is pressed and false when it’s released. If the button has already been registered as pressed, then it will not be able to be registered again until the button is released.

Successfully implementing this UI system helped the game feel more complete. At this stage the Items, Collision and Player are all being worked on and completed by other members in the group and the game is coming along nicely. There are a few little tweaks that need to be done to the game and I also need to implement the collision for the Rooms, which I’ll work on next.