Лучшие компьютерные игры

СОЗДАЁМ ИГРУПАНЕЛЬ ИНСТРУМЕНТОВ

Автор материала:
Ричард Псмит (Андрей Ленский)
Опубликовано в журнале
«Лучшие компьютерные игры»
№7 (32) июль 2004

Занятие второе: Двумерное мастерство

- Урри! Урри! Где у него кнопка?
(«Приключения Электроника»)


Напомним, что в прошлом номере мы начали наш новый проект - «Игра своими руками». Начиная с июньского выпуска, на компакт-диске «Лучших компьютерных игр» публикуются свежие версии пакета для создания игр, написанного специально для нашего журнала - ЛКИ-Creator, работающего с Delphi 5-7 версий.

В прошлом номере мы с вами убедились, что при помощи этого пакета за одно занятие вполне реально научиться делать несложные игры-аркады. Однако внимательные читатели, приславшие нам немало писем, обратили внимание на нехватку некоторых весьма полезных механизмов: например, поддержки мыши, реализованных через DirectX кнопок и так далее. Второе занятие мы посвятим как раз работе с этими механизмами.

Ну, а материалы нашего прошлого занятия вы можете найти на нашем компакт-диске, в специально созданном для этой цели разделе «Игра своими руками».

Подготовка к работе

Перед началом работы нам потребуется переустановить пакет ЛКИ-Creator. Дело в том, что по сравнению с прошлым разом в него внесено немало изменений и дополнений.

(Надеюсь, что Delphi у вас уже установлен; если нет, то рекомендации на эту тему читайте в нашей предыдущей статье - в июньском номере журнала или на CD этого номера или на сайте.)

Возьмите с нашего компакт-диска (раздел «Игра своими руками») файл с текстами программ и картинками и распакуйте его в каталог проектов.

Теперь вы можете скачать нужные файлы отсюда.

У нас должно получиться три подкаталога. В одном - Units - хранятся библиотеки DirectX и модули пакета ЛКИ-Creator. В другом - Project - мы будем работать; туда заблаговременно положены картинки, которые нам понадобятся, и предыдущая версия нашей аркады. В третьем - Escort - готовая программа, которая должна у нас получиться.

Теперь установим (переустановим) ЛКИ-Creator. В меню Delphi откройте пункт Component, в нем выберите Install Component. Если у вас уже был установлен этот пакет, оставайтесь на закладке Into existing package, иначе перейдите на закладку Into new package и заполните пустые строчки, как показано на рисунке (в верхней строчке проще всего выбрать файл LKI2dEngine.pas с помощью кнопки Browse, а в нижней просто запишите LKI). После чего нажмите OK и выберите Install. В верхней панели Delphi у вас должна появиться закладка LKI.

Теперь осталось только загрузить наш проект. В меню File выбираем Open, открываем файл Project\StarEscort.dpr…

Кнопки, значки, текстовые строки

Тем, кому уже приходилось работать в Delphi или, скажем, в Visual Basic, наверняка недостает возможности одним щелчком мыши, как обычно, поставить на наш игровой экран кнопки и другие визуальные элементы. Конечно, никто не запрещает поместить кнопки сбоку от DirectX-ового окна (пока мы работаем в оконном режиме), но это - не самое красивое решение, и к тому же непонятно, как оно будет работать, когда мы перейдем в full screen (я уж не говорю - в 3D).

Это важно: расставляя визуальные элементы по экрану, ставьте их так, чтобы их координаты не были равны 0.

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

Итак…

Значок - TLKIStatic

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

Это интересно: большинство других управляющих элементов - кнопки, тексты - потомки этого класса (что такое "потомок" - смотрите в микроучебнике Delphi на нашем диске). Поэтому все то, что вы научитесь делать с TLKIStatic, пригодится вам и в работе с остальными классами, описанными в этой главе.

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

Итак, открываем наш проект, на котором уже стоит экземпляр класса TLKI2dEngine - DirectX'овое двумерное окно. В закладке LKI у нас теперь есть, помимо самого движка, элементы TLKIStatic, TLKIButton, TLKIEdit и TLKIText. Поставим на панель DirectX три экземпляра TLKIStatic (в предположении, что мы собираемся дать нашему кораблику три запасных "жизни") и, отредактировав у каждого из них свойство Picture, загрузим туда кораблик из ShipMini.bmp. Включим свойство AutoSize, чтобы объект занял правильные размеры.

