Урок 11. Программируем
ловушки и источники жизни
Надо ли выделять ловушки в
отдельный класс игровых объектов, или же удастся обойтись уже имеющимися наработками?
Механизм ловушек достаточно прост - герой встает на некоторый таил, и здоровье
его либо ухудшает, либо растет. Воплотить этот механизм в игре можно, например,
добавлением новых тайлов - таила ловушки и таила источника, например,
"оздоравливающей маны". Срабатывание ловушек будет происходить в
момент перемещения героя на эти тайлы. Добавим в модуль Map две новые
константы:
tileGrass = 1;
tileGround = 2;
tileStairsUp = 3;
tileStairsDown = 4 ;
tileTrap
= 5;
tileLive
= 6;
tileFirstStopTile = 7;
tileTree
= tileFirstStopTile;
tileStone
= tileFirstStopTile+1;
tileLast =
tileFirstStopTile+1;
Теперь при попытке компиляции система выдаст сообщение об ошибке, указав на
неверную длину массива TileRecords. Это естественно, так как
число тайлов в игре изменилось. Мы как раз планировали, чтобы подобные
сообщения компилятора выдавались в нужных местах исходного текста при
расширении характеристик игры. Опишем представление новых тайлов:
const
TileRecords: array[1..tileLast] of TTileRecord =
(
(C:
' . '
; Clr:Green),
(C: '
_ ' ;
Clr:Brown),
(C: ' <
' ; Clr:LightGray),
(C: ' >
' ; Cir:LightGray) ,
(C: ' .
' ; Clr:Green),
(C: ' *
' ; Clr:LightCyan),
(C: ' !
' ; Clr:LightGreen),
(C: ' /\
' ; ClrrLightGray)
);
Здесь символ * будет
соответствовать источнику жизни. Обратите внимание, что описание ловушки в
точности совпадает с одним из описаний проходимых тайлов. Это необходимо чтобы
герой исходно не мог определить, где находится ловушка. Выявление нужных мест
будет выполняться с помощью особого умения героя, которое мы реализуем когда
займемся концепцией навыков персонажа.
Осталось только распределить их
на карте достаточно разумным способом. Как часто мы можем ловушки на карте?
Предположим, желательно, чтобы одна ловушка находилась на участке 10*10 тайлов.
Тогда вероятность появления ловушки на этом участке равна 1/100. Из этого
соотношения и будем исходить.
Добавление ловушек па карту
будем выполнять в процедуре MapGeneration. В блоке, ответственном за фиксацию
проходимого таила (tileGrass или tileGround), допишем оператор, размещающий с
вероятностью 0,01 в текущей точке карты ловушку или источник
begin
if random(100) < 35
then GameMap[CurMap].Cells [x,у] .Tile :=
tileTree else
if r.mdom(2) = 0
then
GameMap[CurMap].Cells[x,y].Tile
:= tileGrass
else GameMap[CurMap].Cells[x,y].Tile
:= tileGround;
if random(100) =
0 then
if random(2) = 0
then
GameMap[CurMap].Cells[x,y].Tile
:= tileTrap
else GameMap [CurMap] .Cells[x, y] .Tile
:= tileLive;
еnd;
Теперь программируем момент
срабатывания ловушек. Это должно происходить, очевидно тогда, когда герой
наступает на соответствующий таил. Процесс перемещения реализован в процедуре
Movellero. Добавим в конец этой процедуры дополнительную проверку вида таила,
па котором находится персонаж. Если это ловушка, нанесем герою удачное
поражение; если это источник, восстановим утраченное здоровье. Чтобы ловушка
Могла наносить поражение,
соответствующее реальному уровню здоровья героя, будем вычислять поражение как
функцию от максимального значения здоровья. Ведь если это вычисление будет
абсолютным (например, жестко заданные три или пять условных единиц). По мере
совершенствования героя и увеличения его параметров опасность такой ловушки
будет уменьшаться. Договоримся, что ловушка может причинить вред, случайно
определяемый случайно в диапазоне от единицы до величины максимального здоровья
(МахНР), умноженной коеффициент 1,1 (он чуть больше единицы, поэтому ловушка
потенциально может убить героя).
If GameMap[CurMap] . Cells [
Heroes [CurHero] .x, Heroes [CurHero] .у] .Tile
in
TrapTileSet then
begin
GameMap[CurMap].Cells [
Heroes[CurHero].x, Heroes[CurHero].y] .Tile
tileGround;
dam I:=random(
round(Heroes[CurHero].MaxHP
* 1.1) ) + 1;
ShowInfo(STR TRAP + IntToStr(abs (dam)));
INCHP Heroes[CurHero], -dam
);
end;
Пропорционально выделив
константу-множество тайлов-ловушек TrapTileSet, описанную в TileMap
Const TrapTileSet = [TileTrap];
Данный подход удобен тем, что
позволяет в дальнейшем свободно добавлять новые тайлы специально не запоминая
глубоко запрятанное место в программной коде, требующее дополнительного
уточнения списка таких тайлов. Все изменения вносятся только в область
определения констант.
На место сработавшей ловушки
записывается обычный таил "земли". Далее играющему выдается
информационное сообщение о том, что он наступил на ловушку и получил
повреждение. Режим информирования пользователя о текущих событиях в виртуальном
мире очень популярен. Это неотъемлемая "фича", удобная и ставшая
стандартной особенностью всех коммерческих игр. Обычно она реализуется в виде
так называемого журнала, причем описания событий в нем могут представлять собой
логически связанные и литературно обработанные тексты. Обычно часть из них
готовится заранее сценаристами, а какая-то информация генерируется
автоматически, причем алгоритмы такой генерации подчас весьма сложны и
изощренны. Рекомендую попробовать поэкспериментировать с режимом выдачи
человеку сообщений игры.
Сообщение выводится на экран с
помощью зависимой от платформы процедуры Showlnfo, единственным параметром
которой и является сообщение. Константа STR_TRAP (модуль Texts), составляющая
текстовую информацию, может выглядеть так:
const STR_TRAP
= ' Ловушка
сработала и наносит
поражение ' ; Величина поражения переводится в
текстовый вид с помощью функции IntToStr. Эта функция стандартна в Delphi,
однако в TurboPascal такой возможности еще не было, поэтому запрограммируем ее
сами, добавив в модуль LowLevcl. Отметим, что даже ее описание в интерфейсной
части этого модуля должно быть привязано к выбранной целевой платформе, так как
в Delphi функция IntToStr уже существует:
{$1
DEFINES.INC)
unit LowLevel;
interface uses Map, Monster;
{$IFDEF DOS_GAME}
function IntToStr( v: Integer ): string;
{$ENDIF}
Это ее ДОС-реализация:
function
IntToStr( v: Integer
): string;
var
s: string;
begin
Str(v,
s);
IntToStr :=
s;
end; Процедура вывода сообщения
может выглядеть следующим образом:
procedure
Showlnfo(Msg: string) ;
begin
GoToXY(l,1);
TextColor(LightGray);
Write(Msg,STR_INFO);
Readln;
end; В первой строке печатается
текст сообщения. Строка STRJNFO содержит служебную информацию о том, что для
продолжения игры надо нажать Enter:
const STR_INFO =
' (нажмите Enter)
' ; Дополнительные пробелы в
конце нужны, чтобы стереть возможный хвост старого, более длинного сообщения.
Нажатие клавиши требуется для того, чтобы человек успел прочитать текст, в
противном случае выведенная на экран информация может быть затерта новыми
сообщениями.
Продолжим корректировку процедуры
MoveHero- вычислим величину здоровья героя и выфзовем процедуру IncHP,
изменяющую текущтй уровень здоровья. Ее мы поместим в модуль Hero:
Procedure incHp (var H:Thero; dam:Integer);
Begin
H.Hp :=H.HP + dam;
If H.HP<=0 then HeroDead;
If H.Hp>H.MaxHp then H.HP:=H.MaxHp;
End;
Рассмотрим данный код более
подробно. Вначале здоровье героя (соответсвующий обьект передается по ссылке)
изменяется на величину, хранящуюся в переменной dam. Если текущее здоровье
героя меньше или равно нулю, что означает, что он мертв, вызываем процедуру
HeroDied, которая должна сообщить играющему о гибели персонажа и завершить
работу программы. В случае, если здоровье выше допустимого для текущего
здоровья героя значения, исправим его на более реалистичное значение.
Чтобы проверить что программа
после внесенных изменений вновь стала работоспособной, нам необходимо написать
текст процедуры HeroDied. Так как в ней должно происходить завершение
программы, внесем ее в модуль LowLevel:
Procedure HeroDied;
Begin
ShowInfo(STR_Hero_Died);
Readln;
ClrScr;
Wait;
End;
Играющий получает сообщение о
смерти героя, нажимает Enter, после чего программа завершает работу. Константа
Str_Hero_Died описывается, как и другие текстовые константы в модуле Texts:
Const Str_Hero_Died = ‘ Герой погиб! ’;
Откомпилируем и запустим
программу. Побродив по игровому пространству, рано или поз дно можно
натолкнуться на ловушку и получить повреждение.
Кроме ловушек мі разместили на
карте еще и источники жизни. Корда герой встает на тайл, его здоровье
увеличивается. Пусть оно увеличивается до миаксимума, потому что мі хотим тобі
источник жизни пропадал после использования. Если же он остается на карте, то
не имеет смысла ограничивать величину восстановления – ведь ничто не мешает не
заходить на тайл источника повторно, до полного оздоровления. Допишем проверку
на тайл, которій вступил герой, следующим образом:
If GameMap[CurMap].Cells[ Heroes[CurHero].x, Heroes[CurHero].y ].Tile in
LiveTileSet then
Begin
ShowInfo(Str_Live);
IncHP( Heroes[CurHero], Heroes[CurHero].MaxHP
);
End;
Модуль тайлов источников
выглядит так (модуль Map):
Const LiveTileSet = {tileLive};
Сообщение об оздоровлении
{модуль Texts} – так:
Const STR_LIVE = ‘ Вы
восстановили здоровье в источнике. ’;
Здоровье увеличивается на
максимальное значение с запасом. Оно будет исправлено на допустимое значение в
процедуре IncHP автоматически. Потестируйте программу – походите по карте,
подрываясь на ловушках и восстанавливая здоровье в источниках жизни.
|