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

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

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

Занятие четвертое: По вашим заявкам

- Будьте здоровы! - говорит король.

- Не могу отказать вам в вашей просьбе, - отвечает добрый волшебник старческим, дребезжащим голосом - и необычайно здоровеет.

Е.Шварц, "Золушка"

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

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

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

Первая читательская игра

Первым приславшим нам игры собственного исполнения на ЛКИ-Creator оказался господин Oct Opus АКА Ось Миног. Он прислал нам две своих игры: одна - ремейк логической игры Filler, другая - продукт собственного сочинения, "Оборотень". Второй мы разберем позднее, а сейчас займемся первым, потому что в нем есть несколько показательных моментов. Игру можно найти на нашем компакт-диске.

Особая польза этого примера - в том, что на его основании вы легко сможете конструировать собственные логические игры, вроде "Тетриса", Lines или "Сапера". По словам Oct Opus'а, программирование заняло у него вечер; так что, если у вас есть свежая идея логической игры, за воплощением дело не станет.

122KB
Игра Filler в исполнении читателя Oct Opus.
Для тех, кто не помнит, суть игры Filler достаточно проста. Поле разбито на цветные ромбы (см. картинку). Играют двое (один из них может быть представлен ИИ). Один начинает из нижнего левого угла, другой - из правого верхнего. Каждый в свой ход выбирает цвет, отличный от своего и вражеского "текущего" цветов, и в него перекрашиваются все ромбы, которые связаны сплошной цепочкой ромбов его текущего цвета. Например, если сейчас на картинке игрок 1 выберет оранжевый цвет, все желтые ромбы в левом нижнем углу станут оранжевыми. Задача - завоевать больше половины территории.

Первое, на что обращает внимание пример: игра походовая, плавных движений нет. Поэтому процедура Process игрового мира содержит только обращение к клавиатуре, проверку на кнопку Escape - и вызов встроенной Process. Все остальные функции лежат на нажатии кнопок, которым автор придал вид таких же цветных ромбов.

Рисование поля

Можно было бы сделать каждый ромб игровым объектом, и это было бы намного проще, чем то, что сделал автор. Но Oct Opus вспомнил, что в оригинальной игре одноцветные ромбы группировались в более крупные. Поэтому он сделал так, что все объекты создаются специальным методом GroupTiles и им же уничтожаются при каждом изменении на игровом поле.

 

GroupTiles

procedure TFillerWorld.GroupTiles;

 var i,j,t : integer;

 begin

  while NObj>0 do

   begin

    RemoveObj(true, 0)

   end;

  btl := tiles;

   

  for j:=1 to MaxTileY do

   for i:=1 to MaxTileX do

    begin

     if Btl[i,j] = 255 then continue;

     t := Btl[i,j];

     if (Btl[RX(i,j),j+1] = t)

      and (Btl[LX(i,j),j+1] = t)

       and (Btl[i,j+2] = t)

      then

       begin

        AddObj(true, 1, PX(i,j)-7, 

            PY(i,j), 0, Btl[i,j]+8, 1);

        Btl[LX(i,j),j+1] := 255;

        Btl[RX(i,j),j+1] := 255;

        Btl[i,j+2] := 255;

        continue;

       end;

      AddObj(true, 1, PX(i,j), PY(i,j), 

            0, Btl[i,j]+16, 1);

    end;

 end;

Посмотрим на эту процедуру (на врезке). Первым делом мы копируем массив ромбов Tiles во временную переменную Btl. Затем для каждого ромба из Btl проверяем, не находятся ли прямо под ним, слева внизу и справа внизу ромбы того же цвета, и если да - то создаем большой ромб (а задействованные перекрашиваем в несуществующий цвет, чтобы не отрисовывать их снова), если же нет - маленький. На самом деле на врезке приведен сокращенный вариант процедуры; у Oct Opus'а рассматриваются и более крупные ромбы, состоящие из 16 маленьких.

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

