netlib.narod.ru< Назад | Оглавление | Далее >

Горячие точки, или как я научился любить щелчки мыши

Теперь у вас есть чудесно выглядящий титульный экран, но идти дальше никуда нельзя. Лучшим выбором будет добавить несколько функциональных возможностей для работы с мышью и двигаться дальше! Предыдущий пример программы выглядит действительно предлестно, но не предоставляет пользователю никаких возможностей для взаимодействия. Чтобы исправить это упущение я покажу вам как добавлять к приложениям активные зоны, реагирующие на щелчки мышью. Поскольку в большинстве игр для PC управление выполняется мышью, это очень важный шаг. В данном разделе мы обсудим следующие темы:

Обнаружение активных зон

Если вы забыли, что такое активная зона, пожалуйста вернитесь к началу этой главы и еще раз прочитайте раздел «Активные зоны и графика». Первая из программ, которые мы сейчас рассмотрим, называется D3D_MouseZones, и демонстрирует простую навигацию по меню. Она содержит несколько работающих меню, по которым может перемещаться пользователь. Экран программы с главным меню изображен на рис. 6.22.


Рис. 6.22. Окно программы D3D_MouseZones

Рис. 6.22. Окно программы D3D_MouseZones


На рис. 6.22 показан экран с главным окном программы. В данном примере работают не все кнопки меню, а только Options и Exit. Программа устанавливает активные зоны и реагирует на щелчки пользователя по кнопкам меню. Теперь загрузите проект, чтобы перейти к изучению кода. Имя проекта — D3D_MouseZones.

Архитектура проекта D3D_MouseZones

Проект содержит четыре уникальных файла: main.cpp, main.h, MouseZoneClass.cpp и MouseZoneClass.h. Файлы с кодом класса для реализации активных зон являются основным отличием этого проекта от предыдущего.

Программе необходимы следующие библиотеки: d3d9.lib, dxguid.lib, d3dx9dt.lib, d3dxof.lib, comctl32.lib и winmm.lib.

Заголовочный файл Main.h

Главный заголовочный файл проекта называется main.h. В нем я выполняю следующие действия:

Глобальные данные активных зон

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

#define STRICT
#include <windows.h>
#include <commctrl.h>
#include <commdlg.h>
#include <math.h>
#include <tchar.h>
#include <stdio.h>
#include <D3DX9.h>
#include "DXUtil.h"
#include "D3DUtil.h"
#include "MouseZoneClass.h"

// Глобальные переменные
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVBInterface = NULL;
// Глобальный массив для хранения графики интерфейса
LPDIRECT3DTEXTURE9  g_pTexture[32];
// Смещения видимой области окна
int    g_iXOffset = 0;
int    g_iYOffset = 0;
// Размеры окна
int    g_iWindowWidth = 640;
int    g_iWindowHeight = 480;
// Структура данных нашего настраиваемого формата вершин
struct CUSTOMVERTEX
{
   D3DXVECTOR3 position;   // Местоположение
   D3DXVECTOR3 vecNorm;    // Нормаль
   FLOAT       tu, tv;     // Координаты текстуры
   FLOAT       tu2, tv2;   // Координаты текстуры
};
// Описание структуры настраиваемого формата вершинwh
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2)

void vDrawInterfaceObject(int iXPos, int iYPos, float fXSize, float fYSize,
                          int iTexture);
void vInitInterfaceObjects(void);
LRESULT WINAPI fnMessageProcessor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
HRESULT InitD3D(HWND hWnd);
void vRender(void);
void vCleanup(void);
void vCheckInput(void);
void vSetupMouseZones(int iMenu);

// Глобальный класс активных зон
MouseZoneClass   MZones;
// Идентификатор текущего меню
int    g_iCurrentScreen = 0;
// Переменные состояния кнопок мыши
bool   g_bLeftButton = 0;
bool   g_bRightButton = 0;
// Глобальный дескриптор окна игры
HWND   g_hWnd;

Первым отличием приведенного кода является наличие директивы включения заголовочного файла MouseZoneClass.h. Этот файл содержит заголовочные данные класса MouseZoneClass. Я подробно опишу его чуть позже. До этого момента просто помните о необходимости включать этот файл каждый раз, когда вы хотите использовать класс MouseZoneClass.

Теперь обратите внимание на два новых прототипа функций. Первая из функций, называемая vCheckInput(), проверяет состояние мыши и обрабатывает полученные результаты. Вторая функция называется vSetupMouseZones() и устанавливает необходимые горячие точки в зависимости от текущего меню.

Следующий фрагмент нового кода создает глобальный объект MouseZoneClass. Он называется MZones и используется для всех активных зон, присутствующих в игре.

Затем в коде расположилась целочисленная переменная с именем g_iCurrentScreen. Она отслеживает в каком меню пользователь находится в данный момент. Это необходимо для того, чтобы программа знала какие команды меню ей обрабатывать.

Следующие две новых строки содержат переменные состояния кнопок мыши. Кнопка мыши может быть либо нажата, либо отпущена, так что для хранения ее состояния можно воспользоваться логической переменной. Когда значение переменной равно 0, — кнопка отпущена. Когда значение переменной равно 1, — кнопка нажата.

В последней строке кода создается глобальный дескриптор окна. Он необходим для доступа к созданному программой окну из функций, отличных от WinMain().

Файл программы Main.cpp

Следующий уникальный файл проекта называется main.cpp. Он содержит обычный код Windows и некоторый объем нового кода для обработки ввода от мыши и обнаружения активных зон.

Функция WinMain()

Функция WinMain() в рассматриваемой программе изменилась весьма незначительно. Взгляните на приведенный ниже фрагмент кода, в котором выделены главные отличия:

// Инициализация Direct3D
if(SUCCEEDED(InitD3D(hWnd))) {
   // Инициализация виртуального буфера для отображения четырехугольников
   vInitInterfaceObjects();
   // Инициализация активных зон
   vSetupMouseZones(0);
   // Начало цикла обработки сообщений
   while(msg.message != WM_QUIT) {
      if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
      else {
         // Если все нормально, обработать щелчок мыши
         if(timeGetTime() > dwInputTimer) {
            // Проверка входных данных
            vCheckInput();
            dwInputTimer = timeGetTime() + 50;
         }

         // Проверка необходимости выхода из программы
         if(g_iCurrentScreen == 3) {
            break;
         }

         // Визуализация сцены
         vRender();
      }
   }
}

