Творец. День нулевой. Игра готова

В первой части статьи я рассказал о 3D-движке GLScene для Delphi (см. «МБ» №35’2007). Я надеюсь, вы уже ознакомились со всеми демками движка и успели придумать множество вариантов его применения. Я предложу сегодня свой вариант.
«Мой фильм готов, осталось его снять», – сказал когда-то режиссёр Федерико Феллини. У нас ситуация очень похожа. Но давайте всё-таки уясним поточнее, что именно мы будем разрабатывать. Итак, как я уже говорил, для начала попробуем силы в жанре «платформер».

Моя игра v.0.0 (без патча)
Что же будет в нашей игре? Вид сбоку (вспоминаем старые консольные игры), три вида оружия, бесконечные патроны, три вида монстров, пять уровней, ограничение по возрасту: от 85 и младше. Теперь пора разобраться, как именно мы всё это будем реализовывать.
Для начала посмотрим на папку для нашей игры. В корневом каталоге должны находиться следующие папки: Objects, Levels, Sound. Игра загружает очередной уровень из папки Levels и подгружает необходимые объекты из Objects. Также в файле с уровнем должна быть информация о музыке. Её воспроизводим из папки Sound. В качестве файла уровня будет выступать конфигурационный ini-файл (который по желанию можно зашифровать, чтобы его не редактировал, кто попало). Вот пример файла (Level1.ini):

; Первый уровень нашей игрушки
[level]
Name=Резня на скотобойне
Next=level2.ini
[music]
File=track1.mp3
[map]
Count=5
Line1=111111111111111111111
Line2=100000000000000000001
Line3=100000000000000000001
Line4=1P00000111000000000E1
Line5=111111111111111111111

Игра считывает каждый символ в текущей строке LineCount указываем, сколько всего строк) из сектора Map и ищет папку в каталоге Objects с таким же названием, как и символ. В такой папке хранится файл с информацией о данном объекте (статичный или анимированный) и картинка, которая будет отображаться на месте символа. К примеру: 0 – пусто, 1 – стена, P – место появления игрока, а E – место, куда должен прийти игрок для завершения уровня. Один символ – один объект, поэтому все картинки должны быть стандартизированы (например, 32×32 пикселя и формат BMP). В качестве стены может выступать и стандартный 3D-куб – 2D‑ и 3D‑графика совместимы.

Теперь подробнее об игроке и монстрах. Понятно, что они должны быть анимированными. Это можно реализовать тремя способами:
1. Копаемся в исходниках GLScene и добавляем поддержку GIF-формата.
2. Используем объект GLActor. Он поддерживает, например, форматы MD2 и MD3, а также SMD. Создаём в 3DSMax’е любую анимацию, качаем плагин для импорта/экспорта MD3 и конвертируем наше творение. Можно запихнуть в один файл несколько анимаций, тогда необходим дополнительный файл с расширением AAF, в котором будут указаны промежутки для каждой из них. Вполне возможно, что именно этот способ мы и возьмём на вооружение.
3. Создаём группу изображений формата BMP и обзываем их следующим образом: left1.bmp; left2.bmp; left3.bmp. То же самое и для right (наши персонажи могут двигаться только влево-вправо, прыжок тоже к этому относится). Игра будет просто открывать их по очереди, создавая анимированную картинку. Почему именно BMP? Битмапы подгружаются без тормозов и отображаются полностью при перемещении, тогда как другие форматы пускают по картинке белые полосы.

Разум для кота
Теперь несколько слов о поведении монстров. Я не думаю, что для приставочной стрелялки стоит разрабатывать сложный AI. Пусть будет так: монстр ходит влево-вправо относительно места своего появления и в зависимости от степени агрессии (всё в ini-файле), может напасть на игрока, если он стреляет или даже просто стоит рядом.
Управление игроком. Использовать FormKeyDown не рекомендую – эта процедура не позволяет нажимать одновременно несколько клавиш. Намного продуманнее GLScene’овская функция IsKeyDown. Чтобы она заработала, пропишите Keyboard в разделе Uses вашего проекта Delphi. Использовать IsKeyDown я советую в событии GLCadencerProgress – единственном событии компонента GLCadencer, отвечающего за рендеринг сцены. То есть нажатие клавиш будет проверяться при очередной отрисовке формы приложения. Вполне логично. Пример обработки нажатия клавиши «D» приведён в листинге 1.

