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

Ввод текста в игре

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

Для начала взгляните на рис. 9.7, где изображен пример ввода текста в игре.


Рис. 9.7. Пример ввода текста в игре

Рис. 9.7. Пример ввода текста в игре


На рис. 9.7 вы видите уже ставший знакомым интерфейс игры Battle Armor с полем ввода текста в центре экрана. В это поле вводится имя игрока. Обратите внимание, что я ввел в это поле строку «Lost Logic» и курсор находится в конце введенного текста. Все графические элементы должны быть вручную обработаны в вашей игре, так что читайте дальше, чтобы выяснить как это происходит.

Откройте проект с именем D3D_InputBox чтобы увидеть код, создающий окно, изображенное на рис. 9.7. Этот проект является вариантом рассмотренного ранее проекта игрового интерейса, так что большая часть кода должна выглядеть знакомо. После загрузки проекта взгляните на рис. 9.8, где изображен ход выполнения программы. На рис. 9.8 видно, что программа инициализирует DirectInput, клавиатуру, Direct3D, объекты интерфейса и активные зоны. После завершения цинициализации программа входит в цикл обработки сообщений где проверяет поступающие данные и отображает графику.


Рис. 9.8. Ход выполнения программы D3D_InputBox

Рис. 9.8. Ход выполнения программы D3D_InputBox


Навигация по меню

Вы можете вспомнить, как в главе 6 навигация по меню была реализована в функции проверки ввода. Внутри нее программа смотрела, активирована ли какая-нибудь из зон меню. Если да, код определял какой из пунктов меню выбран и выполнял соответствующие действия. Взгляните на приведенный ниже код:

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);
   }
}

Приведенный код выполняется когда активен титульный экран. Если пользователь выбирает активную зону TITLE_SCREEN, программа делает активным экран главного меню и устанавливает активные зоны для него. Если выбрана кнопка Exit, код активирует экран выхода из игры и устанавливает активные зоны для него. Такие же действия выполняются в коде для каждого доступного пункта меню. Ход выполнения функции проверки ввода показан на рис. 9.9.


Рис. 9.9. Структура функции проверки ввода

Рис. 9.9. Структура функции проверки ввода


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

Активация ввода текста

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

// Экран начала новой игры
else if(iMenu == 4) {
   MZones.vFreeZones();
   MZones.vInitialize(1);
   MZones.iAddZone("EXIT_BUTTON", 587, 0, 53, 24, 0);
   //
   // Установка поля ввода текста
   //
   // Установка позиции курсора
   g_shTextInputXPos = 200;
   g_shTextInputYPos = 196;
   // Очистка текста
   memset(g_szTextInputBuffer, 0x00, 64);
   // Установка позиции ввода данных
   g_shTextInputPosition = 0;
   // Установка активного поля данных
   g_iTextInputFieldID = GAMEINPUT_NAME;
   // Установка флага активности поля ввода
   g_bTextInputActive = 1;
   // Установка таймера мерцания курсора
   g_dwTextInputTimer = 0;
   // Установка состояния мерцания курсора
   g_bTextInputCursorFlash = 0;
   // Установка максимальной длинны текста: 20 символов
   g_shTextMaxSize = 20;
}

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

Таблица 9.2. Глобальные переменные, управляющие вводом текста

Переменная Описание
g_shTextInputXPos Координата X текстового поля ввода.
g_shTextInputYPos Координата Y текстового поля ввода.
g_szTextInputBuffer Хранит содержимое текстового поля.
g_shTextInputPosition Активная позиция в текстовом поле.
g_iTextInputFieldID Следит, какое текстовое поле активно.
g_bTextInputActive Сообщает системе, что текстовый ввод включен.
g_dwTextInputTimer Таймер для анимации курсора в активном текстовом поле.
g_bTextInputCursorFlash Определяет включен курсор или выключен во время мерцания.
g_shTextMaxSize Максимальное количество символов в буфере.


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

Затем устанавливается позиция ввода текста. Я присваиваю этой переменной 0, чтобы игрок вводил текст с начала буфера имени.

Далее идентификатору поля присваивается значение GAMEINPUT_NAME. В заголовочном файле main.h у меня есть ряд констант, соответствующих присутствующим в игре текстовым полям. Вы не обязаны использовать константы, но мне они помогают следить за тем, что происходит в программе.

Потом я присваиваю полю g_bTextInputActive значение 1. Оно сообщает программе, что текстовое поле активно и ожидает ввод. Это важно знать, так как программа должна добавлять текст в поле и отображать его.

После того, как текстовое поле активизировано, я присваиваю 0 переменной g_dwTextInputTimer. Данный таймер отвечает за анимацию курсора. Следующая переменная, g_bTextInputCursorFlash, определяет включен курсор или выключен. Когда таймер курсора заканчивает отсчет она меняет свое состояние.