Первым изменением является добавленный вызов функции vSetupMouseZones(). Программа только что была запущена и вызов этой функции необходим для того, чтобы установить активные зоны для первого экрана. На рис. 6.23 показан первый экран, выводимый программой.


Рис. 6.23. Первый экран программы D3D_MouseZones

Рис. 6.23. Первый экран программы D3D_MouseZones


Сейчас изображенный на рис. 6.23 экран должен выглядеть для вас очень знакомо. Это титульный экран игры Battle Armor. Поскольку делать на титульном экране почти нечего, я устанавливаю лишь пару активных зон. Давайте перейдем к коду функции vSetupMouseZones(), чтобы увидеть какие зоны используются на титульном экране.

Функция vSetupMouseZones()

Функция vSetupMouseZones() содержит следующий код:

void vSetupMouseZones(int iMenu)
{
   // Титульный экран
   if(iMenu == 0) {
      MZones.vFreeZones();
      MZones.vInitialize(2);
      MZones.iAddZone("TITLE_SCREEN", 0, 0, 640, 480, 2);
      MZones.iAddZone("EXIT_BUTTON", 587, 0, 53, 24, 0);
   }
   // Главное меню
   else if(iMenu == 1) {
      MZones.vFreeZones();
      MZones.vInitialize(5);
      MZones.iAddZone("EXIT_BUTTON", 587, 0, 53, 24, 0);
      MZones.iAddZone("MAINMENU_NEWGAME", 192, 64, 256, 64, 0);
      MZones.iAddZone("MAINMENU_LOADGAME", 192, 128, 256, 64, 0);
      MZones.iAddZone("MAINMENU_SAVEGAME", 192, 192, 256, 64, 0);
      MZones.iAddZone("MAINMENU_OPTIONS", 192, 256, 256, 64, 0);
   }
   // Экран выхода из игры
   else if(iMenu == 2) {
      MZones.vFreeZones();
      MZones.vInitialize(1);
      MZones.iAddZone("TITLE_SCREEN", 0, 0, 640, 480, 2);
   }
   // Меню параметров
   else if(iMenu == 7) {
      MZones.vFreeZones();
      MZones.vInitialize(5);
      MZones.iAddZone("EXIT_BUTTON", 587, 0, 53, 24, 0);
      MZones.iAddZone("OPTIONS_AUDIO", 192, 64, 256, 64, 0);
      MZones.iAddZone("OPTIONS_VIDEO", 192, 128, 256, 64, 0);
      MZones.iAddZone("OPTIONS_DIFF", 192, 192, 256, 64, 0);
      MZones.iAddZone("OPTIONS_BACK", 192, 256, 256, 64, 0);
   }
}

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

Функция MouseZoneClass::vFreeZones()

Как видите, функция проверяет переданный ей номер меню и действует соответствующим образом. WinMain() при вызове функции передает ей номер меню 0. В коде, относящемся к меню с номером 0, первым расположен вызов функции MouseZoneClass::vFreeZones(). Эта функция удаляет все существующие на данный момент активные зоны и выполняет инициализацию класса активных зон. Это необходимо, так как к титульному экрану можно перейти из других меню.

Функция MouseZoneClass::vInitialize()

Затем вызывается функция MouseZoneClass::vInitialize(). Она выделяет память для активных зон, количество которых передано ей в параметре. Вы можете указать число, превышающее максимальное количество активных зон, которые вы реально будете использовать. Соответствие параметра количеству активных зон необходимо только для повышения эффективности программы.

ВНИМАНИЕ
Убедитесь, что вы инициализируете класс для достаточного количества активных зон, чтобы он мог обрабатывать все зоны, которые вы намереваетесь использовать. Если вы превысите указанное при инициализацмм максимальное значение, код не будет работать.

Затем начинается код действительного создания активных зон. Активные зоны, используемые в титульном экране, создаются путем вызова функции MouseZoneClass::iAddZone(). Титульный экран содержит две активных зоны: одну для кнопки выхода из игры и одну для всего остального экрана. Кнопка выхода из игры направляет пользователя к финальному экрану программы, а вторая активная зона переносит игрока к главному меню.

Функция MouseZoneClass::iAddZone()

Прототип добавляющей активную зону функции выглядит следующим образом:

int MouseZoneClass::iAddZone(
   char *szZoneName,
   short shX,
   short shY,
   short shWidth,
   short shHeight,
   short shClickType)

Первый параметр называется szZoneName и в нем передается имя создаваемой активной зоны. Класс активной зоны использует это имя, чтобы сообщить вам какая из зон была активирована, поэтому оно очень важно. Для титульного экрана используются два имени. Кнопке выхода из игры я присваиваю имя EXIT_BUTTON, а остальной части экрана — имя TITLE_SCREEN.

Следующие два параметра, shX и shY, задают местоположение левого верхнего угла активной зоны. Активные зоны являются прямоугольными, так что данные о местоположении одного из углов необходимы. Все координаты указываются в пространстве рабочей области окна, так что вам не надо беспокоиться об их вычислении. Если вы взглянете на код, то увидите, что зона с именем TITLE_SCREEN начинается в левом верхнем углу экрана с координатами (0, 0).

Следующий параметр называется shWidth. Он задает ширину активной зоны. Ширина окна игры в рассматриваемом примере равна 640 точкам, поэтому я задаю ширину охватывающей весь экран зоны равной 640.

Следующий параметр, shHeight, задает высоту зоны.

Последний параметр называется shClickType и определяет тип щелчка мыши, на который будет реагировать зона. Доступные типы и их описания приведены в таблице 6.10.

Таблица 6.10. Типы щелчков мышью в классе MouseZoneClass

Значение Описание
0 Нажатие левой кнопки мыши.
1 Нажатие правой кнопки мыши.
2 Нажатие любой кнопки мыши.
3 Нажатие кнопок мыши не требуется.


Тип щелчка позволяет управлять поведением активной зоны в зависимости от состояния кнопок мыши. Помимо прочих применений, это очень полезно для зон, реагирующих на наведение указателя мыши. В рассматриваемом примере я указываю, что зона TITLE_SCREEN будет срабатывать при щелчке любой кнопкой мыши, а зона EXIT_BUTTON реагирует только на щелчок левой кнопкой.

Функция vCheckInput()

Мы покинули функцию WinMain() и перешли к функции vSetupMouseZones(), а затем к членам класса активных зон. Настало время вернуться обратно к функции WinMain() и посмотреть где обрабатываются события мыши. В главной функции находится следующий код:

