В предыдущей статье мы набросали основу нескольких программ, которые можно развить в полезные для работы приложения достаточно высокого уровня. И даже пришли к выводу, что в Delphi есть всё, что нам нужно, чтобы написать игру. Так давайте же реализуем это утверждение!
Сложней всего написать полноценную стратегию или хороший экшен. Эти жанры не для начинающего творца-одиночки, а для команды профессионалов. Кроме того, помимо чисто технических сложностей, в играх такого класса требуется придумать хороший сюжет, вокруг которого и будет строиться сама игра. Значительно проще создать квэст. При желании в квэстах можно обойтись практически без движущихся объектов, а это значительно (!) упрощает жизнь разработчика. Но тут возникает другая проблема. Тут одним сюжетом не отделаешься, тут надо едва ли не сценарий для игры писать. Да и, как вы понимаете, продемонстрировать создание квэста "от и до" на нескольких страницах явно не удастся. Отложим пока квэсты. Но мы к ним ещё вернёмся. Итак, остаются логические игры.
Название "логические игры" охватывает большой класс игр, поэтому уточним: простые логические игры. Почему простые? Если писать достаточно сложную игру, требуется искать хороший алгоритм работы для такой программы (ну кому понравятся шахматы, ходящие случайным образом?). Вот, казалось бы, простая игра крестики-нолики (она же – гомоки). А реализация хорошего алгоритма игры занимает порядка 20 килобайт только программного кода или, если кому трудно это представить, 12 печатных страниц мелким шрифтом… Поэтому мы и займёмся простыми логическими играми не требующими сложных алгоритмов для компьютерного противника.
Для начала напишем очень простую игрушку: "поиск сокровищ". При этом, как и в прошлый раз, будем пользоваться Delphi 4. Итак, игра. Перед нами лежит карта местности, где закопан клад. Только вот она с одной особенностью: место, где копать, нужно ещё найти. Мы можем ткнуть в карту пальцем и спросить: "далеко ли отсюда клад?" и получим ответ. Цель – найти место, где закопан клад. Как видите, очень простая игрушка. Для её реализации нам понадобится, прежде всего, сама карта. Поскольку игра довольно условна, не обязательно искать точную карту вашего района, можно нарисовать что-нибудь отдалённо напоминающее план (достаточно нескольких линий в редакторе типа PhotoImpact, к которым применено сильное размытие). Теперь, когда у нас есть карта, приступим непосредственно к программированию. Запустите Delphi и разместите на новой форме компонент Image (он на вкладке Additional). Его свойство Align (используйте Object Inspector – Инспектор объектов) установите в alClient, при этом Image (образ) займёт всё свободное пространство формы. Параметру Picture присвойте полное имя вашего рисунка – "карты". Просто нажмите на маленькую кнопочку рядом с этим параметром, а дальше Delphi попросит вас указать этот рисунок. Как несложно было догадаться, свойство Picture отвечает за то, какая картинка отображается в Image'е. Ну вот, можно заняться обработкой событий. Событие формы OnCreate (ObjectInspector\Events\OnCreate) будет иметь следующий обработчик:
SetBounds(0,0,800,600);
Randomize;
Treasure.x:=random(700)+50;
Treasute.y:=random(500)+50;
Image1.Picture.LoadFromFile('Back.bmp');
Image1.Canvas.Brush.Style:=bsClear;
Image1.Canvas.Font.Color:=clWhite;
Первая строчка устанавливает размеры формы во весь экран, вторая – нужна для инициализации генератора случайных чисел, следующие две задают случайные координаты (вот зачем генератор) местонахождения клада. Обратите внимание: координаты записаны в несуществующую переменную Treasure. Пока эта переменная не будет объявлена, программа не запустится. Поэтому отыщите в редакторе кода слово private и после него впишите
Treasure:TPoint;
А две последние строчки нужны для того, чтобы надписи (расстояние до клада) выводились красиво. Можете поэкспериментировать на досуге с параметрами Image1.Canvas.Font, Image1.Canvas.Brush, Image1.Canvas.Pen.
Теперь надо обработать щелчёк по карте, т.е. образу (Image\OnClick). Вся процедура обработчика выглядит так:
procedure TForm1.Image1MouseDown(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; X, Y: Integer);
var d,xx,yy:integer; // объявление переменных
begin
xx:=Treasure.x-x;
yy:=Treasure.y-y;
d:=round(sqrt(xx*xx+yy*yy)/10);// Расстояние до клада находим по
// теореме Пифагора (Евклидова метрика :)
Image1.Canvas.TextOut(x,y,inttostr(d)); // И выводим его как текст
if d<=1 then // Если игрок нашёл место…
begin
for xx:=1 to 50 do // …нарисуем кое-что…
begin
Image1.Canvas.MoveTo(x,y);
Image1.Canvas.LineTo(x+random(200)-100,y+random(200)-100);
end;
ShowMessage('Копать здесь!'); //…и сообщим ему об этом!
end;
end;
Все слова в строке после символов "//" в текст программы не входят и называются комментариями.
Ну вот, игра готова. Разумеется, можно к ней ещё много что приписать, но я оставлю это вам, дорогие читатели. Ищите и пишите. Пожалуй, востребованней всего будет команда "Новая игра". Но это будет уже ваша самостоятельная доработка.
Это была самая простая игра, какую я только смог вспомнить. А теперь напишем принципиально другую игрушку. Это известная всем с детства игра: надо восстановить картинку, разбитую на квадраты, попарно меняя эти квадраты местами. Для реализации этой игры мы будем использовать сетку (компонент DrawGrid со страницы Additional). На основе такой сетки можно реализовать огромное множество игр, для которых нужно поле в клеточку! Игра в пятнадцать, крестики-нолики и даже сокобан общаются с игроком через поле-сетку.
Кроме, сетки нам понадобится список изображений (компонент ImageList со страницы Win32). Сначала расположите сетку на форме и настройте свойства следующим образом:
DefaultColWidth = 80
DefaultRowHeight = 80
FixedCols = 0
FixedRows = 0
Height = 410
ScrollBars = ssNone
Width = 410
Обратите внимание, какие изменения происходят при настройке каждого параметра. В результате получается достаточно большая квадратная сетка с 25 ячейками. Наша игрушка будет использовать картинки размером 400х400 точек, поэтому подготовьте сейчас такую картинку. Конечно, можно было бы использовать картинки и произвольного размера, но это чуть-чуть увеличило и усложнило бы программу. Для ImageList установите свойства Width и Height равными 80. В нём будут храниться те самые квадраты, на которые мы порежем рисунок.
Теперь обработаем создание формы (Form1\OnCreate), т.е. опишем для компьютера все шаги необходимые для начала игры. Обработчик будет выглядеть следующим образом:
procedure TForm1.FormCreate(Sender: TObject);
var TempB,BMP:TBitmap; //необходимые переменные
x1,y1,x2,y2,i,j:byte;
DestRect,SourceRect:TRect;
Begin
Randomize;
for i:=1 to 5 do //установка правильного порядка частей рисунка
for j:=1 to 5 do
pict[i,j]:=(i-1)*5+j;
BMP:=TBitmap.Create;
BMP.Width:=400; BMP.Height:=400;
TempB:=TBitmap.Create;
TempB.Width:=80; TempB.Height:=80;
Bmp.LoadFromFile('BMP.bmp');
for i:=1 to 5 do
for j:=1 to 5 do
begin //а это – копирование частей рисунка
SourceRect:=Rect((j-1)*80,(i-1)*80,j*80,i*80);
DestRect:=Rect(0,0,80,80);
TempB.Canvas.CopyRect(DestRect,BMP.Canvas,SourceRect);
Imagelist1.Add(TempB,nil);
end;
BMP.Destroy; TempB.Destroy;
for i:=1 to 21 do
begin //в этом цикле фрагменты картинки меняются местами
repeat
x1:=random(5)+1; y1:=random(5)+1;
x2:=random(5)+1; y2:=random(5)+1;
until (x1<>x2)and(y2<>y1);
j:=pict[x2,y2];
pict[x2,y2]:=pict[x1,y1];
pict[x1,y1]:=j;
end;
end;
Выглядит немного устрашающе, не так ли? Но это едва ли не большая часть программы! В этой процедуре программа обращается к массиву pict. Объявим его и ещё пару переменных после слова private, как это делали в предыдущей игре.
pict:array[1..5,1..5]of byte;
Switch:TPoint;
SwitchB:boolean;
Сохраните программу, если вы ещё этого не сделали. Скопируйте подготовленную раньше картинку в папку программы под именем BMP.bmp и запустите программу. Вы увидите пустую сетку. А всё потому, что программа пока не знает, как на сетке рисовать. Чтобы "объяснить", как это делается напишите обработчик DrawGrid1\OnDrawCell, состоящий всего из двух строк:
Tag := Pict[ARow+1,ACol+1]-1;
ImageList1.Draw(DrawGrid1.Canvas,Rect.Left,Rect.Top,Tag);
Можете убедиться, что теперь искромсанная картинка лежит в сетке. Осталось обработать нажатие мышкой на сетке и всё! DrawGrid1\OnMouseDown выглядит так:
procedure TForm1.DrawGrid1MouseDown(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; X, Y: Integer);
var i,j,t:byte;
win:boolean;
begin
if SwitchB then //надо менять фрагменты местами
begin
t:=pict[y div 81+1,x div 81+1];
pict[y div 81+1,x div 81+1]:=pict[switch.y,Switch.x];
pict[switch.y,Switch.x]:=t;
SwitchB:=false;
end
else //первое нажатие в паре "выбор-обмен"
begin
Switch.x:=x div 81 +1;
Switch.y:=y div 81 +1;
SwitchB:=true;
end;
win:=true;
for i:=1 to 5 do
for j:=1 to 5 do
win:=win and (pict[i,j]=(i-1)*5+j);
if win then // если всё собрано…
application.MessageBox('Ты выйграл!','***', MB_ICONINFORMATION);
end;
И это всё! Игра готова к употреблению. Немного видоизменив эту программу получим игру в 15. Всего-то: другие картинки в ImageList'е и немного другие правила перемещения картинок, а остальное – один в один совпадает. Как и раньше сразу говорю – эта игра не совершенство. К ней можно добавить ещё кучу полезных возможностей. Например "Начать новую игру", "Просмотреть исходную картинку", "Ограничение времени",… Всё, на что у вас хватит фантазии!
Как видите, написать игру не так уж сложно. Нужно всего лишь выделить в игре главное и сосредоточиться именно на этом, а все финтифлюшечки приделать потом. Давайте посмотрим ещё раз на квэсты (я же обещал к ним ещё вернуться). Вся игра состоит из набора статичных, как правило, сцен по которым разбросаны разные объекты: предметы, люди… Все предметы имеют общие свойства: положение, изображение, поведение курсора над ними, и др. К тому же каждый из них обладает и уникальными свойствами - реакция на нажатие например. Итак, что получается? С чисто технической точки зрения квэст состоит из сцен, для каждой из которых нужно описать все находящиеся в ней объекты. И для большинства объектов требуется обработчик нажатия. В упрощённом варианте всё это представляется как массив записей и набор процедур, а в этом нет ничего сложного. Для создания не слишком сложного квэста требуется немного знаний, много терпения и ничего больше!
Пишите, творите и помните: нет предела совершенству!
Макаров Владимир
Исходники программы "поиска сокровищ" (5,7 Кб) и "восстановления картинки" (6,7 Кб).