Мы можем работать с этими элементами так же, как и обычно в Delphi работают с визуальными компонентами: двигать их как угодно (но не вздумайте поставить их за пределами панели TLKI2dEngine), менять размеры, ставить произвольную картинку. Другими словами, все средства визуального проектирования, привычные тем, кто работал в Delphi или, скажем, Visual Basic - при нас.

Это важно: самое ценное, что все эти возможности остаются при нас, даже если мы проектируем полноэкранное приложение. Главное, чтобы размер окна TLKI2dEngine соответствовал тому разрешению экрана, которое мы намерены установить.

В длинном списке свойств объекта мы найдем три важных для нас пункта:

  • IsVisible. Это свойство определяет, показывать ли в данный момент рисунок или же нет. Его можно переключать непосредственно при работе программы.

    Это важно: у объекта есть также унаследованное от его предков свойство Visible, но им пользоваться нельзя. Дело в том, что оно определяет, надо ли рисовать объект средствами WinAPI, а не DirectX, и потому наш движок автоматически отключит его. Нам не нужен конфликт между разными типами рисования в одном и том же окне!

  • Transparent. Это свойство определяет, есть ли у нашего объекта прозрачная часть. Мы хотим, чтобы отображался только значок кораблика, но не фон - поэтому мы включаем его.
  • TransColor. Если Transparent включен, то это свойство определяет, какой именно цвет считать прозрачным. Если же выключен - то это свойство не задействуется.

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

Итак, включааем прозрачность, ставим прозрачным черный цвет, запускаем программу и убеждаемся, что все работает…

Обратите внимание, что положение значков на экране не зависит от сдвига экранных координат: куда бы мы не летали, значки застынут в тех же точках, в которые мы их поместили.

Это интересно: менять картинку в процессе работы приложения можно, но только при посредстве специальной процедуры ChangePic (ее параметр - имя файла либо объект типа TBitmap). Однако использовать это для анимации не следует из-за низкого быстродействия. Если же вы хотите изредка менять изображение - эта процедура будет вам в самый раз. Кроме того, учтите, что размеры новой картинки должны строго совпадать со старыми.

Добавляем несколько жизней

Чтобы эти значки не просто рисовались на экране, а имели какой-то смысл, нам осталось сделать совсем немного.

Во-первых, создадим целочисленную глобальную переменную Lives и в самом начале, при создании формы, приравняем ее 3. Далее, всякий раз, когда у нашего кораблика кончаются хиты, снимем одну жизнь, сделаем проверку на наличие оставшихся жизней, и, если у нас их осталось меньше 0 (все запасные кончились, и свою последнюю мы потеряли) - переходим к имевшейся ранее процедуре завершения игры. А если нет - возвращаем хиты на максимум и убираем один из значков запасных жизней. Как это делается, показано на врезке «Запасные жизни».


Запасные жизни

// Убит наш корабль

if Objects[i].ID = 1 then

  begin

   Dec(Lives);

   // Проверяем, есть ли запасные жизни

   if Lives<0 then

     begin

       GameLost := true;

       AddText('Вы погибли...',

           clRed, Engine.Width div 2 - 200, 200);

       RemoveObj(true, i);

     end

    else

     begin

      // Восстанавливаем хиты корабля

      Objects[i].Hits := 50;

      // Гасим нужный значок запасной жизни

      case Lives of

        2 : Form1.LKIStatic3.IsVisible := false;

        1 : Form1.LKIStatic2.IsVisible := false;

        0 : Form1.LKIStatic1.IsVisible := false;

        else ;

       end

     end



Кнопка - TLKIButton

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

Автоматического создания рисунка кнопки, как в стандартном интерфейсе Delphi, здесь не предусмотрено, и по очень простой причине: для каждой игры нужен свой собственный стиль кнопок, и универсальный дизайн едва ли возможен. А потому рисунок кнопки, вместе с надписью, придется создать в графическом редакторе - скажем, Adobe Photoshop.