if(timeGetTime() > dwInputTimer) {
   // Проверка входных данных
   vCheckInput();
   dwInputTimer = timeGetTime()+50;
}
Управление щелчками мыши

Внутри этого небольшого фрагмента кода находится вызов функции vCheckInput(). Возможно, вы недоумеваете для чего здесь нужен вызов функции timeGetTime(). Дело в том, что современные компьютеры настолько быстрые, что единственное нажатие на кнопку мыши длится десятки итераций цикла обработки сообщений. В результате одно нажатие на кнопку мыши активирует пункт меню десятки раз. Это приведет к проблемам, поскольку ваш интерфейс окажется слишком чувствительным к сделанным пользователем щелчкам мышью. Чтобы побороть проблему, я установил таймер, который разрешает обработку сообытия мыши не чаще чем раз в 50 милисекунд. Код проверяет текущее время и смотрит прошло ли 50 милисекунд с момента последнего вызова функции vCheckInput(). Если прошло достаточно времени, функция вызывается снова и таймер сбрасывается. Если время еще не истекло, выполнение кода продолжается, но никакой проверки введенных данных не происходит. Величина 50 милисекунд выбрана мной произвольным образом и вы можете изменить ее в соответствии с вашими вкусами. Если вам непонятно, какой эффект она оказывает, установите значение 0 и запустите пример (попробуйте щелкнуть в меню по кнопке Options).

Функция vCheckInput() проверяет текущую позицию указателя мыши и выполняет действия, зависящие от того, какое меню в данный момент показывается пользователю. Вот как выглядит код этой функции:

void vCheckInput(void)
{
   bool   bRet;
   char   szZoneHit[64];
   POINT  Point;
   RECT   rcWindowRect;
   int    iMouseX;
   int    iMouseY;

   // Проверка смещения окна
   GetWindowRect(g_hWnd, &rcWindowRect);
   // Обновление позиции указателя мыши
   GetCursorPos(&Point);
   // Вычисление реальных координат указателя мыши
   iMouseX = Point.x - g_iXOffset - rcWindowRect.left;
   iMouseY = Point.y - g_iYOffset - rcWindowRect.top;
   // Проверка попадания в активную зону
   bRet = MZones.bCheckZones(
          (short)iMouseX, (short)iMouseY,
          szZoneHit, g_bLeftButton,
          g_bRightButton);
   if(bRet) {
      // ЛОГИКА ДЛЯ ТИТУЛЬНОГО ЭКРАНА
      if(g_iCurrentScreen == 0) {
         // Переход к главному меню
         if(!stricmp(szZoneHit, "TITLE_SCREEN")) {
            // Делаем главное меню текущим
            g_iCurrentScreen = 1;
            // Устанавливаем активные зоны
            vSetupMouseZones(1);
         }
         // Переход к экрану завершения игры
         else if(!stricmp(szZoneHit, "EXIT_BUTTON")) {
            // Делаем завершающий экран текущим
            g_iCurrentScreen = 2;
            // Устанавливаем активные зоны
            vSetupMouseZones(2);
         }
      }
      // ЛОГИКА ГЛАВНОГО МЕНЮ
      else if(g_iCurrentScreen == 1) {
         // Переход к экрану завершения игры
         if(!stricmp(szZoneHit, "EXIT_BUTTON")) {
            // Делаем завершающий экран текущим
            g_iCurrentScreen = 2;
            // Устанавливаем активные зоны
            vSetupMouseZones(2);
         }
         else if(!stricmp(szZoneHit, "MAINMENU_NEWGAME"))
         {
            // Добавьте сюда код для начала новой игры
         }
         else if(!stricmp(szZoneHit, "MAINMENU_LOADGAME"))
         {
            // Добавьте сюда код для загрузки игры
         }
         else if(!stricmp(szZoneHit, "MAINMENU_SAVEGAME"))
         {
            // Добавьте сюда код для сохранения игры
         }
         else if(!stricmp(szZoneHit, "MAINMENU_OPTIONS"))
         {
            // Делаем меню параметров текущим
            g_iCurrentScreen = 7;
            // Устанавливаем активные зоны
            vSetupMouseZones(7);
         }
      }
      // ЛОГИКА ЭКРАНА ЗАВЕРШЕНИЯ ИГРЫ
      else if(g_iCurrentScreen == 2) {
         // Выходим из программы, если пользователь
         // нажмет любую кнопку мыши
         if(!stricmp(szZoneHit, "TITLE_SCREEN")) {
            // Сообщаем WinMain() о завершении программы
            g_iCurrentScreen = 3;
         }
      }
      // ЛОГИКА МЕНЮ ПАРАМЕТРОВ
      else if(g_iCurrentScreen == 7) {
         // Переход к экрану завершения игры
         if(!stricmp(szZoneHit, "EXIT_BUTTON")) {
            // Делаем экран завершения игры текущим
            g_iCurrentScreen = 2;
            // Устанавливаем активные зоны
            vSetupMouseZones(2);
         }
         // Возврат к главному меню
         else if(!stricmp(szZoneHit, "OPTIONS_BACK")) {
            // Делаем главное меню текущим
            g_iCurrentScreen = 1;
            // Устанавливаем активные зоны
            vSetupMouseZones(1);
         }
      }
   }
}
Вычисление смещения клиентской области окна на рабочем столе

Поскольку окна в операционной системе могут перемещаться, программа сначала должна проверить где именно расположено окно в данный момент. Эту задачу выполняет вызов функции GetWindowRect(). Вооружившись координатами окна, код может вычислить где находился указатель мыши в момент щелчка относительно клиентской области окна. Это очень важный момент, поскольку активные зоны определяются в клиентском пространстве, а не в пространстве рабочего стола. Взгляните, например на экран, изображенный на рис. 6.24.


Рис. 6.24. Различия между клиентской областью и пространством рабочего стола

Рис. 6.24. Различия между клиентской областью и пространством рабочего стола


На рис. 6.24 показано окно игры на рабочем столе системы. Ширина клиентской области окна равна 640 точкам, а высота — 480 точкам. Ширина рабочего стола сотавляет 1024 точки, а его высота — 768 точек. В клиентской области окна есть активная зона, и ее координаты в клиентском пространстве — (340, 10). Очень важно понимать, что класс активных зон хранит координаты в клиентском простанстве, а не в пространстве рабочего стола. Теперь представьте себе, что произойдет, если вы будете искать активную зону с координатами (340, 10), а окно будет передвинуто. В этом вам поможет рис. 6.25.


