Использование системы интерфейса MyGUI

Продолжаем работу над нашим проектом. В данной статье я опишу как подключить к проекту систему пользовательского интерфейса MyGUI. Вообще в Ogre3D уже есть встроенная система интерфейса, но она не очень удобна в работе, поэтому сторонними разработчиками были написаны несколько альтернативных систем. Лично я экспериментировал с двумя из них MyGUI и QuickGUI. MyGUI написана нашими соотечественниками и у неё очень красивые примеры, поэтому я решил остановиться на ней :).

Сначала необходимо скачать и откомпилировать собственно библиотеку MyGUI. Скачать ее можно с сайта http://my-gui.sourceforge.net.
Компиляция библиотеки аналогична компиляции нашего проекта, поэтому сложностей с этим возникнуть не должно.

После сборки библиотеки, попробуем использовать её в игре. Для этого добавим два новых файла в проект Game.h и Game.cpp, в этих файлах описывается класс Game, который будет отвечать за весь игровой процесс в целом.
Ниже приводиться полное содержание файла Game.h

#ifndef __GAME_H__
#define __GAME_H__
	
#include <MyGUI.h>
#include <OIS/OISEvents.h>
	
class Application;
	
enum GameState {
   GAME_STATE_NONE = 0,
   GAME_STATE_MAIN_MENU,
   GAME_STATE_SETTINGS_MENU,
   GAME_STATE_PLAY_GAME
};
	
#define GAME_UPDATE_FPS		20.0f
	
//---------------------------------------------------------
// Игра
//---------------------------------------------------------
class Game
{
   Application*	_app;
   int			_state;
   Ogre::Camera*	_camera;
	
public:
   Game();
   ~Game();
	
   Ogre::Camera* GetCamera()			{ return _camera; }
	
   // Игровой процесс
   void Update(float time);
	
   // Игровые состояния
   void StartMainMenu();
	
   // Делегаты
   void MainMenuDelegate(MyGUI::WidgetPtr widget);
	
   // Обработка событий
   bool EventKeyPressed(const OIS::KeyEvent &keyEventRef);
   bool EventKeyReleased(const OIS::KeyEvent &keyEventRef);
   bool EventMouseMoved(const OIS::MouseEvent &evt);
   bool EventMousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id); 
   bool EventMouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id);
};
#endif

Разберем этот код поближе. Прежде всего здесь описываются игровые состояния:

enum GameState {
   GAME_STATE_NONE = 0,
   GAME_STATE_MAIN_MENU,
   GAME_STATE_SETTINGS_MENU,
   GAME_STATE_PLAY_GAME
};

Игровые состояния нужны для того, чтобы например различать когда мы находимся в начальном меню, а когда в игре и т.п. И соответственно по разному обрабатывать ввод от пользователя.
Дальше мы видим описания функций, которые будут обрабатывать возникающие события:

   // Делегаты
   void MainMenuDelegate(MyGUI::WidgetPtr widget);
	
   // Обработка событий
   bool EventKeyPressed(const OIS::KeyEvent &keyEventRef);
   bool EventKeyReleased(const OIS::KeyEvent &keyEventRef);
   bool EventMouseMoved(const OIS::MouseEvent &evt);
   bool EventMousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id); 
   bool EventMouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id);

Здесь, уже знакомые нам функции обработки ввода с клавиатуры и мышки. А также функция-делегат для обработки всех событий интерфейса, возникающих в начальном меню. Делегатом в MyGUI называется функция, в которой производится обработка событий, происходящих в интерфейсе. В примерах использования библиотеки каждой кнопке назначается свой делегат, но чтобы не плодить функции, я направляю все события в один обработчик.

А теперь рассмотрим сам код. Ниже приведен текст файла Game.cpp

#include "Game.h"
#include "Application.h"
	
//---------------------------------------------------------
// Конструктор
//---------------------------------------------------------
Game::Game()
{
   _state = GAME_STATE_NONE;
   _app = &Application::getInstance();
   _camera = Ogre::Root::getSingletonPtr()->getSceneManager("SceneManager")->createCamera("Camera");
   _camera->setPosition(-400, 300, -400);
   _camera->lookAt(0,0,0);  
   _camera->setNearClipDistance(1);
}
	
//---------------------------------------------------------
// Деструктор
//---------------------------------------------------------
Game::~Game()
{
}
	
//---------------------------------------------------------
// Переход в основное меню
//---------------------------------------------------------
void Game::StartMainMenu()
{
   _app->GetGui()->load("main_menu.layout");
	
   MyGUI::IntSize view = _app->GetGui()->getViewSize();
   MyGUI::WidgetPtr window = _app->GetGui()->findWidget<MyGUI::Widget>("MainMenu");
   window->setPosition(view.width/2 - window->getWidth()/2, view.height/2 - window->getHeight()/2);
	
   MyGUI::ControllerFadeAlpha * controller = new MyGUI::ControllerFadeAlpha(window->getAlpha(), 2, true);
   window->setAlpha(0);
   MyGUI::ControllerManager::getInstance().addItem(window, controller);
	
   MyGUI::ButtonPtr button = _app->GetGui()->findWidget<MyGUI::Button>("ExitButton");
   button->eventMouseButtonClick = MyGUI::newDelegate(this, &Game::MainMenuDelegate);
	
   button = _app->GetGui()->findWidget<MyGUI::Button>("StartButton");
   button->eventMouseButtonClick = MyGUI::newDelegate(this, &Game::MainMenuDelegate);
	
   button = _app->GetGui()->findWidget<MyGUI::Button>("SettingsButton");
   button->eventMouseButtonClick = MyGUI::newDelegate(this, &Game::MainMenuDelegate);
	
   _state = GAME_STATE_MAIN_MENU;
}
	