Последнее, что требуется сделать для инициализации текстового ввода — задать максимальное количество символов в имени игрока. Я делаю это присваивая переменной g_shTextMaxSize значение 20.

Обработка текстового ввода

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

// ВВОД С КЛАВИАТУРЫ
// Чтение из буфера клавиатуры
int iResult = iReadKeyboard();
// Проверяем, сколько нажатий на клавиши возвращено
if(iResult) {
   // Цикл перебора полученных данных
   for(int i = 0; i < iResult; i++) {
      // Выход из программы, если нажата клавиша ESC
      if(diks[DIK_ESCAPE][i]) {
         PostQuitMessage(0);
      }
      // ЛОГИКА РАБОТЫ ТЕКСТОВОГО ВВОДА
      if(g_bTextInputActive) {
         // Не сохранять текст, если для него нет места в буфере
         if(g_shTextInputPosition < g_shTextMaxSize) {
            // Сохраняем отпущенные клавиши
            for(int j = 32; j < 123; j++) {
               // Проверяем, что введен допустимый символ
               if((j > 96)
                  || (j == 32)
                  || (j > 47 && j < 58)) {
                  // Проверяем, что клавиша отпущена
                  if(ascKeys[j][i]) {
                     if(g_bShift) {
                        g_szTextInputBuffer[
                        g_shTextInputPosition]
                        = toupper(j);
                     }
                     else {
                        g_szTextInputBuffer[g_shTextInputPosition] = j;
                     }
                     g_shTextInputPosition++;
                  }
               }
            }
         }
         // Проверяем не нажата ли клавиша удаления символа
         if(diks[DIK_BACK][i]) {
            // Проверяем введен ли какой-нибудь текст
            if(g_shTextInputPosition) {
               // Удаляем последний символ
               g_szTextInputBuffer[g_shTextInputPosition - 1] = '\0';
               // Сдвигаем курсор назад
               g_shTextInputPosition--;
            }
         }
         // Проверяем не нажата ли клавиша ENTER
         if(diks[DIK_RETURN][i]) {
            // Завершаем ввод имени
            g_bTextInputActive = 0;
            // АКТИВАЦИЯ НОВОЙ ИГРЫ
            if(g_iTextInputFieldID == GAMEINPUT_NAME) {
               // Делаем текущим основной игровой экран
               g_iCurrentScreen = 5;
               // Устанавливаем активные зоны
               vSetupMouseZones(5);
            }
            break;
         }
      }
   }
}

Это достаточно большой фрагмент кода, так что взгляните на рис. 9.10.


Рис. 9.10. Блок-схема ввода данных с клавиатуры

Рис. 9.10. Блок-схема ввода данных с клавиатуры


На рис. 9.10 показана логика, необходимая для получения данных от клавиатуры и их помещения в текстовое поле. Начнем сверху: программа вызывает функцию чтения с клавиатуры, чтобы проверить есть ли какие-либо ожидающие обработки данные. Если есть, система в цикле перебирает полученные данные и выполняет ряд проверок. Сперва проверяется не была ли нажата клавиша Esc. Если да, программа помещает в очередь сообщение о выходе и завершает работу. Если нет, работа продолжается и выполняется проверка активности текстового поля. Если текстовое поле активно, система проверяет осталось ли в текстовом поле свободное место для ввода очередного символа. Если свободное место обнаружено, программа в цикле перебирает все клавиши клавиатуры и проверяет состояние каждой из них. Если проверяемая на данной итерации цикла клавиша является алфавитно-цифровой или пробелом, программа проверяет, была ли данная клавиша отпущена. Если клавиша была отпущена, проверяется нажата ли клавиша Shift. Если да, программа помещает в буфер имени игрока символ данной клавиши в верхнем регистре. Если клавиша Shift не нажата, в буфер помещается полученный по умолчанию символ. Данный процесс повторяется, пока не будут обработаны все состояния клавиш, находящиеся в буфере DirectInput.

Кроме того, на рис. 9.10 изображены проверки нажатия клавиш Backspace и Enter. Если игрок нажимает клавишу Backspace, программа удаляет последний символ в буфере имени игрока и передвигает курсор на одну позицию назад. Если нажата клавиша Enter, программа переходик к экрану новой игры и деактивирует текстовый ввод.

Отображение введенного текста

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