Рис. 6.25. Перемещение окна на рабочем столе

Рис. 6.25. Перемещение окна на рабочем столе


На рис. 6.25 окно было перемещено. Поэтому его клиентская область сместилась на 10 точек вправо и на 10 точек вниз. Щелчки мышью по активной зоне теперь регистрируются со смещением на 10 точек по обеим осям. Это вызвано тем фактом, что система передает программе данные о положении указателя мыши в пространстве рабочего стола. Проверка активной зоны использует координаты (340, 10) и не беспокоится о смещении клиентской области окна. Это вызывает проблему, поскольку активаня зона на рис. 6.25 в действительности расположена по координатам (350, 20). Для решения этой проблемы мы вычисляем в каком месте рабочего стола расположено окно и вычитаем его координаты из координат указателя мыши в момент щелчка. В результате мы получаем координаты в клиентской области не зависящие от местоположения окна. На рис. 6.25 корректировка значений снова вернет нас в точку (340, 10), поскольку вычисления будут выглядеть следующим образом: (350–10, 20–10).

Вычисление местоположения указателя мыши

Следующим этапом работы кода является вычисление текущего местоположения указателя мыши. Это делается с помощью вызова предоставляемой Windows функции GetCursorPos(). Эта функция проверяет текущее местоположение указателя мыши и сохраняет полученный результат в структуре POINT. Структура POINT содержит координаты указателя мыши по осям X и Y.

ПРИМЕЧАНИЕ
Функция GetCursorPos() не является частью DirectX SDK. Это внутренняя функция Windows. Для работы с мышью я предпочитаю применять стандартные функцииI Windows, поскольку они достаточно быстрые и значительно проще в использовании, чем вызовы DirectX.

Следующие строки кода корректируют полученные координаты указателя мыши. Это достаточно простое вычисление — берутся координаты указателя мыши и из них вычитаются координаты окна и смещение клиентской области окна. В результате будут получены скорректированные координаты, которые можно использовать для проверки попадания по активной зоне.

Функция MouseZoneClass::bCheckZones()

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

bool MouseZoneClass::bCheckZones(
   short shX,
   short shY,
   char *szZoneHit,
   bool bLeftDown,
   bool bRightDown)

В первых двух параметрах передаются скорректированные координаты указателя мыши по осям X и Y соответственно. Мы передадим координаты, которые вычислили чуть раньше.

Следующим параметром является символьный буфер, куда будет помещено название активированной пользователем зоны. Название копируется в буфер если найдена зона с требуемыми координатами и выполнены условия ее активации.

В последних двух параметрах функции передается состояние кнопок мыши. Если кнопка нажата, передается 1, а если отпущена — 0. Я передаю здесь переменные g_bLeftButton и g_bRightButton.

Для примера предположим, что пользователь запустил программу и щелкнул по рисунку на титульном экране. В результате была активирована зона TITLE_SCREEN. Символьный массив szZoneHit теперь содержит название активированной зоны. Что делать дальше? Вы устанавливаете переменную g_iCurrentScreen, чтобы указать, что теперь пользователь находится в главном меню, а затем устанавливаете активные зоны для главного меню.

Переменная g_iCurrentScreen хранит состояние меню. Это необходимо, чтобы отслеживать местоположение пользователя в мире интерфейсов игры. Число 0 означает, что пользователь находится на титульном экране. Число 1 означает, что пользователь находится в главном меню. В приведенном ниже списке перечислены все используемые в рассматриваемом примере значения.

0 Титульный экран
1 Главное меню
2 Экран завершения игры
3 Выход из программы
7 Меню Options

Чтобы переместить пользователя от одного меню к другому вы должны изменить значение переменной, содержащей номер текущего экрана, а затем установить активные зоны для нового меню. Теперь вы можете завершить перемещение пользователя, отобразив новый экран. Вот и все, что относится к навигации по меню! Взгляните на оставшуюся часть функции vCheckInput() и посмотрите, сможете ли вы следовать за ее логикой. Завершив это дело, взгляните на рис. 6.26, где изображена вся рассмотренная к данному моменту структура меню.


Рис. 6.26. Титульный экран, экран завершения, главное меню и меню параметров

Рис. 6.26. Титульный экран, экран завершения, главное меню и меню параметров


Обнаружение сообщений кнопок мыши

Работа функции vCheckInput() зависит от состояния кнопок мыши. К счастью, определение состояния кнопок мыши — очень простой процесс. Взгляните на функцию fnMessageProcessor() из программы. Вот как выглядит ее фрагмент:

switch(msg)
   {
      case WM_LBUTTONDOWN:
         g_bLeftButton = 1;
         break;
      case WM_LBUTTONUP:
         g_bLeftButton = 0;
         break;
      case WM_RBUTTONDOWN:
         g_bRightButton = 1;
         break;
      case WM_RBUTTONUP:
         g_bRightButton = 0;
         break;
      case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
      default:
         break;
   }

Первые четыре инструкции case проверяют системные сообщения мыши. Первое из них, WM_LBUTTONDOWN, позволяет узнать что нажата левая кнопка мыши. Следующее, WM_LBUTTONUP, сообщает вам, что левая кнопка мыши была отпущена. То же самое справедливо и для правой кнопки мыши, только используются сообщения WM_RBUTTONDOWN и WM_RBUTTONUP.

Простейший способ хранить состояние мыши — воспользоваться глобальными переменными. Для хранения состояния двух кнопок мыши я использую переменные g_bRightButton и g_bLeftButton. Что может быть проще?

Выход из программы

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

if(g_iCurrentScreen == 3) {
   break;
}

Этот код я использую для выхода пользователя из программы. Нельзя выкинуть пользователя из программы, как только он щелкнул по завершающему экрану. Сперва надо завершить главный цикл обработки сообщений. Лучший способ обнаружения завершения программы состоит в проверке значения специальной переменной. Я проверяю не равно ли значение переменной g_iCurrentScreen числу 3. Это сообщает мне, что пользователь закончил играть и необходимо завершить работу программы. В реальной игре может использоваться более трудоемкий метод, но для простого примера подходит и рассмотренный простой способ.

Динамическое отображение меню

Мы почти закончили исследование кода программы. К данному моменту вы уже знаете как установить активные зоны, определить местоположение указателя мыши, обрабатывать сообщения кнопок мыши и завершить работу пользователя с программой. Осталось узнать только одну вещь — как динамически отображать экраны меню. Это необходимо, поскольку графика меню меняется на каждом экране. Сосредоточьтесь на функции vRender() чтобы увидеть как это делается.

