Difference between revisions of "Complete roguelike tutorial using C++ and libtcod - part 10.2: game menu"

From RogueBasin
Jump to navigation Jump to search
Line 50: Line 50:
     <span style="color:green">gameStatus=STARTUP;</span>
     <span style="color:green">gameStatus=STARTUP;</span>
  }
  }
==A menu helper==
Now we need some utility to display a menu and return the selected item. I put it in the Gui.hpp/Gui.cpp files to avoid using a dedicated file for such a small class :
Gui.hpp :
class Menu {
public :
    enum MenuItemCode {
    NONE,
    NEW_GAME,
    CONTINUE,
        EXIT
    };
    ~Menu();
    void clear();
    void addItem(MenuItemCode code, const char *label);
    MenuItemCode pick();
protected :
    struct MenuItem {
        MenuItemCode code;
        const char *label;
    };
    TCODList<MenuItem *> items;
};
We use an enum to list all possible cases. NONE is when the player didn't select an item in the menu (when he closes the game window). Each menu item has a code and a label. We add this menu as a public field of the Gui class :
class Gui : public Persistent {
public :
    <span style="color:green">Menu menu;</span>
The clear function remove all the items from the menu.
Menu::~Menu() {
    clear();
}
void Menu::clear() {
    items.clearAndDelete();
}
The addItem function create a new item associated with a value from the MenuItemCode enumeration.
void Menu::addItem(MenuItemCode code, const char *label) {
    MenuItem *item=new MenuItem();
    item->code=code;
    item->label=label;
    items.push(item);
}
The pick function displays the menu and wait for the player to select an item with UP/DOWN/ENTER keys. To improve the menu look, we'll use some background image. libtcod makes it possible to display an image using the cells background colors. Of course, one pixel per cell results in a very pixelized picture. We can improve that slightly by using some special characters in libtcod's font that make it possible, using both foreground and background color, to display 2x2 pixels per console cell.
We will use the same image as the python tutorial :
You can save it to your project's main directory (right click the image and choose save as..). The image is also in the zip file attached to the article. We load it in a static variable so that the image is loaded only the first time the menu is displayed. On subsequent calls, we reuse the same image.
Menu::MenuItemCode Menu::pick() {
    static TCODImage img("menu_background1.png");
Then we declare a variable to store the currently selected menu item and start the menu loop :
int selectedItem=0;
while( !TCODConsole::isWindowClosed() ) {
The rendering part start by blitting the image on the root console, using subcell resolution :
img.blit2x(TCODConsole::root,0,0);
The image size is 160x50, twice the size of the console, thus, it covers the whole console and will erase all existing characters/colors. Then we render the menu.
int currentItem=0;
for (MenuItem **it=items.begin(); it!=items.end(); it++) {
    if ( currentItem == selectedItem ) {
        TCODConsole::root->setDefaultForeground(TCODColor::lighterOrange);
    } else {
        TCODConsole::root->setDefaultForeground(TCODColor::lightGrey);
    }
    TCODConsole::root->print(10,10+currentItem*3,(*it)->label);
    currentItem++;
}
We use a light orange for the selected item and light grey for the other items. Once the menu is rendered, we flush it to the screen, then check for keypress :
TCODConsole::flush();
// check key presses
TCOD_key_t key;
TCODSystem::checkForEvent(TCOD_EVENT_KEY_PRESS,&key,NULL);
We don't use waitForEvent to allow the window close event to be detected. While waiting on waitForEvent, you cannot close the game window by clicking on the close window button.
When the player presses the UP key, we set selectedItem to the previous item :
switch (key.vk) {
        case TCODK_UP :
            selectedItem--;
            if (selectedItem < 0) {
                selectedItem=items.size()-1;
            }
        break;
For the DOWN key, we don't need a test, we can use the modulo function to handle the loop :
    case TCODK_DOWN :
    selectedItem = (selectedItem + 1) % items.size();
break;
When the player presses ENTER, we return the item index. Other keys are ignored.
        case TCODK_ENTER : return items.get(selectedItem)->code;
        default : break;
    }
}
Finally, if the player closes the game window, we return NONE :
    return NONE;
}
[[Category:Developing]]
[[Category:Developing]]

Revision as of 15:35, 17 October 2015

Complete roguelike tutorial using C++ and libtcod
-originally written by Jice
Text in this tutorial was released under the Creative Commons Attribution-ShareAlike 3.0 Unported and the GNU Free Documentation License (unversioned, with no invariant sections, front-cover texts, or back-cover texts) on 2015-09-21.


In this article, we're going to implement the game menu. This will bring several improvements to the game :

possibility to start a new game even if there is a saved game. possibility to restart a new game once the player dies or wins, or even in the middle of a game. libtcod function used in this article :

TCODImage::TCODImage

TCODImage::blit2x

Engine refactoring

We now have to be able to restart a new game in the middle of a game. For this, we need some function to clean the engine without having to call the destructor :

Engine.hpp :