Перекрашивание и ИИ

Для перекраски ромбов при делании хода автор выбрал рекурсивный метод. Он, может, и не слишком быстр, но в данном случае работает достаточно хорошо, а главное - прост и понятен. См. фрагмент кода "Перекрашивание".

Перекрашивание

function TFillerWorld.Redraw(x,y : integer; c : byte) : integer;

 var r : integer;

 procedure Redr(ax,ay : integer; ac,ab : byte);

  begin

   if (Tiles[ax,ay] < ab) or (Tiles[ax,ay] = 255) then exit;

   Tiles[ax,ay] := ac;

   Redr(LX(ax,ay),ay+1,ac,ab);

   Redr(RX(ax,ay),ay+1,ac,ab);

   Redr(LX(ax,ay),ay-1,ac,ab);

   Redr(RX(ax,ay),ay-1,ac,ab);

   inc(r);

  end;

 begin

  r := 0;

  Redr(x,y,c,Tiles[x,y]);

  GroupTiles;

  Result := r;

 end;

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

Обратите внимание на переменную-счетчик R, которая увеличивается на единицу каждый раз, когда клетка перекрашивается. Этот счетчик в конце работы процедуры дает нам текущий размер домена (без учета новоприсоединившихся ромбов).

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

Искусственный интеллект

  function TFillerWorld.CountAI : byte;

 var i, k, mc, mn : integer;

 begin

  mc := 255;

  mn := 0;

  for i:=0 to 7 do

   begin

    if i = Tiles[1, MaxTileY] then continue;

    if i = Tiles[MaxTileX,1] then continue;

    k := Count(MaxTileX,1,i);

    if k<=mn then continue;

    mn := k;

    mc := i;

   end;

  Result := mc;

 end;

Процедура Count, которую вызывает эта процедура, устроена так же, как и перекраска, за двумя исключениями. Во-первых, она работает не с настоящим игровым полем, а с его копией (той же самой Btl, которую мы уже видели в коде метода GroupTiles), а значит, ничего не перекрашивает. Во-вторых, она делает перекраску дважды, потому что во время перекраски считается текущий размер домена, а нам нужен тот, который будет на следующий ход.

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

Полный код примера, равно как и исполняемый файл, вы можете найти на нашем диске и на сайте.

Напоследок замечу, что автор программы выбрал довольно большое число ромбов, и игра длится долго. Сократить ее вполне в ваших силах - достаточно поменять константы MaxTileX и MaxTileY и пропорционально уменьшить поле.

Консоль

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

В роли консоли выступит все тот же объект TLKIEdit - ведь, в сущности, что такое консоль, как не текстовая строка ввода? Как пользоваться этим объектом - вы прочтете в июльском выпуске ЛКИ (статья есть на CD этого номера).

Добавить нам пришлось только одно - параметры ActivationMode и ActivationKey, которые теперь встроены в TLKIEdit. Первый может принимать значения: acNone (не включается клавишами), acShift, acCtrl, acAlt (включается при нажатых клавишах Shift, Ctrl, Alt соответственно). Ну, а в ActivationKey - символ, соответствующий клавише, которая должна включать консоль. Обратите внимание: acNone - это не признак того, что строка включается без Shift-Ctrl-Alt, а признак того, что она вообще не включается нестандартным способом.

Частицы

Но самый, вне сомнения, популярный вопрос, который звучит чуть ли не в половине писем с пожеланиями - это вопрос о частицах, они же particles ("партиклы").

Идя навстречу этим пожеланиям, мы сделали в нашем движке излучатель частиц. Это - метод класса TLKIGameWorld, именуемый Emit.

Вот его описание:

procedure TLKIGameWorld.Emit(n : integer; ChangeDest : TProc; Shape : TLKIParticleShape; dir, p1, p2 : integer; Pic : TLKISprite; x, y, spd : integer; deathtime : integer; pa, pe : integer);