Код этой функции выглядит очень похоже на код предыдущей программы. Главное отличие заключается в появившейся проверке значения переменной g_iCurrentScreen. Цикл визуализации проверяет текущее значение переменной, чтобы определить, какое изображение выводить на экран. Как видите, фоновое оформление каждого меню одно и то же; меняется только изображение в центре экрана. Если вы взглянете на каждый логический блок, то увидите, что отличается только последний вызов функции рисования. Вы, если пожелаете, можете полностью изменить графику. Я же хотел оставить пример максимально простым.

Вот и все, что требуется для простого динамического отображения меню. Главная хитрость — проверять состояние программы и отображать соответствующую графику. Правда, просто?

Заголовочный файл MouseZoneClass.h

Итак, вы увидели класс активных зон в действии, и теперь настало время разобраться как он работает. Класс активных зон состоит из двух файлов: MouseZoneClass.h и MouseZoneClass.cpp. Откройте заголовочный файл MouseZoneClass.h и следуйте дальше.

Структура данных stHotSpot

Первый заслуживающий внимания элемент заголовочного файла класса — структура данных stHotSpot. Вот как выглядит ее код:

struct stHotSpot
{
   short m_shZoneXPos;
   short m_shZoneYPos;
   short m_shZoneWidth;
   short m_shZoneHeight;
   bool m_bActive;
   short m_shClickType;
   char *m_szZoneName;
};

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


Рис. 6.27. Взаимосвязь между структурой данных и активной зоной

Рис. 6.27. Взаимосвязь между структурой данных и активной зоной


Из рис. 6.27 видно, что переменные m_shZoneXPos и m_shZoneYPos задают координаты верхнего левого угла зоны. Член данных m_shZoneWidth определяет ширину зоны, а m_shZoneHeight — ее высоту.

Член данных m_bActive предназначен для внутреннего использования классом активных зон и определяет используется ли зона в данный момент. Он используется, когда при создании новой зоны происходит поиск свободного места для ее данных.

Член данных m_shClickType хранит информацию о том, какой именно тип щелчка активирует данную зону. Возможные значения перечнслены в таблице 6.10.

Переменная m_szZoneName используется для хранения имени активной зоны.

Закрытые члены данных класса MouseZoneClass

Далее в заголовочном файле расположено объявление класса MouseZoneClass. Ниже приведен его код:

class MouseZoneClass
{
   private:
      int       m_iMaxZones;
      stHotSpot *m_HotSpots;

   public:
      MouseZoneClass(void);
      ~MouseZoneClass(void);
      void vInitialize(int iMaxZones);
      void vFreeZones(void);
      int iAddZone(char *szZoneName, short shX, short shY,
                   short shWidth, short shHeight, short shClickType);
      int iRemoveZone(char *szZoneName);
      bool bCheckZones(short shX, short shY, char *szZoneHit,
                       bool bLeftDown, bool bRightDown);
};

Есть только две закрытые переменные класса — m_iMaxZones и m_HotSpots. Переменная m_iMaxZones хранит количество активных зон, для которых выделена память. Это очень важные сведения, поскольку количество используемых зон может изменяться. Переменная m_HotSpots является указателем на массив структур данных stHotSpot, представляющих реально существующие активные зоны.

Функции класса MouseZoneClass

Первые две объявленные функции — это конструктор и деструктор класса. В их прототипах нет ничего необычного; это просто обычная рутина программирования.

Следом за ними идет функция vInitialize(). Она получает единственный параметр, определяющий для скольких активных зон будет выделена память.

Функция vFreeZones() очищает всю память, выделенную для хранения данных активных зон и сбрасывает внутренние переменные.

Функция iAddZone() активирует новую зону в массиве m_HotSpots. Если свободная зона доступна, функция возвратит ее номер. Если же свободных зон больше нет, функция возвращает –1.

Функция iRemoveZone() делает указанную зону неактивной.

Функция bCheckZones() получает набор координат и состояние кнопок мыши, после чего определяет активирована ли какая-нибудь зона. Функция возвращает имя активной зоны. Если никакая зона не активирована, функция возвращает NULL.

Файл MouseZoneClass.cpp

Файл MouseZoneClass.cpp содержит код функций, объявленных в заголовочном файле. Откройте файл MouseZoneClass.cpp и пойдемте дальше.

Функция MouseZoneClass::MouseZoneClass()

Первой из функций является конструктор класса. Его код выглядит следующим образом:

MouseZoneClass::MouseZoneClass(void)
{
   // Количество зон равно нулю
   m_iMaxZones = 0;
}

Эта функция исключительно проста. Она только лишь устанавливает количество активных зон, для которых выделена память равным нулю. Это необходимо для того, чтобы деструктор не пытался освободить память, которая не была выделена.

Функция MouseZoneClass::~MouseZoneClass()

Следом идет деструктор класса, код которого выглядит так:

MouseZoneClass::~MouseZoneClass(void)
{
   // Очистка выделенных зон
   vFreeZones();
}

Единственное действие деструктора — вызов функции vFreeZones(). Поскольку деструктор вызывается когда класс покидает область видимости, необходимо гарантировать освобождение выделенной памяти до выхода из него. Именно по этой причине я и включил в код вызов функции освобождающей активные зоны.

Функция MouseZoneClass::vInitialize()

Ах, наконец-то мы добрались до функции чей код занимает больше двух строк. Она получает максимальное количество активных зон, которые вы намереваетесь использовать, выделяет память для хранения их данных и присваивает начальные значения. Вот ее код:

void MouseZoneClass::vInitialize(int iMaxZones)
{
   int i;
   // Очистка существующих зон
   vFreeZones();
   // Сохранение максимального количества зон
   m_iMaxZones = iMaxZones;
   // Выделение памяти для указанного количества зон
   m_HotSpots = new stHotSpot[m_iMaxZones];
   // Очистка данных зоны
   for(i = 0; i < m_iMaxZones; i++) {
      m_HotSpots[i].m_shZoneXPos = 0;
      m_HotSpots[i].m_shZoneYPos = 0;
      m_HotSpots[i].m_shZoneWidth = 0;
      m_HotSpots[i].m_shZoneHeight = 0;
      m_HotSpots[i].m_shClickType = 0;
      m_HotSpots[i].m_bActive = 0;
      m_HotSpots[i].m_szZoneName = new char[64];
      memset(m_HotSpots[i].m_szZoneName, 0x00, 64);
   }
}