// наша процедура с двумя параметрами:
// название анимации и сторона(влево или вправо)
if IsKeyDown('d') or IsKeyDown('в')
  then CreateAnim('animate/walk', 'left');

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

Комментарии к листингу
Весь код, приведённый в статье, вы напишете позже, а пока разберёмся, как он работает. Все многоточия обозначают то, что до этого куска кода в этом разделе ещё что-то содержится и нам нужно просто дописать туда текущий код.
Итак, сначала нужно добавить новые переменные в раздел var и создать процедуру FormCreate для нашей формы (надеюсь, вы знаете, как это делается). Также нужно добавить новый объект GLDummyCube и обозвать его GLLevel. В процедуре, перво-наперво, укажем путь для уровня. Это делается присваиванием TINIFile.Create переменной level. В скобках и указываем файл. Затем нужно узнать, сколько «строк» на нашем уровне. Это делается строчкой level.ReadString (опять же, присваиванием. Можно также прочитать тип Integer, Boolean и прочее). Стоит заметить, что последний параметр в скобках – это переменная, которой мы и присваиваем значение считывания. Затем счётчиком сначала перебираем все line из файла, а затем – все символы в строке. Мы смотрим принадлежность символа строчкой следующим кодом:

case line[k] of

где k – это текущий символ. Если символ – 1, то добавляем нового потомка TGLCube для GLLevel и назначаем для координатных осей значения переменных I и K (счётчики как раз имеют нужные нам значения). Текущий TGLCube находится в переменной cube. Можно и не использовать переменную для добавления новых потомков какого либо объекта, однако в нашем случае уровень будет отображаться некорректно. Проверка символа 0 не производится – цикл просто увеличит переменную K на единицу. Вместо 3D-куба можно добавлять спрайты (2D-картинки). Тогда между 1: begin и end; надо вписать код из комментария (он заключён в фигурные скобки) и использовать переменную sprite вместо cube.

Программист-мясник
Задачи чётко прописаны, пора приниматься за работу. На этот раз ДЗ будет попроще: создайте файл Level1.ini (весь текст для файла берите из примера) и вбейте Листинг 2 в проект Delphi, который мы создавали в прошлой статье. А во-вторых, попробуйте нарисовать анимацию ходьбы для Главного Героя (пример на рисунке). Если хватит сил и усидчивости, проделайте то же самое и с монстром.

var
 // ...
 level: TINIFile;
 i, k, ls: integer;
 TexFile, GameDir: string;
 line, count, cs: string;
 cube: TGLCube;
 {sprite: TGLSprite;}
procedure TForm1.FormCreate(Sender: TObject);
begin
 // ...
 GameDir:=ExtractFilePath(Application.ExeName);
 level:=TINIFile.Create(GameDir+'\Levels\level1.ini');
 count:=level.ReadString('map', 'count', count);
 for i:=1 to StrToInt(count) do
  begin
   line:='';
   line:=level.ReadString('map', 'line'+IntToStr(i), line);
   ls:=length(line);
   inc(y);
   for k:=1 to ls do
    begin
     inc(x);
     case line[k] of
      '1':
       begin
        GLLevel.AddNewChild(TGLCube);
        TGLCube(GLLevel.Children[k-1]).Position.Y:=y;
        TGLCube(GLLevel.Children[k-1]).Position.X:=x;
       end;
     end;
   {
   TexFile:=GameDir+’Objects\’+line[k]+’\’+line[k]+’.bmp’;
   sprite.Material.Texture.Image.LoadFromFile(TexFile);
   sprite.Material.Texture.Disabled:=false;
   sprite.Position.Y:=i;
   sprite.Position.X:=k;
   }
  end;
end;

Также, если вы знакомы с 3DStudioMax, готовьтесь к тому, что в следующей статье мы будем заниматься действительно серьёзной 3D-графикой.

Полезные ссылки
www.glscene.ru – русскоязычный сайт движка GLScene. Файлы, форум, статьи.
www.gamedev.ru – русскоязычный сайт, посвящённый разработке игр. Файлы, форум, статьи. Есть раздел «Работа».


Рекомендуем почитать: