- Будьте здоровы! - говорит король.
- Не могу отказать вам в вашей просьбе, - отвечает добрый волшебник старческим, дребезжащим голосом - и необычайно здоровеет.
Е.Шварц, "Золушка"
Напомним, что в 6-м номере журнала за этот год мы начали наш новый проект - "Игра своими руками". Начиная с июньского выпуска, на компакт-диске "Лучших компьютерных игр" публикуются свежие версии пакета для создания игр, написанного специально для нашего журнала - ЛКИ-Creator, работающего с Delphi 5-7 версий.
В двух предыдущих выпусках мы научились делать аркады и стратегии. По плану, нам надлежало заняться AI, но эта тема требует больше места, чем у нас сейчас есть. Поэтому мы займемся еще одной назревшей темой: вашими письмами, предложениями и идеями.
Материалы наших прошлых занятий вы можете найти на нашем компакт-диске, в специально созданном для этой цели разделе "Игра своими руками".
Первая читательская игра
Первым приславшим нам игры собственного исполнения на ЛКИ-Creator оказался господин Oct Opus АКА Ось Миног. Он прислал нам две своих игры: одна - ремейк логической игры Filler, другая - продукт собственного сочинения, "Оборотень". Второй мы разберем позднее, а сейчас займемся первым, потому что в нем есть несколько показательных моментов. Игру можно найти на нашем компакт-диске.
Особая польза этого примера - в том, что на его основании вы легко сможете конструировать собственные логические игры, вроде "Тетриса", Lines или "Сапера". По словам Oct Opus'а, программирование заняло у него вечер; так что, если у вас есть свежая идея логической игры, за воплощением дело не станет.
Игра Filler в исполнении читателя Oct Opus. |
Первое, на что обращает внимание пример: игра походовая, плавных движений нет. Поэтому процедура 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 kv class="silver"codep class="title"LightMutate/pp align="justify"bprocedure/b LightMutate(P : TLKIParticle);p align="justify" bbegin/bp align="justify" P.Angle := P.Angle + Random(21)-10;p align="justify" bif/b P.Angle < 0 bthen/b P.Angle := P.Angle + 360;p align="justify" bif/b 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.x, Objects.y, 40, Tick + 1000, 0, 50);
Порождается 500 частиц со скоростью 40, которые живут 1 секунду (1000 миллисекунд, параметр Tick+1000), по кольцу с радиусами 10 и 40, на месте нашего объекта. Каждые 50 миллисекунд частица меняет угол полета на плюс-минус 20 градусов.
Попробуйте сами поэкспериментировать с разными видами частиц; это легко и приятно. Не забудьте, что правильно нарисовать саму частицу очень важно; у нас используются простейшие частицы, а с более красивыми получаются намного лучшие эффекты. Попробуйте испускать сразу несколько потоков разных частиц.
Дополнительные возможности откроются нам чуть позже, когда мы научимся работе с прозрачностью.
Как видите, мы учитываем ваши пожелания в новых версиях пакета. А в следующий раз мы все-таки поговорим о построении ИИ для нашей стратегии. До встречи через месяц!
В будущих номерах
В следующих номерах мы поговорим о:
работе с прозрачностью;
трехмерных движках;
основах AI;
отладке программы;
создании замысла и сценария игры,
написании дизайн-документа;
игровом балансе;
продумывании игровых персонажей и их реплик;
работе с Photoshop и трехмерными пакетами;
анимации;
музыке и озвучке;
и многом другом.
Все это вполне реально научиться делать своими руками. Вы скоро в этом убедитесь.
Пишите нам...
Все, кто хочет поделиться своими соображениями о пакете ЛКИ-Creator и этом цикле статей, сообщить о найденной ошибке, спросить совета или предложить какое-то усовершенствование - милости просим писать по адресу . Кто-нибудь из авторов пакета постарается ответить вам.
Тем, кто считает, что пакет можно чем-то дополнить: во-первых, не забудьте, что на нашем диске сегодня еще не финальная версия пакета, а только та, в которой реализованы описанные в наших статьях функции. Возможно, что-то из ваших идей уже реализовано и ждет своей очереди (см. список "В будущих номерах"). И в любом случае: предлагая нам какую-то идею, попытайтесь обосновать, почему ваше предложение полезно сразу для многих игр, а не только для вашей конкретной.