Сначала выполняется вызов функции vFreeZones(), которая освобождает любую выделенную ранее память.

Затем я присваиваю значение единственного параметра функции внутренней переменной класса m_iMaxZones. Эта переменная используется внутри класса и очень важна, поскольку предохраняет внутренние циклы функций от выхода за пределы доступной памяти.

Теперь выделяется память для хранения массива структур данных горячих точек. Для каждой активной зоны создается одна структура данных.

В цикле перебираются все созданные активные зоны и всем их параметрам присваиваются нулевые значения. Кроме того, в цикле выделяется 64 байта памяти для хранения имени зоны. Это максимальная длина имени зоны. (Я выбрал это значение абсолютно произвольно; если вам нужны более длинные имена зон, вы вольны изменить его.) После выделения памяти для имени зоны, я заполняю ее нулевыми значениями.

Функция MouseZoneClass::vFreeZones()

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

void MouseZoneClass::vFreeZones(void)
{
   int i;

   if(m_iMaxZones) {
      // Освобождение имен
      for(i = 0; i < m_iMaxZones; i++) {
         delete [] m_HotSpots[i].m_szZoneName;
      }
      // Освобождение горячих точек
      delete [] m_HotSpots;
      m_iMaxZones = 0;
   }
}

Сначала функция проверяет значение члена данных m_iMaxZones, чтобы убедиться, что для горячих точек была выделена какая-то память. Если нет, функция немедленно завершается не выполняя никаких действий. Если же активные зоны были созданы, функция перебирает их по одной, освобождая выделенную для хранения их имен память. Сразу после этого функция удаляет данные всех активных зон. И, перед завершением работы функция устанавливает количество доступных зон равным 0.

Функция MouseZoneClass::iAddZone()

Эта функция активирует зону и записывает ее данные в доступный элемент массива структур данных горячих точек. Перед вызовом этой функции необходимо вызвать функцию vInitialize(). Если свободных элементов в массиве нет, функция возвращает –1. Код функции выглядит следующим образом:

int MouseZoneClass::iAddZone(char *szZoneName,
                 short shX, short shY,
                 short shWidth, short shHeight,
                 short shClickType)
{
   int i;

   for(i = 0; i < m_iMaxZones; i++) {
      // Ищем неиспользуемую зону
      if(m_HotSpots[i].m_bActive == 0) {
         m_HotSpots[i].m_shZoneXPos = shX;
         m_HotSpots[i].m_shZoneYPos = shY;
         m_HotSpots[i].m_shZoneWidth = shWidth;
         m_HotSpots[i].m_shZoneHeight = shHeight;
         m_HotSpots[i].m_shClickType = shClickType;
         // Активируем горячую точку
         m_HotSpots[i].m_bActive = 1;
         // Сохраняем имя
         strcpy(m_HotSpots[i].m_szZoneName, szZoneName);
         return(i);
      }
   }
   // Нет свободных зон, возвращаем -1 (ошибка)
   return(-1);
}

Функция начинается с цикла, перебирающего все зоны для которых выделена память. Если найдена свободная зона, ее данные устанавливаются в соответствии с параметрами функции, после чего зона активируется. В этом случае вызывающей программе возвращается номер зоны. Если свободных зон не найдено, функция возвращает –1.

Функция MouseZoneClass::iRemoveZone()

Данная функция отключает активную зону. Вот ее код:

int MouseZoneClass::iRemoveZone(char *szZoneName)
{
   int i;

   for(i = 0; i < m_iMaxZones; i++) {
      // Проверим, активна ли зона
      if(m_HotSpots[i].m_bActive == 1) {
         // Проверка соответствия имени зоны
         if(!stricmp(m_HotSpots[i].m_szZoneName, szZoneName)) {
            // Деактивация
            m_HotSpots[i].m_bActive = 0;
            return(1);
         }
      }
   }
   return(0);
}

Функция перебирает в цикле все зоны, для которых выделена память и сравнивает имя каждой из них с переданным параметром. Если имя найдено, зона деактивируется путем установки переменной m_bActive в 0. Чтобы сообщить об успещном завершении функция возвращает 1.

Если зона с указанным именем не найдена, функция сообщает об ошибке, возвращая 0.

Функция MouseZoneClass::bCheckZones()

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