Кнопка ставится на панель и снабжается картинкой точно так же, как и значок; сделайте это, взяв в качестве изображения файл Pause.bmp и установив черный цвет прозрачным (см. выше).

Отличается кнопка от значка в первую очередь тем, что она реагирует на нажатия. Нажимать надо, естественно, мышью. Так что нам придется зарегистрировать мышь для этого объекта. Это делается одной стандартной операцией: выбираем в окне событий (events) формы (а не DirectX-экрана!) OnMouseDown и дважды щелкаем по этому событию, так что в окне кода появляется «болванка» процедуры. Процедура эта будет (пока) состоять из единственной строки:

Engine.MouseProcess(Sender, Button, Shift, x, y);

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

Осталось сделать так, чтобы нажатие кнопки производило желаемый эффект. Написанная нами процедура создает у кнопки событие OnPressed. Стало быть, находим кнопку, делаем двойной щелчок по OnPressed и пишем код постановки на паузу и снятия с нее (см. «Пауза»).

Пауза

if Engine.FActive then 

    Engine.Pause

   else

    Engine.Unpause;

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

Если вы хотите, чтобы по кнопке было видно, когда она нажата, сделать это тоже очень просто. Для этого введите в строку свойств кнопки Alternate имя файла с картинкой «нажатой» кнопки (в нашем случае это PauseA.bmp) и включите свойство HasAlternate (установите в true). Тогда, пока на кнопку наведена мышь с нажатой левой клавишей, будет отображаться именно эта картинка. Если же вы хотите, скажем, отображать факт включения режима паузы - то вам придется воспользоваться методом ChangePic (см. описание TLKIStatic).

Текстовые компоненты

Их два - TLKIText и TLKIEdit. Как несложно догадаться, отличаются они тем, что первый - просто статический текст (который, впрочем, можно менять или скрывать), а второй допускает редактирование. В обоих случаях текст должен умещаться на одной строке.

Свойство AutoSize у этих компонентов рекомендуется отключать, а размер необходимо задать достаточно большим, чтобы текст заведомо поместился на отведенной территории. Не забудем также о свойстве Background - оно определяет фон, на котором печатается текст, если, конечно, не включено свойство Transparent.

Это интересно: в нашей программе для отображения текста есть объект TLKITextSurface. Этот класс на сегодняшний день можно считать устаревшим, но пока он оставлен для целей совместимости. Впоследствии он будет убран из пакета. Рекомендуется пользоваться вместо него TLKIText.

Эти компоненты - тоже потомки TLKIStatic, так что все, что сказано о нем, верно и для них. Кроме того, у них есть поле Text, в котором хранится строка - статичная или редактируемая.

Шрифт

Работа со шрифтом здесь отличается от стандарта Delphi. Все шрифты, используемые в окне DirectX, со всеми их свойствами следует сперва объявить.при помощи процедуры RegisterFont. Вот пример ее написания:

RegisterFont(1, aFont);

Здесь AFont - некий объект типа TFont, а 1 - регистрационный номер. Этот номер вы должны будете прописать в свойстве FontNumber вашего компонента. Строка с RegisterFont должна помещаться в стартовой процедуре (FormCreate), еще до вызова Engine.Init.

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

Это важно: текстовые компоненты LKI-Creator'а не работают с Unicode-шрифтами, они предоставляют только базовые 255 символов. Это сделано не по какому-то недосмотру, а с целью добиться достойной скорости отображения.

Чтобы удобно определить шрифт, вы можете поступить, например, так: задать где-нибудь на форме, в сторонке, невидимую (Visible = False) метку TLabel, определить ее шрифт, а потом написать:

RegisterFont(1, Label1.Font);

Изменение статичного текста

В TLKIText можно менять и сам текст, и шрифт. Для этого следует обратиться к параметрам Text и FontNumber. Но просто изменить их мало; надлежит после этого вызвать специальный метод (без параметров) Update. Например:

LKIText1.Text := 'Лямбда';

LKIText1.Update;

Добавлять новые шрифты по ходу работы программы нельзя - позаботьтесь о том, чтобы объявить их все заранее.

TLKIEdit

