Урок 4. Программируем карту
Программируем карту
Сначала
создадим, как обычно, главный файл программы main.pas:
program LearningRPG;
begin
end.
Затем добавим к проекту модуль map.pas:
unit
Map;
interface
implementation
end.
Мы
договорились, что будем работать с записями, избегая объектной технологии.
Поэтому опишем новые типы - ячейку карты, и саму карту, как обычные записи:
type
TMapCell = record
Tile:
Integer; IsVisible:Boolean
end;
TMap = record
Cells: array[1.,MAP_WIDTH,1..MAP_HEIGHT] of TMapCell;
LocalMapLeft, LocalMapTop:
Integer-end;
Здесь поле Tile
хранит значение таила ячейки карты, IsVisible определяет, виден ли таил. Все
ячейки хранятся в массиве Cells (первая размерность - координата х, вторая - координата
у), текущие координаты видимой части карты записываются в поля LocalMapLeft и LocalMapTop
(значение левой и верхней координат). К этому тексту необходимо добавить
некоторые важнейшие константы. MAP_WIDTH и MAX_HEIGHT задают размеры глобальной
карты (счет начинается с нуля), LOCAL_MAP_W!DTH и LOCAL_MAP_HEIGHT - размеры видимой части.
const
LOCAL_MAP_WIDTH = 8;
LOCAL_MAP_HEIGHT = 8;
MAP_WIDTH =
32 + L0CAL_MAP_WIDTH*2;
MAP_HEIGHT =
32 + L0CAL_MAP_HEIGHT*2;
Обратите
внимание как записаны константы размеров глобальной карты - к значению этих
размеров (примем условную карту величиной 32*32 тайла) прибавляется два размера
видимой части карты чтобы как уже говорилось раньше, эти краевые зоны карты
заполнить непроходимыми тайлами и упростить процесс автоматического
скроллирования.
Размер карты 32*32 тайла и видимой части 8*8
тайлов конечно невелики, но в нашем случае они физически ограничены размером
сегмента данных ДОС-а (64 килобайта). Для Windows игры размеры можно увеличить
практически неограниченно. Здесь видно, как размеры этих констант существенно
зависят от операционной системы, поэтому сразу определимся с директивами
условной компиляции, не забыв добавить в самое начало файла {$ DEFINEC.INC} (об этом вызове - в следующем
выпуске):
{$IFDEF DOS_GAME}
LOCAL_MAP_WIDTH = 8;
LOCAL_MAP_HEIGHT = 8;
MAP_WIDTH = 32+LOCAL_MAP_WIDTH*2;
MAP_HEIGHT = 32+LOCAL_MAP_HEIGHT*2;
{$ ENDIF}
Далее определим
какие значениям поля Tile каким тайлам будут равны. Условимся о двух типах покрытиях
местности (например, земля и трава) и двух типах непроходимых зон(камни и
дерево) Кроме того, подготовим такие тайлы, как "ступеньки вверх" (переход
на уровень выше) и «ступеньки вниз» (переход на уровень ниже).
Далее все нужные нам константы разделим для
удобства на две группы проходимых и непроходимых типов и добавив дополнительную
константу, определяющую границу этих групп:
TileGrass=1;
TileGround=2;
TileStairsUp=3;
TileStairsDown=4;
TileFirstStopTile=5;
TileTree = TileFirstStopTile;
TileStone = TileFirstStopTile+1;
TileLast = TileFirstStopTile+1;
Этот вариант
удобен тем, что позволяет добавлять новые тайлы в этот список, не требуя
переписывания всех других тайлов группы.
Достаточно лишь изменить значения TileFirstStopTile или TileLast.
Как узнать, проходимый ли некоторый таил?
Вынесем такую проверку в отдельную функцию. Содержание ее будет тривиальным: (внимание
в внутри функции в выделенной области возможны
ошибки)
Function FreeTile(Tile : Integer) : Boolean;
Begin
FreeTile := Tile < tileFirstStopTile;
End;
(На делфи данная
функция сработа, на паскале – не тестировал)
Последовательность
локаций
Количество
уровней (семь уровней) зададим в константе
MaxDungeonLevel=7;
Создадим
переменную хранящую текущую игровую карту. Она будет обьявлена в интерфейсном
разделе, чтобы быть доступной в других модулях. Создадим массив для этой
переменной, чтобы без особых затруднений быстро работать в заполняемом файле.
TGameMap: array[1..MaxDungeonLevel]
of TMap;
Var
GameMap:TGameMap;
CurMap:integer;
Переменная CurMap
будет хранить номер текущей локации (элемента массива GameMap), используемой в
игре.
Следующий шаг -
генерация карты. Для этого существует немало хороших алгоритмов, причем все они
отличаются достаточно высокой сложностью. Мы возьмем простой вариант и будем
расставлять непроходимые тайлы на пустой карте случайно. Практика показывает,
что если на карте около 35% непроходимых, случайно "набросанных"
тайлов, то перемещаться по ней становится ничуть не легче, чем по лабиринту. Запишется
процедура генерации игровой карты так:
procedure MapGeneration{MapLevel: Integer);
var x,y: Integer;
begin
CurMap := MapLevel;
for x := 1 to
MAP_WIDTH do for у :=
1 to MAP_HEIGHT do
begin
if
(x <= LOCAL_MAP_WIDTH) or
(x >= MAP_WIDTH-LOCAL_MAP_WIDTH) 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[CurMap].Cells[x,y].Tile :=
tileGrass
else
GameMap[CurMap].Cells[x,y].Tile :=
tileGround;
end;
GameMap[CurMap].Cells[x,y].IsVisible := false;
end;
GameMap[CurMap].LocalMapLeft := MAP_WIDTH div 2;
GameMap[CurMap].LocalMapTop := MAP_HEIGHT div 2;
end;
В качестве параметра указывается уровень в пещере для
генерируемой локации. Первая проверка выясняет, находится ли точка карты
(х,у) вблизи границ, которые мы договорились заполнять непроходимыми тайлами.
Далее в зависимости от результата датчика случайных чисел выбирается таил - он
может быть либо непроходимым (tileTree) либо одним из двух проходимых. В
заключение данный таил помечается как исходно невидимый.
Начало видимой
части сделаем примерно расположенной в центре глобальной карты. Далее - добавляем
средства визуализации карты.
|