bool MouseZoneClass::bCheckZones(short shX, short shY, char *szZoneHit, bool
                                 bLeftDown, bool bRightDown)
{
   int i;
   for(i = (m_iMaxZones-1); i >= 0; i--) {
      // Проверим активна ли зона
      if(m_HotSpots[i].m_bActive == 1) {
         // Соответствует ли состояние кнопок требуемому?
         if((bLeftDown && m_HotSpots[i].m_shClickType == 0) ||
            (bRightDown && m_HotSpots[i].m_shClickType == 1) ||
            ((bRightDown || bLeftDown) && m_HotSpots[i].m_shClickType == 2) ||
            ((!bRightDown && !bLeftDown) && m_HotSpots[i].m_shClickType == 3)) {
            // Проверка координат по горизонтали
            if(m_HotSpots[i].m_shZoneXPos <= shX {
               // Проверка координат по вертикали
               if(m_HotSpots[i].m_shZoneYPos <= shY) {
                  // Попали ли в зону заданной ширины?
                  if((m_HotSpots[i].m_shZoneXPos + m_HotSpots[i].m_shZoneWidth)
                                    >= shX) {
                     // Попали ли в зону указанной высоты?
                     if((m_HotSpots[i].m_shZoneYPos + m_HotSpots[i]
                                  .m_shZoneHeight) >= shY) {
                        // Устанавливаем указатель на имя зоны
                        strcpy(szZoneHit, m_HotSpots[i].m_szZoneName);
                        // Возвращаем 1 (попадание)
                        return(1);
                     }
                  }
               }
            }
         }
      }
   }
   // Возвращаем 0 (нет попадания)
   return(0);
}

Функция начинается с цикла, перебирающего активные зоны для которых выделена память в обратном порядке. Я делаю это для того, чтобы зона, добавленная последней была выбрана первой. Благодаря этому вы можете создавать слои из зон. Зона добавленная поверх других сработает раньше, чем зоны добавленные ранее нее.

Сперва в цикле выполняется проверка включена ли зона. Если зона включена, мы переходим к проверке соответствия текущего состояния кнопок мыши типу щелчка, активирующего зону.

Если зона активируется щелчком левой кнопкой мыши, код проверяет равен ли тип щелчка 0 и равно ли 1 значение переменной bLeftDown.

Если зона активируется щелчком правой кнопкой мыши, код проверяет равен ли тип щелчка 1 и равно ли 1 значение переменной bRightDown.

Если зона может быть активирована щелчком любой кнопки мыши, код проверяет равен ли тип щелчка 2 и равно ли 1 значение переменной bLeftDown или bRightDown.

Если зона активируется, когда на нее наведен указатель мыши, а ни одна из кнопок не нажата, код проверяет равен ли тип щелчка 3 и равно ли 0 значение переменных bLeftDown и bRightDown.

Если какое-либо из перечисленных выше правил выполнено, код копирует имя активной зоны в буфер и возвращает 1, сигнализируя об успехе. Если же для всех зон ни одно из правил не выполнено, функция возвращает 0, сообщая что ни одна из активных зон не сработала.

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

Подсветка пунктов меню

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

Возможно, играя в такие игры как Populous или Sacrifice вы удивлялись как их создатели заставляют элементы меню изменяться, когда на них наведен указатель мыши. Теперь вам не придется удивляться. В следующем примере программы я покажу простой и эффективный способ добиться такого эффекта. Загрузите программу D3D_MouseZoneHighlights и следуйте вместе со мной дальше.

Архитектура проекта D3D_MouseZoneHighlights

Проект содержит четыре уникальных файла: main.cpp, main.h, MouseZoneClass.cpp и MouseZoneClass.h. Данный пример программы является усовершенствованной версией проекта D3D_MouseZones в которой сделано не так уж и много изменений.

Для программы необходимы следующие библиотеки: d3d9.lib, dxguid.lib, d3dx9dt.lib, d3dxof.lib, comctl32.lib и winmm.lib.

Заголовочный файл Main.h

Главный заголовочный файл содержит несколько новых строк кода, необходимых для реализации подсветки пунктов меню. Перед тем, как перейти к коду, взгляните на рис. 6.28, чтобы увидеть подсветку пунктов меню в действии.


Рис. 6.28. Главное меню с подсветкой кнопки Options

Рис. 6.28. Главное меню с подсветкой кнопки Options


На рис. 6.28 показано главное меню. Главное отличие между этим рисунком и предыдущим примером программы заключается в том, что кнопка Options выделена с помощью подсветки. Кнопки меню изменяют свой цвет, когда указатель мыши проходит над ними. В этом и заключается суть подсветки зон.

Давайте вернемся к коду заголовочного файла. В приведенном ниже фрагменте кода отражены те изменения, которые были сделаны для поддержки подсветки зон:

// Переменные состояния подсветки
bool   g_bMainMenu_NewGame_Highlight = 0;
bool   g_bMainMenu_LoadGame_Highlight = 0;
bool   g_bMainMenu_SaveGame_Highlight = 0;
bool   g_bMainMenu_Options_Highlight = 0;

В рассматриваемом примере программы реализована подсветка четырех пунктов главного меню. Для этих четырех пунктов программа должна отслеживать какой именно из них в данный момент активирован. Я делаю это используя логические значения. Каждое логическое значение представляет состояние отдельного пункта меню. Если значение равно 0, соответствующая кнопка не подсвечена. Если же значение равно 1, механизм визуализации знает, что для кнопки следует включить подсветку. Весьма просто, не так ли?

Файл программы Main.cpp

Я не хочу утомлять вас, описывая код с которым вы уже знакомы, так почему бы сразу не перейти к сделанным изменениям? Итак, отправимся к функции vCheckInput(), чтобы увидеть первый набор изменений.

Как определить подсвечиваемую активную зону

Вот как выглядит код функции vCheckInput():

void vCheckInput(void)
{
   bool      bRet;
   char      szZoneHit[64];
   POINT     Point;
   RECT      rcWindowRect;
   int       iMouseX;
   int       iMouseY;

   // Проверка смещения окна
   GetWindowRect(g_hWnd, &rcWindowRect);

   // Обновить местоположение мыши
   GetCursorPos(&Point);

   // Вычисление реальных координат мыши
   iMouseX = Point.x - g_iXOffset - rcWindowRect.left;
   iMouseY = Point.y - g_iYOffset - rcWindowRect.top;

   // Проверка попадания
   bRet = MZones.bCheckZones((short)iMouseX, (short)iMouseY, szZoneHit,
                             g_bLeftButton, g_bRightButton);
   if(bRet) {
      // ЛОГИКА ТИТУЛЬНОГО ЭКРАНА
      if(g_iCurrentScreen == 0) {
         // Переход к главному меню
         if(!stricmp(szZoneHit, "TITLE_SCREEN")) {
            // Делаем главное меню текущим
            g_iCurrentScreen = 1;
            // Устанавливаем активные зоны
            vSetupMouseZones(1);
      }
      // Переход к экрану завершения игры
      else if(!stricmp(szZoneHit, "EXIT_BUTTON")) {
         // Делаем экран завершения текущим
         g_iCurrentScreen = 2;
         // Устанавливаем активные зоны
         vSetupMouseZones(2);
      }
   }
   // ЛОГИКА ГЛАВНОГО МЕНЮ
   else if(g_iCurrentScreen == 1) {
      // Выключаем подсветку всех зон
      g_bMainMenu_NewGame_Highlight = 0;
      g_bMainMenu_LoadGame_Highlight = 0;
      g_bMainMenu_SaveGame_Highlight = 0;
      g_bMainMenu_Options_Highlight = 0;
      // Переход к экрану завершения игры
      if(!stricmp(szZoneHit, "EXIT_BUTTON")) {
         // Делаем экран завершения текущим
         g_iCurrentScreen = 2;
         // Устанавливаем активные зоны
         vSetupMouseZones(2);
      }
      else if(!stricmp(szZoneHit, "MAINMENU_NEWGAME"))
      {
         // Добавьте сюда логику начала новой игры
      }
      else if(!stricmp(szZoneHit, "MAINMENU_LOADGAME"))
      {
         // Добавьте сюда логику для загрузки игры
      }
      else if(!stricmp(szZoneHit, "MAINMENU_SAVEGAME"))
      {
         // Добавьте сюда логику для записи игры
      }
      else if(!stricmp(szZoneHit, "MAINMENU_OPTIONS"))
      {
         // Деламем меню параметров текущим
         g_iCurrentScreen = 7;
         // Устанавливаем активные зоны
         vSetupMouseZones(7);
      }
      // Проверим находится ли указатель над активной зоной
      else if(!stricmp(szZoneHit, "MAINMENU_NEWGAME_H"))
      {
         // Активация подсветки
         g_bMainMenu_NewGame_Highlight = 1;
      }
      else if(!stricmp(szZoneHit, "MAINMENU_LOADGAME_H"))
      {
         // Активация подсветки
         g_bMainMenu_LoadGame_Highlight = 1;
      }
      else if(!stricmp(szZoneHit, "MAINMENU_SAVEGAME_H"))
      {
         // Активация подсветки
         g_bMainMenu_SaveGame_Highlight = 1;
      }
      else if(!stricmp(szZoneHit, "MAINMENU_OPTIONS_H"))
      {
         // Активация подсветки
         g_bMainMenu_Options_Highlight = 1;
      }
   }
   // ЛОГИКА ЭКРАНА ЗАВЕРШЕНИЯ
   else if(g_iCurrentScreen == 2) {
      // Выходим из программы, если пользователь нажал кнопку мыши
      if(!stricmp(szZoneHit, "EXIT_SCREEN")) {
         // Флаг, сообщающий WinMain() о завершении программы
         g_iCurrentScreen = 3;
      }
   }
   // ЛОГИКА МЕНЮ ПАРАМЕТРОВ
   else if(g_iCurrentScreen == 7) {
      // Переходим к завершающему экрану
      if(!stricmp(szZoneHit, "EXIT_BUTTON")) {
         // Делаем завершающий экран текущим
         g_iCurrentScreen = 2;
         // Устанавливаем активные зоны
         vSetupMouseZones(2);
      }
      // Возврат к главному меню
         else if(!stricmp(szZoneHit, "OPTIONS_BACK")) {
            // Делаем главное меню текущим
            g_iCurrentScreen = 1;
            // Устанавливаем активные зоны
            vSetupMouseZones(1);
         }
      }
   }
}

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

Затем код проверяет не активна ли какая-либо из подсвечиваемых зон. Подсвечиваемые зоны активируются когда ни одна из кнопок мыши не нажата; поэтому достаточно только навести на зону указатель мыши. Если подсвечиваемая зона активна, код присваивает соответствующей логической переменной значение true. Это сообщает функции визуализации, что надо вывести подсвеченное изображение данной кнопки вместо обычного.

Как создать подсвечиваемую активную зону

В функции vCheckInput() вы проверяете активацию подсвечиваемых зон, но как они перед этим были созданы? Очень просто. Вы создаете их в функции vSetupMouseZones(). Данная функция также слегка отличается от кода предыдущего примера. Вот ее измененный фрагмент:

MZones.iAddZone("MAINMENU_NEWGAME_H", 192, 64, 256, 64, 3);
MZones.iAddZone("MAINMENU_LOADGAME_H", 192, 128, 256, 64, 3);
MZones.iAddZone("MAINMENU_SAVEGAME_H", 192, 192, 256, 64, 3);
MZones.iAddZone("MAINMENU_OPTIONS_H", 192, 256, 256, 64, 3);

Эти вызовы функции iAddZone() выглядят также как и остальные за исключением параметра, определяющего тип щелчка. Я устанавливаю значение этого параметра равным 3. Это сообщает системе, что зона становится активной когда над ней расположен указатель мыши и ни одна из кнопок мыши не нажата. Вот как выполняется создание подсвечиваемых зон — достаточно только указать соответствующий тип щелчка. Кроме того, я добавил к именам зон символы _H в конце, чтобы их было легче идентифицировать в коде.

Как отобразить подсветку активной зоны

Итак, вы создали переменные состояния подсвечиваемых активных зон и сами зоны. Теперь пришло время отобразить эти зоны в функции vRender(). К счастью для вашей психики и рассудка по сравнению с предыдущим примером функция изменилась не слишком сильно. Вот самые главные изменения:

else if(g_iCurrentScreen == 1) {
   // Отображение главного меню
   vDrawInterfaceObject(0, 0, 256.0f, 256.0f, 0);
   vDrawInterfaceObject(256, 0, 256.0f, 256.0f, 1);
   vDrawInterfaceObject(512, 0, 256.0f, 256.0f, 2);
   vDrawInterfaceObject(0, 256, 256.0f, 256.0f, 3);
   vDrawInterfaceObject(256, 256, 256.0f, 256.0f, 4);
   vDrawInterfaceObject(512, 256, 256.0f, 256.0f, 5);
   // Отображение подсвеченных зон, если они есть
   // либо обычного меню без подсветки
   if(g_bMainMenu_NewGame_Highlight) {
      vDrawInterfaceObject(192, 64, 256.0f, 256.0f, 10);
   }
   else if(g_bMainMenu_LoadGame_Highlight) {
      vDrawInterfaceObject(192, 64, 256.0f, 256.0f, 11);
   }
   else if(g_bMainMenu_SaveGame_Highlight) {
      vDrawInterfaceObject(192, 64, 256.0f, 256.0f, 12);
   }
   else if(g_bMainMenu_Options_Highlight) {
      vDrawInterfaceObject(192, 64, 256.0f, 256.0f, 13);
   }
   else {
      // Меню без подсветки
      vDrawInterfaceObject(192, 64, 256.0f, 256.0f, 7);
   }
}

Изменения выделены полужирным курсивом. Вместо того, чтобы рисовать при каждом вызове одно и то же изображение меню, программа теперь проверяет значения переменных состояния подсветки, чтобы определить какое именно изображение меню выводить на экран. Я создал четыре дополнительных изображения главного меню для представления всех возможных состояний подсветки. Все эти изображения представлены на рис. 6.29.


Рис. 6.29. Изображения меню для реализации подсветки

Рис. 6.29. Изображения меню для реализации подсветки


На рис. 6.29 представлены пять изображений меню. На первом представлено меню в котором не подсвечен ни один пункт. Следующие четыре изображения представляют меню с различными состояниями подсветки. Каждое состояние характеризуется собственным подсвеченным пунктом меню. Пусть вас не смущает термин «подсветка». При желании вы можете полностью изменить изображение пункта меню.

Я отображаю на экране первое изображение с рис. 6.29, если ни одна из подсвечиваемых зон не активна. Я отображаю второе изображение, если подсвечивается пункт меню New Game, третье изображение если подсвечивается пункт меню Load Game и четвертое изображение если подсвечивается пункт меню Save Game. Пятое изображение показывается когда должен быть подсвечен пункт меню Options.

Это совсем не трудно, не так ли?


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz