Урок 12. Программируем
предметы
Предметы.
На следующем шаге введем в игру
концепцию предметов. Мы договаривались, что в нашем учебном варианте их список
будет ограничен двумя типами - оружием и броней. Приводимые далее приемы
добавления предметов читатель сможет без проблем расширить на свои собственные
типы объектов (всевозможное снаряжение, еда, драгоценности, амулеты и т. п.).
Подготовим модуль Gameltem: unit Gameltem; interface implementation end. Чем
будет характеризоваться предмет? Уникальным идентификатором конкретного
объекта, координатами местоположения на карте, типом предмета (броня, оружие),
названием и набором дополнительных свойств. Заранее определить все мыслимые
свойства очевидно, не удастся, так как мы пока не знаем, какие конкретно типы
предметов будут появляться в нашей игре. В то же время желательно описать
концепцию предмета так, чтобы в дальнейшем она могла быть свободно расширена
под любые объекты, вводимые разработчиком. Поступим так - подготовим два
достаточно больших массива целых и вещественных чисел, и в зависимости от типа
предмета будем по разному трактовать содержимое этих массивов. Размеры массивов
определятся следующими константами: const Maxltemlnt = 20;
MaxReallnt = 20; Тип предмета (оружие или броня) зададим
отдельным типом TGameltemType:
type TGameltemType =
(itemHandWeapon, itemArmor);
Даее нам потребуется тип, описывающий конкретный игровой предмет: type
TGameltem = record ID: Integer; x,y: Integer; IType: TGameltemType; Name :
String[20];
Ints
: array[1..Maxltemlnt] of
Integer; Reals:
array[1..MaxReallnt] of Real; end; Как хранить доступные в
локации предметы? Напрашивающийся способ -
сделать это по аналогии с хранением монстров. То есть прежде всего нам
потребуется массив ItemTypes шаблонов игровых предметов, идеологически схожий с
массивом шаблонов монстров MonslerTypes. Первоначально в нем будет два вида
оружия и два вида брони: const MaxItemTypes
= 4;
ItemTypes:
array[1..MaxItemTypes] of
TGameltem =
( (ID:1;
x:0;
y:0;
IType:itemHandWeapon;
Name:STR_AXE;
Ints: (0, 0,0, 0,0,0, 0,0, 0, 0,0, 0,0, 0,0,
0,0, 0,0,0) ;
Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,
0,0)), (ID:2;
x:0; y:0;
IType:itemHandWeapon;
Name:STR_SWORD;
Ints: (0, 0,0, 0,0, 0,0, 0,0,0,
0,0, 0,0, 0,0, 0,0,0, 0);
Reals (0, 0,0,0,0,0,0, 0,.0,0,0,0,0,
О,О, О,О,О,0,0)).,.
IType:itemArmor;
Name:STR_HELM;
Ints: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
Reals: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0)),
IType:itemArmor;
Name: STR_BODYARMOR;
Ints: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))
);
Значения предметов поместим как обычно в модуль Texts, не забыв сослаться на него в модуля
Gameltem:
Const Str_AXE = '
Топор ' ;
STR_W0RD
= '
Меч ' ;
STR_BODYARMOR = '
Бронежилет ' ;
STR_HELM
= ' Шлем
' ;
Предметы поместим в массив
констант Items.
Const NexItems =
10;
Var Items:
array[1..Maxltems] of TGameltem;
Предметы не всегда будут
существовать в игре. Они могут по различным причинам лежать, портиться, продаваться,
и так далее. Как отличать элементы массива Items, используемые в игре предметы,
от "пустых" предметов? Договоримся, что значение координаты х, равное
нулю, будет означать незадействоваипый, свободный слот в Items.
Определимся со свойствами разных
типов предметов. Оружие будет, очевидно, курироваться величиной наносимого
поражения и вероятностью попадания. Броня характеризуется одним параметром -
"толщиной", или величиной поглощения удара противника (шкуры монстров). Опишем константы,
соответствующие этим свойствам:
Const IntAttack_d1 = 1;
IntAttack_d2 = 2;
IntAttackHit = 3;
IntArmorDefence = 1;
Умения определяют индексы массива
Ints, в которых
хранятся соответсвующие параметры. Так, параметры виртуального броска кубиков
(число кубиков и число граней кубика) будут записываться в первый (IntAttack_d1) и второй (IntAttack_d2), а вероятность опадания в третий (IntAttackHit). Если же тип
элемента – броня, то значение защиты будет размещаться в первом (intArmorDefence)
элементе. Возможные значения этих параметров занесем в наш массив ItemTypes:
ItemTypes: array [1..MaxItemTypes] of TGameItem =
{
(ID:1;
X:0; y:0;
IType:itemHandWeapon;
Name:STR_AXE;
Ints:
(1,6,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
(ID:2;
ITypei.itemHandVte&pon; •
Name:STR_SWORD;
Ints:
(2, 4, 80, 0,0,0, 0,0, 0,0, 0,0, 0,' 0,0, 0,0,0, 0,0);
Reals:
(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)), (ID:3;
x:0;
y:0;
IType:itemArmor;
Name:STR_HELM;
Ints:
(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
Reals:
(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)), (ID:4;
x:0; y:0;
IType:itemArmor;
Name:STR_BODYARMOR;
Ints: (5, 0,0, 0,0, 0,0,0, 0,0,
0,0, 0,0, 0,0, 0,0, 0,0);
Reals: (0,0, 0,0, 0,0, 0,0, 0,0,
0,0, 0,0, 0,0, 0,0, 0,0))
);
To есть меч будет наносить
поражение, равное значению, выпавшему на двух четырехгранных кубиках, а
вероятность попадания составит 80%. А бронежилет сможет погасить пять пунктов
удара противника.
Остается решить следующие
задачи, связанные с предметами: как предметы будут размещаться на карте, как
они будут отрисовываться, как играющий сможет их поднимать с карты и, самое
главное, как герой сможет эти предметы применять.
Разбрасываем предметы по локации
Этот процесс схож с распределением
по локации ловушек. В силу условной ограниченности числа предметов в игре
(десять) продемонстрируем несложный прием расчета вероятности размещения
предмета на тайле в процессе генерации карты. Общее число доступных тайлов в
игре - 32*32 = 1024 (для ДОС-версии). Разделив эту величину на число предметов,
получим 102,4 - это и есть среднее количество тайлов, на которых желательно
разместить один предмет. Отсюда получим вероятность размещения предмета на
конкретном тайле -она будет примерно равна одной сотой.
Следом за оператором, изредка
устанавливающим на свободный таил карты ловушку или источник (процедура
MapGeneration), добавим группу операторов, добавляющую в массив Items случайный
элемент из массива ItemTypes. Переменная п нужна, чтобы следить за постепенным
заполнением массива Items. Правда, наш алгоритм не гарантирует, что все
элементы Hems обязательно получат значения. Поэтому оставшиеся не у дел
"предметы" сделаем невидимыми - занесем в поле х значение ноль, что,
как уже говорилось выше, означает свободный элемент в Items.
procedure MapGeneration(MapLevel: Integer);
var n,i,x,y: Integer;
begin
CurMap := MapLevel;
n := 0;
for x := 1 to MAP_WIDTH do
for у := 1 to MAP_HEIGHT do begin if (x <= LOCAL_MAP_WIDTH) or (x >=
MAP_WIDTH-LOCAL_MAPJflIDTH) or
(y <= LOCAL_MAP_HEIGHT) or (y >=
MAP_HEIGHT-LOCAL_MAP_HEIGHT) then
GameMap[CurMap].Cells[x,y].Tile := tileStone
else begin
If random(100)<35
then GameMap[CurMap].Cells[x,y].Tile :=
tileTree else
if random (2) = 0
then
GameMap.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;
if FreeTile(GameMap[CurMap].Cells[x,y].Tile)
then
if random (100) = 0 then
begin
if n<MaxItems then begin
Iterns[n] := ItemTypes[random(MaxItemTypes)+1];
Items[n].x := x;
Items[n] . у := у;
end;
end;
end;
GameMap[CurMap].Cells [x, y] . IsVisible :=
false;
End;
For i:=n+1
to MaxIterns do
Items[i]. x = 0 ;
Перед попыткой размещения
предмета вставлено условие, согласно которому предмет размещается только на
проходимом для персонажа тайле (что естественно), когда требуется показывать на
экране предметы, расположенные на карте. Как поставим соответствие разным типам
предметов разные символы и цвета их представления. Создадим для
этого массив ItemRecords:
Const ItemRecords:array[TGameItemType] of
TTileRecord=
{
Color: [‘]’; Clr:LightCyan),
Color[‘]’; Clr:LightGreen)
}
Значение массива определяется
структурой типа TGameItemType.
Рассмотрим для отображения
предметов процедуру ShowItem
в модуле LowLevel:
Procedure ShowItem(itm:TGameItem);
Begin
…
(WINDOW_LEFT+itm.x-GameMap[CurMap].LocalMapLeft,
Window_top+itm.y-GameMap[CurMap].LocalMapTop);
… (itemRecords[itm.IType].Clr);
ItemRecords[itm.IType].C)
End;
Вызывается из процедуры ShowItems, которую разместим
в модуле Game, так как
… кода, зависимого от операционной системы:
Procedure ShowItems;
Var …:integer;
begin
for i:=1 to MaxItems do
…IdlePoints[Items[i].x, Items[i].y] then
ShowItem(Items[i]);
End;
Показывать предметы станем сразу
следом за выводом тайлов карты:
procedure ShowGame;
begin
ShowMap;
Showltems;
ShowMonsters;
ShowHero(CurHero);
ShowHeroInfo(CurHero);
end;
Возможен еще один, более
эффективный и простой - но и более ресурсоемкий - способ хранения предметов в
игре. Он подходит для случаев, когда у разработчика заведомо отсутствуют
жесткие ограничения на доступность компьютерных ресурсов. Так, при создании игр
для ДОС-а (и соответственно старых компьютеров) приходится учитывать множество
нюансов, связанных с низким быстродействием процессора (чтоне позволяет
рсализовывать достаточно сложные алгоритмы искусственного интеллекта или в
крайнем случае требует значительных дополнительных усилий по их оптимизации и
использовании ассемблерных вставок), ограниченными объемами оперативной памяти
(что накладывает ограничения на количество и объемы хранимых в программе
массивов и констант) и жесткого диска (что не позволяет создавать качественное
анимационное и музыкальное сопровождение).
При создании Windows-приложений
для современных персональных компьютеров подобные проблемы как правило не
возникают. Программист может резервировать в программе практически
неограниченные массивы данных - до двух гигабайтов. На практике, конечно
приходится исходить из более реалистичных посылок и ориентироваться на
доступные объемы ОЗУ порядка 100-200 мегабайтов, что, впрочем, само по себе уже
очень и очень много.
Как можно было бы реализовать
эффективное хранение предметов на карте? Игровщх предметов на карте может быть
довольно много - сотни, а то и тысячи, поэтому процесс перерисовки всех тайлов,
связанный с многократным выполнением длинного цикла проверки всех предметов (не
находится ли какой-то из них на конкретном месте, как мы сделали в процедуре
Showltems), может вызывать серьезную нагрузку на процессор и заметно 'тормозить
работу программы. Простым решением этой незадачи выглядит расширение структуры
элемента карты TMapCell новым полем, имеющим тип TGameltem. Таким образом мы
получаем возможность хранить один предмет на каждом тайле, получать к нему
мгновенный доступ и проводить другие манипуляции, не связанные с обработкой
массива Items. Однако такой подход накладывает определенные требования к оперативной
памяти - не очень высокие, но весьма сложно реализуемые в ДОС-компиляторе.
Опытные разработчики в таких ситуациях прибегают к средствам динамического
размещения больших массивов в памяти путем запроса соответствующих объемов уже
в ходе работы программы. Это возможный путь, но он не снимает ограничений,
связанных с невозможностью получения в 16-разрядной операционной системе
области памяти, превышающей 64 килобайта. Имеются средства обхода и этого
недостатка (запуск программы в защищенном режиме с помощью дополнительных
драйверов, предоставляющих виртуальный доступ ко всей оперативной памяти),
однако их использование обычно вызывает трудности в настройке у конечных
пользователей. ДОС-платформа была нами выбрана в демонстрационных целях, чтобы
показать идею и способы создания программ, способных "собираться" и
работать в разных операционных системах на основе одного исходного кода. Обход
конкретных ограничений, накладываемые ДОС-ом, мы не будем рассматривать, так
как это устаревшая система.
Тренировка навыков, инвентарь.
|