   void init();
   void term();
};

The term function must clean the map, the actors and the message log so that the engine is ready to either restart a new game or load a saved one.

Engine::~Engine() {
    <term();
   delete gui;
}

void Engine::term() {
   actors.clearAndDelete();
   if ( map ) delete map;
   gui->clear();
}

The Gui::clear function clears the log :

Gui::~Gui() {
   delete con;
   clear();
}

void Gui::clear() {
   log.clearAndDelete();
}

Now a small subtlety, this the engine can now be initialized after a game was started, so we need to reset the game status to STARTUP to force the recomputation of the field of view, else the fov would be empty after we load a game from the pause menu. In Engine::init :

   gui->message(TCODColor::red, 
       "Welcome stranger!\nPrepare to perish in the Tombs of the Ancient Kings.");
    gameStatus=STARTUP;
}

A menu helper

Now we need some utility to display a menu and return the selected item. I put it in the Gui.hpp/Gui.cpp files to avoid using a dedicated file for such a small class :

Gui.hpp :

class Menu {
public :
   enum MenuItemCode {
   NONE,
   NEW_GAME,
   CONTINUE,
       EXIT
   };
   ~Menu();
   void clear();
   void addItem(MenuItemCode code, const char *label);
   MenuItemCode pick();
protected :
   struct MenuItem {
       MenuItemCode code;
       const char *label;
   };
   TCODList<MenuItem *> items;
};

We use an enum to list all possible cases. NONE is when the player didn't select an item in the menu (when he closes the game window). Each menu item has a code and a label. We add this menu as a public field of the Gui class :

class Gui : public Persistent {
public :
   Menu menu;

The clear function remove all the items from the menu.

Menu::~Menu() {
   clear();
}

void Menu::clear() {
   items.clearAndDelete();
}

The addItem function create a new item associated with a value from the MenuItemCode enumeration.

void Menu::addItem(MenuItemCode code, const char *label) {
   MenuItem *item=new MenuItem();
   item->code=code;
   item->label=label;
   items.push(item);
}

The pick function displays the menu and wait for the player to select an item with UP/DOWN/ENTER keys. To improve the menu look, we'll use some background image. libtcod makes it possible to display an image using the cells background colors. Of course, one pixel per cell results in a very pixelized picture. We can improve that slightly by using some special characters in libtcod's font that make it possible, using both foreground and background color, to display 2x2 pixels per console cell.

We will use the same image as the python tutorial :

You can save it to your project's main directory (right click the image and choose save as..). The image is also in the zip file attached to the article. We load it in a static variable so that the image is loaded only the first time the menu is displayed. On subsequent calls, we reuse the same image.

Menu::MenuItemCode Menu::pick() {
   static TCODImage img("menu_background1.png");

Then we declare a variable to store the currently selected menu item and start the menu loop :

int selectedItem=0;
while( !TCODConsole::isWindowClosed() ) {

The rendering part start by blitting the image on the root console, using subcell resolution :

img.blit2x(TCODConsole::root,0,0);

The image size is 160x50, twice the size of the console, thus, it covers the whole console and will erase all existing characters/colors. Then we render the menu.

int currentItem=0;
for (MenuItem **it=items.begin(); it!=items.end(); it++) {
   if ( currentItem == selectedItem ) {
       TCODConsole::root->setDefaultForeground(TCODColor::lighterOrange);
   } else {
       TCODConsole::root->setDefaultForeground(TCODColor::lightGrey);
   }
   TCODConsole::root->print(10,10+currentItem*3,(*it)->label);
   currentItem++;
}

We use a light orange for the selected item and light grey for the other items. Once the menu is rendered, we flush it to the screen, then check for keypress :

TCODConsole::flush();

// check key presses
TCOD_key_t key;
TCODSystem::checkForEvent(TCOD_EVENT_KEY_PRESS,&key,NULL);

We don't use waitForEvent to allow the window close event to be detected. While waiting on waitForEvent, you cannot close the game window by clicking on the close window button.

When the player presses the UP key, we set selectedItem to the previous item :

switch (key.vk) {
       case TCODK_UP : 
           selectedItem--; 
           if (selectedItem < 0) {
               selectedItem=items.size()-1;
           }
       break;

For the DOWN key, we don't need a test, we can use the modulo function to handle the loop :

   case TCODK_DOWN : 
   selectedItem = (selectedItem + 1) % items.size(); 
break;

When the player presses ENTER, we return the item index. Other keys are ignored.

       case TCODK_ENTER : return items.get(selectedItem)->code;
       default : break;
   }
}

Finally, if the player closes the game window, we return NONE :

   return NONE;
}