Сначала создадим, как обычно, главный файл программы 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 (первая размерность - координата x, вторая - координата y), текущие координаты видимой части карты записываются в поля LocalMapLeft и LocalMapTop (значение левой и верхней координат).
К этому тексту необходимо добавить некоторые важнейшие константы. MAP_WIDTH и MAX_HEIGHT задают размеры глобальной карты (счет начинается с нуля), LOCAL_MAP_WIDTH и LOCAL_MAP_HEIGHT - размеры видимой части.
Обратите внимание, как записаны константы размеров глобальной карты - к значению этих размеров (примем условно карту величиной 32*32 тайла) прибавляется два размера видимой части карты, чтобы, как уже говорилось раньше, эти краевые зоны карты заполнить непроходимыми тайлами и упростить процесс автоматического скроллирования.
Размеры пещеры 32*32 тайла и видимой части 8*8 тайлов конечно невелики, но в нашем примере они физически ограничены размером сегмента данных ДОС-а (64 килобайта). Для Windows-версии эти размеры можно увеличить практически неограниченно. Здесь видно, что размеры этих констант существенно зависят от операционной системы, поэтому сразу выделим их директивами условной компиляции, не забыв добавить в самое начало файла вызов {$I DEFINES.INC} (об этом вызове - в следующем выпуске):
Далее определим, какие значения поля Tile каким тайлам будут равны. Условимся о двух типах свободной местности (например, земля и трава) и двух типах непроходимых зон (например, камни и дерево). Кроме того, подготовим такие тайлы, как "ступеньки вверх" (переход на уровень выше) и "ступеньки вниз" (переход на уровень ниже).
Опишем все тайлы как константы, разделив для удобства на две группы проходимых и непроходимых и добавив дополнительную константу, определяющую границу этих групп:
Такой подход удобен тем, что позволяет добавлять новые тайлы в этот список, не корректирую значения всех других тайлов группы. Достаточно лишь изменить значения констант tileFirstStopTile или tileLast.
Как определить, проходИм ли некоторый тайл? Вынесем такую проверку в отдельную функцию. Содержимое ее будет тривиальным:
function FreeTile(Tile: Integer): Boolean; begin
FreeTile := Tile < tileFirstStopTile; end;
Игровая пещера (последовательность локаций)
Глубину пещеры (семь уровней) зададим в константе
const MaxDungeonLevel = 7;
Опишем также переменную, непосредственно хранящую текущую игровую карту. Она должна быть размещена в интерфейсном разделе, чтобы быть доступной в других модулях. Опишем также особый тип для этой переменной, чтобы без особых затруднений быстро сохранять ее в типизированном файле.
type 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 y := 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;
В качестве параметра указывается уровень в пещере для генерируемой локации.
Первая проверка выясняет, находится ли точка карты (x,y) вблизи границ, которые мы договорились заполнять непроходимыми тайлами. Далее в зависимости от результата датчика случайных чисел выбирается тайл - он может быть либо непроходимым (tileTree) либо одним из двух проходимых. В заключение данный тайл помечается как исходно невидимый.
Начало видимой части сделаем примерно расположенной в центре глобальной карты.
Далее - добавляем средства визуализации карты.
Я отвечаю на вопросы по поводу только текущего выпуска. Когда выйдет следующий выпуск, вопросы по прошлым уже не принимаются. Кто не успел, его проблемы.
Единственная из всех ролевых игр, в которую я играл полгода и так и не прошел до конца - столь глубока :)
Ни одна из коммерческих не тянет по игручести на подобные этим. Предупреждаю - затягивает сильно! :)