Разберем ее параметры подробно.

79KB
Круговые излучения хороши для взрывов.
  • N - количество частиц.

  • ChangeDest - процедура, меняющая направление движения частиц. Вам нужно описать (или взять готовую) процедуру и передать ее имя излучателю в качестве параметра. Об этом подробнее см. ниже.

  • Shape - форма излучателя. Характеристики этой формы определяются тремя следующими параметрами. На сегодня поддерживаются четыре формы:

    • psCircle - кольцо. Параметр Dir не играет роли (кольцо направлено во все стороны), p1 - большой радиус кольца, p2 - малый радиус. Изначальное направление движения частиц - от центра;

    • psBurst - круг. Как и с кольцом, но играет роль только p1.

    • psCone - конус. Dir - угол, в направлении которого надлежит испускать частицы (в градусах, как обычно), p1 - ширина конуса в градусах, p2 - изначальная длина конуса. Стартовое направление частиц - из вершины конуса;

    • psLine - линия. Dir - направление линии в градусах (и стартовое направление частиц), p1 - ширина потока в пикселах, p2 - изначальная длина линии.

  • Pic, x, y, spd, deathtime - обычные параметры игрового объекта (spd - скорость);

  • Param - зарезервированная переменная для "хитрых" законов движения;

  • Period - частота смены направления.


Закон движения

Процедуру закона движения надо описывать отдельно. Исходно определены две. Одна из них - LightMutate, которая случайным образом меняет угол движения в пределах плюс-минус 10 градусов. На ее примере мы и увидим построение таких процедур (см. "LightMutate").

LightMutate

procedure LightMutate(P : TLKIParticle);

 begin

  P.Angle := P.Angle + Random(21)-10;

  if P.Angle < 0 then P.Angle := P.Angle + 360;

  if P.Angle > 360 then P.Angle := P.Angle - 360;

  P.MoveForward(80);

 end;

Эта процедура прибавляет к углу случайную величину (Random дает число от 0 до 20, вычтя 10, получаем от минус 10 до 10), проверяет, не вышел ли угол за допустимые рамки, а затем двигает вперед частицу. Расстояние, на которое мы ее двигаем, большой роли не играет - главное, чтобы частица не успела полностью его пройти до следующего изменения.

Еще определена процедура NoChange, которая ничего не меняет. Она используется, если мы хотим, чтобы частицы продолжали лететь по прямой.

Пример

Попробуем сделать так, чтобы в нашем старом примере StarEscort инопланетяне, взрываясь, вместо спрайта оставляли разлетающиеся частички. Зададим для этого спрайт Sparkle.bmp, уберем у объектов инопланетян параметр RemoveSpr (нам ни к чему, чтобы оставался еще и спрайт вспышки) и напишем перед удалением объекта вражеского корабля такой код:

Emit(500, LightMutate, psCircle, 0, 40, 10, Sprites[15], Objects[i].x, Objects[i].y, 40, Tick + 1000, 0, 50);

Порождается 500 частиц со скоростью 40, которые живут 1 секунду (1000 миллисекунд, параметр Tick+1000), по кольцу с радиусами 10 и 40, на месте нашего объекта. Каждые 50 миллисекунд частица меняет угол полета на плюс-минус 20 градусов.

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

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

* * *

Как видите, мы учитываем ваши пожелания в новых версиях пакета. А в следующий раз мы все-таки поговорим о построении ИИ для нашей стратегии. До встречи через месяц!


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

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

  • работе с прозрачностью;

  • трехмерных движках;

  • основах AI;

  • отладке программы;

  • создании замысла и сценария игры,

  • написании дизайн-документа;

  • игровом балансе;

  • продумывании игровых персонажей и их реплик;

  • работе с Photoshop и трехмерными пакетами;

  • анимации;

  • музыке и озвучке;

  • и многом другом.

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

Пишите нам...

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

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



Назад