//---------------------------------------------------------
// Обработка сообщений начального меню
//---------------------------------------------------------
void Game::MainMenuDelegate(MyGUI::WidgetPtr widget)
{
   if(widget->getName() == "ExitButton") {
      _app->Shutdown();
   }
}
	
//---------------------------------------------------------
// Обработка нажатия клавиши
//---------------------------------------------------------
bool Game::EventKeyPressed(const OIS::KeyEvent &evt)
{
   return true;
}
	
//---------------------------------------------------------
// Обработка отпускания клавиши
//---------------------------------------------------------
bool Game::EventKeyReleased(const OIS::KeyEvent &evt)
{
   return true;
}
	
//---------------------------------------------------------
// Обработка движения мышки
//---------------------------------------------------------
bool Game::EventMouseMoved(const OIS::MouseEvent &evt)
{
   return true;
}
	
//---------------------------------------------------------
// Обработка нажатий клавиш мышки
//---------------------------------------------------------
bool Game::EventMousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id)
{
   return true;
}
	
//---------------------------------------------------------
// Обработка отпусканий клавиш мышки
//---------------------------------------------------------
bool Game::EventMouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id)
{
   return true;
}

В этом файле нас интересует функция StartMainMenu, в которой происходит настройка главного меню.
Разберем её содержимое:

_app->GetGui()->load("main_menu.layout");

Здесь происходит загрузка расположения элементов интерфейса. (кнопки, окошки и т.п.) Можно конечно было создать все кнопки вручную, но гораздо удобнее хранить их расположение в отдельном xml-файле. Ниже покажу его содержимое:

<?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout">
	
<Widget type="Window" skin="Window" position="600 450 365 256" layer="Main" name="MainMenu">
	<Property key="Widget_Caption" value="#{TXT_MAIN_MENU}"/>
	<Property key="Widget_Alpha" value="0.8"/>
	
	<Widget type="Button" skin="Button" position="10 10 150 30" align="ALIGN_DEFAULT" name="StartButton" >
      		<Property key="Widget_Caption" value="#{TXT_START_GAME}" />
    	</Widget>
	<Widget type="Button" skin="Button" position="10 50 150 30" align="ALIGN_DEFAULT" name="ResumeButton" >
      		<Property key="Widget_Caption" value="#{TXT_RESUME_GAME}" />
	</Widget>
    	<Widget type="Button" skin="Button" position="10 90 150 30" align="ALIGN_DEFAULT" name="SettingsButton" >
      		<Property key="Widget_Caption" value="#{TXT_SETTINGS}" />
    	</Widget>
    	<Widget type="Button" skin="Button" position="10 130 150 30" align="ALIGN_DEFAULT" name="ExitButton" >
      		<Property key="Widget_Caption" value="#{TXT_EXIT}" />
    	</Widget>
</Widget>
	
</MyGUI>

В нем мы описываем расположение одного окна и кнопок управления в нем.

После того, как все элементы интерфейса загружены, мы получаем указатель на созданное окно меню и располагаем его посередине, в зависимости от текущего разрешения экрана.

   MyGUI::IntSize view = _app->GetGui()->getViewSize();
   MyGUI::WidgetPtr window = _app->GetGui()->findWidget<MyGUI::Widget>("MainMenu");
   window->setPosition(view.width/2 - window->getWidth()/2, view.height/2 - window->getHeight()/2);

После этого создаем и присоединяем к нему контроллер, благодаря которому окошко будет постепенно проявляться на экране. Это просто для красоты :)

   MyGUI::IntSize view = _app->GetGui()->getViewSize();
   MyGUI::WidgetPtr window = _app->GetGui()->findWidget<MyGUI::Widget>("MainMenu");
   window->setPosition(view.width/2 - window->getWidth()/2, view.height/2 - window->getHeight()/2);

Затем указываем обработчик событий всех кнопок. Как я уже указывал, он будет один. А определять от какой кнопки пришло сообщение, мы будем уже в нем.

   MyGUI::ButtonPtr button = _app->GetGui()->findWidget<MyGUI::Button>("ExitButton");
   button->eventMouseButtonClick = MyGUI::newDelegate(this, &Game::MainMenuDelegate);
	
   button = _app->GetGui()->findWidget<MyGUI::Button>("StartButton");
   button->eventMouseButtonClick = MyGUI::newDelegate(this, &Game::MainMenuDelegate);
	
   button = _app->GetGui()->findWidget<MyGUI::Button>("SettingsButton");
   button->eventMouseButtonClick = MyGUI::newDelegate(this, &Game::MainMenuDelegate);

Ну а в самом конце, укажем в каком состоянии находится сейчас наша игра:

   _state = GAME_STATE_MAIN_MENU;

Все, теперь можно компилировать. Если вы ничего не забыли, то должна получиться вот такая картинка:
tutorial_02_1.jpg

Прикрепляю также исходные файлы этого проекта tutorial02.zip.