// Отображение экрана новой игры
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);
// Поле ввода
vDrawInterfaceObject(192, 64, 256.0f, 256.0f, 14);
// Отображаем курсор, если ввод активен
if(g_bTextInputActive) {
   // Обновление состояния мерцания курсора
   if(timeGetTime() > g_dwTextInputTimer) {
      if(g_bTextInputCursorFlash) {
         g_bTextInputCursorFlash = 0;
         g_dwTextInputTimer = timeGetTime() + 250;
      }
      else {
          g_bTextInputCursorFlash = 1;
          g_dwTextInputTimer = timeGetTime() + 250;
      }
   }
   // Рисуем курсор, если он не скрыт
   if(g_bTextInputCursorFlash) {
      vDrawInterfaceObject(g_shTextInputXPos + g_shTextInputPosition * 8,
                           g_shTextInputYPos, 4.0f, 16.0f, 15);
   }
}
// Отображение текста
// Создаем прямоугольник для текста
RECT rectText = { g_shTextInputXPos,
                  g_shTextInputYPos,
                  g_shTextInputXPos + (g_shTextMaxSize * 8),
                  g_shTextInputYPos + 20 };
// Выводим текст
pD3DXFont->DrawText(g_szTextInputBuffer, -1, &rectText,
                       DT_LEFT, D3DCOLOR_RGBA(255, 255, 255, 255));

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

Рис. 9.11. Ход выполнения процедуры отображения текста

Рис. 9.11. Ход выполнения процедуры отображения текста


На рис 9.11 видно как функция визуализации проверяет номер текущего экрана, чтобы выяснить что именно должно быть отображено. Если активен экран номер четыре, она переходит к визуализации текстового поля ввода. При этом сначала функция рисует основные элементы интерфейса. Затем она выводит изображение текстового поля ввода. Это простая текстура, которую я создал в Photoshop. После этого программа проверяет активен ли текстовый ввод. Если да, то проверяется значение таймера мерцания курсора. Если таймер завершил отсчет заданного временного промежутка, код проверяет скрыт курсор или нет и меняет его состояние на противоположное. Вернувшись к визуализации код отображает курсор, если он не скрыт. И последняя вешь, которую делает код, — отображение введенного имени игрока. Обратите внимание, что код отображает имя игрока независимо от активности текстового ввода. Даже если игрок не вводит текст, введенное имя игрока должно быть видно ему.

Интерфейс шрифта

Возможно, рассматривая код визуализации вы заметили, что для отображения текста на экране я использую объект с именем pD3DXFont. Это экземпляр предоставляемого DirectX интерфейса ID3DXFont. Данный интерфейс очень полезен, так как выполняет все необходимое для отображения шрифтов в Direct3D. Вам надо лишь указать дескриптор шрифта и выводимый текст. Это действительно просто! Если вы взглянете на функцию инициализации объектов интерфейсов, то увидите следующий код:

// Шрифт текста
hFont = CreateFont(16, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0,
                    PROOF_QUALITY, 0, "fixedsys");
D3DXCreateFont(g_pd3dDevice, hFont, &pD3DXFont);

В первой строке вызывается системная функция CreateFont(). Она является частью системы GDI Windows и создаеет дескриптор шрифта, получая в качестве параметров имя шрифта, его размер и ряд других атрибутов. Подробные сведения об этой функции вы найдете в справочнике MSDN.

После того, как вы получили дескриптор шрифта, остается только вызвать функцию D3DXCreateFont(). Эта функция получает указатель на устройство Direct3D, дескриптор шрифта и адрес указателя на объект ID3DXFont. В результате выполнения функции в указатель на объект ID3DXFont помещается ссылка на созданный интерфейс шрифта, который будет использоваться в дальнейшем при визуализации.

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

Вернемся к коду визуализации. Для отображения только что созданного шрифта я обращаюсь к функции интерфейса шрифта DrawText().

Функция ID3DXFont::DrawText()

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

INT DrawText(
   LPCSTR pString,
   INT Count,
   LPRECT pRect,
   DWORD Format,
   D3DCOLOR Color
);

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

Второй параметр, Count, содержит количество отображаемых символов. Я передаю в этом параметре –1, чтобы DirectX мог сам вычислить, сколько символов отображать. Если вы будете поступать так же, убедитесь, что ваша строка завершается нулевым символом!

Третий параметр, pRect, представляет собой описание прямоугольной области, сообщающее DirectX где именно следует отображать текст. В рассматриваемом примере я создаю область визуализации внутри изображения текстового поля ввода и сохраняю ее параметры в переменной rectText.

В четвертом параметре, Format, передаются флаги форматирования, сообщающие системе как выполнять визуализацию текста. В рассматриваемой программе я использую флаг DT_LEFT, указывающий системе, что выводимый текст должен выравниваться по левому краю. Существует множество других флагов. Их описание вы найдете в документации DirectX SDK.

Пятый параметр, Color, определяет цвет, используемый при визуализации. В этом параметре я использую макрос D3DCOLOR_RGBA(), позволяющий просто указать значения RGBA для шрифта.


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

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