Чтобы пользователь мог что-то ввести или исправить в поле редактуры этого компонента, его надо сначала сделать активным. Это можно сделать вручную (метод SetActive) или же просто щелкнув по нему мышью во время работы программы, если он, конечно, видим. Только не забудьте разрешить использование мыши движком (см. главу «Кнопка»).

В окне редактуры работают такие клавиши: буквенно-цифровые, Shift, пробел, Backspace, Delete, стрелки влево и вправо, Home, End. Кнопки Enter и Tab заканчивают ввод и деактивируют компонент.

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

Полноэкранный режим

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

Ну что ж, давайте готовиться к серьезной работе. Устройтесь поудобнее, полноэкранность - дело серьезное…

Берем свойство Windowed нашего движка и переключаем его из true в false (в инспекторе объектов, а не в теле программы, конечно - переключения из окна в полный экран «на лету» - не то, что нам надо). Далее, устанавливаем размеры нашего движка (не окна!) в какие-нибудь стандартные значения - например, 1024 и 768 или 800 и 600.

И запускаем программу. Дело сделано без написания даже одной-единственной строчки кода!...

Это интересно: размеры окна при этом никакой роли не играют. Даже если ваша панель движка не будеть в нем полностью помещаться - никому нет до этого дела. Равно и до координат окна.

Что характерно, все, чему мы научились до сих пор, работает.

Вы можете спросить: так почему же мы с самого начала не проектировали полноэкранное приложение? А потому, что отлаживать программу стократ удобнее, когда она работает в окне. И потому на будущее вам добрый совет: делайте с программой все, что можно, в оконном варианте, и только напоследок, перед выдачей «чистового» варианта, переводите в Full Screen.

Работа с мышью

В главе «Кнопка» мы уже научились использовать для работы наших стандартных компонентов мышь. Но что, если нам нужно выбирать на экране что-то еще, кроме кнопок и окон редактуры?

Все очень просто. Нам понадобится та процедура, которая сейчас работает с мышью - TForm1.FormMouseDown.

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

После вызова MouseProcess мы можем вызывать функцию Engine.Pick. Ее единственный параметр - флажок, показывающий, интересуют ли нас все объекты или только активные. Если параметр равен true - пассивные объекты рассматриваться не будут. Таким образом, вызов:

P := Engine.Pick(Active);

76KB
«Инопланетянин» слева - это наша мышь. Недаром он нас совершенно игнорирует…

загрузит в переменную P ближайший по номеру активный объект из числа тех, в которые мы попали нашим щелчком мыши. Если мы не попали ни во что - Р будет равняться nil.

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

А если нам нужен не он?

Тогда придется написать еще одну строку:

P := Engine.PickNext(Active);

Она выдаст следующий подходящий объект - или nil, если такового не нашлось.

Изменение курсора

Стандартная «стрелочка» - явно не предел мечтаний игродела. К счастью, изменить вид курсора тоже совсем несложно:

Engine.SetMouseCursor(8);

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

Это важно: в оконном режиме эта процедура может не работать или работать некорректно. Она сделана в расчете на полноэкранный режим.

Вы можете менять вид мыши динамически, в зависимости от того, над каким объектом она находится. Это немного похитрее; вам придется добавить несколько строк в метод Process игрового мира. Нам поможет переменная Mouse. Например, код на рисунке «Динамическое изменение мыши» заставит курсор превращаться в инопланетный корабль в левой половине экрана и в ракету - в правой.

Динамическое изменение мыши

if Mouse.CursorPos.X < Engine.Width div 2

   then Engine.SetMouseCursor(9)

   else Engine.SetMouseCursor(8);



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

Дополнительно

В будущих номерах

В следующих номерах мы поговорим о:

  • редакторах уровней и сохранении игровых данных;
  • изометрических двумерных движках;
  • системах «частиц» (particles) для отображения дыма, искр и т.п.;
  • работе с прозрачностью;
  • трехмерных движках;
  • основах AI;
  • отладке программы;
  • создании замысла и сценария игры,
  • написании дизайн-документа;
  • игровом балансе;
  • продумывании игровых персонажей и их реплик;
  • работе с Photoshop и трехмерными пакетами;
  • анимации;
  • музыке и озвучке;
  • и многом другом.

Все это вполне реально научиться делать своими руками. Вы скоро в этом убедитесь.

Пишите нам…

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

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




Назад