Урок 5. Добавляем средства визуализации карты
а) Надо
различать создание игры и создание движка для игры. Последнее я считаю глупым
занятием, если только его авторы не собираются потом движок продавать (хотя все
равно конкурировать с коллективами десятков математиков сложно, да и продать
хоть что-то НА ПОРЯДОК СЛОЖНЕЕ, чем запрограммировать самый уникальный движок),
или же у них нету собственных денег на лицензирование коммерческих движков.
Хотя и опен-сорсных движков приличного качества достаточно.
б) Движок для
ЗО-игры реального времени и движок пошаговой Цивилизации – вещи очевидно
разные. Для Цивилизации можно хоть на Бейсике писать, все равно будет шустрый Л хороший 3D и на Си трудно, нужна куча ассемблерных вставок.
Мы не будем
делать ЗD-движок реального времени, поэтому возможностей Delphi хватит с избытком.
в) На каком
языке написана библиотека и из какой программы она вызывается, значения не
имеет потому что все вызовы (интерфейс библиотеки) выполнены в едином
стандарте, и функция без разницы, из Си или Паскаля они вызываются. Они даже и
не знают об этом. На скорость работы
библиотечных функций, очевидно, форма их вызова никак не сказывается (если
не принимать в расчет каких-нибудь долей процента из-за разных способов
передачи параметров).
г) Вообще
неправильно исходить из низкоуровневого быстродействия. Всю графическую работу
сегодня берут на себя готовые библиотеки, а программная логика конкретной
создаваемой игры отличается тем, что можно потратить месяц работы на
вылизывание кода, ИЛИ перевод его с Паскаля на Си, получив 5% выигрыша, а можно
потратить неделю на то, чтобы ПОДУМАТЬ как следует и изменить сам АЛГОРИТМ
работы прикладной программы так, что выигрыш составит 50% или 500%.
Добавляем
средства визуализации карты
Продолжаем
это: Программируем карту
…В данной
процедуре оказался не реализованным такой момент, как способ перемещения между
пещерами. Мы договорились, что для этого будут задействованы специальные тайлы-ступеньки,
однако таких входов-выходов должно быть немного. Мастера игростроения
рекомендуют в качестве оптимального варианта размещать на карте два спуска вниз
и один подъем. Последуем этому совету, добавив в конец процедуры следующий код
(и описав дополнительную локальную переменную i):
if
MapLevel < MaxDungeonLevel then
for i
:= 1 to
2 do
begin
FreeMapPoint(x,y);
GameMap[CurMap].Cells[x,y].Tile :=
tileStairsDown;
end;
if MapLevel
> 1 then
begin
FreeMapPoint(x,y);
GameMap[CurMap].Cells[x,y].Tile :=
tileStairsUp;
end;
Отметим, что
упомянутая, но пока не реализованная процедура FreeMapPoint поиска :иободной
точки на карте будет нужна нам и в других точках программы - например, в кжеке
подходящего места для предметов, ловушек и т. п. Поэтому вынесем такой алгоритм
i отдельную процедуру" FreeMapPoint и поместим ее в модуль Map:
procedure FreeMapPoint( var
x,y: Integer );
begin
repeat
x
:=randora(MAP_WIDTH-LOCAL_MAP_WIDTH*2)+LOCAL_MAP_WIDTH;
у :=
random(MAP_HEIGHT - LOCAL_MAP_HEIGHT*2) +LOCAL_MAP_HEIGHT;
until
FreeTile(GameMap[CurMap]
.Cells [x,y] .Tile);
end;
Здесь мы учли
заполненные непроходимыми тайлами сдвиги LOCAL_MAP_WIDTH и LOCAL_MAP_HEIGHT по
краям карты.
Так как при
создании карты локации мы обратились к генератору случайных чисел, то сразу выполним
его инициализацию в главной части программы, чтобы избежать повторений и одинаковых
карт. Заодно добавим в главную программу ссылку на модуль Map и выполним компиляцию
проекта, убедившись, что синтаксических ошибок нет.
program
LearningRPG;
uses Map;
begin
Randomize;
end.
Теперь все
готово для вывода карты на экран. Для этою добавим в модуль Map процедуру
отрисовки всей видимой части. Она будет очень простой - для каждой видимой
ячейки карты вызывается процедура ее вывода.
procedure ShowMap;
var x,y:
Integer;
begin
PrepareMap;
for x :=
GameMap[CurMap].LocalMapLeft to
GameMap[CurMap].LocalMapLeft +
LOCAL_MAP_WIDTH - 1 do
for у
:=
GameMap[CurMap].LocalMapTop to
GameMap[CurMap].LocalMapTop +
LOCAL_MAP_HEIGHT - 1 do
ShowCell(GameMap[CurMap].Cells[x,y],x,y);
end;
Дополнительная
процедура ShowCell добавлена умышленно, так как в игре нам наверняка
понадобится возможность выводить (перерисовывать) отдельные элементы карты. Эта
процедура будет относиться к "низкоуровневым" возможностям программы,
зависящим от конкретной реализации и операционной системы. Где разместить
ShowCell? Давайте подготовим новый модуль LowLevel, в котором будем хранить
весь наш код, зависящий от реализации.
Процедура
PrepareMap требуется нам для того, чтобы учесть особенности реализации графических
функций перерисовки экрана. Так, в ДОС-е нам понадобится предварительно очищать
экран от старой информации, накопленной на предыдущем игровом такте. Каким
образом разделять код, который будет относиться к ДОС-у и к Windows? Правильнее
всего это сделать с помощью команд условной компиляции. Сформируем в каталоге
проекта файл Defines.Inc. в котором поместим следующие строки:
{$DEFINE DOS_GAME}
{ $DEFINE
WIN_GAME}
{$DEFINE RUSDOS_GAME}
{ $DEFINE
RUSWIN_GAME)
Первые две
строчки определяют платформу, для которой собирается проект, вторые две строчки
задают, какой язык будет использоваться в программе для вывода всевозможных
сообщений. Дело в том, что русские кодировки для ДОС-а и Windows различаются,
поэтому нам придется дублировать все сообщения в двух кодировках
(Alternative/IBM и Win 1251). Данная возможность также будет крайне полезна при
переносе нашей программы на другие языки - например, английский.
Обратите
внимание, где поставлен пробел в инструкциях компилятору перед символом $. Если
$ следует не сразу за фигурной скобкой, то он уже не воспринимается как
инструкция, таким образом "включены" DOSGAME и RUSDOSGAME и
"выключены" определения преироцессорных констант, подготовленных для
Windows.
Добавим
ссылку на файл определений в начало модуля LowLevel, и выделим в нем часть,
связанную с ДОС-ом:
{$1 DEFINES.INC)
unit
LowLevel;
interface
procedure
ShowCell(t: TMapCell; x,y: Integer);
implementation
{$IFDEF
DOS_GAME}
procedure ShowCell(t:
TMapCell; x,y: Integer);
begin
end;
{ENDIF}
End.
Ссылку на
модуль Map надо ввести в интерфйсном разделе: interface uses Map;
Размещение
ссылок на рабочие модули программы в интерфейсном разделе потенциально чревато
неприятными ошибками, связанными с закольцованными ссылками. Например, если мы в
дальнейшем определим в интерфейсе модуля LowLevel некий тип или константу, которые
захотим использовать в интерфейсной части модуля Map, то получим запрещенную ситуацию
- два интерфейсных раздела разных модулей ссылаются друг на друга:
unit
LowLevel;
interface
uses Map;
unit Map;
interface uses
LowLevel;
Компилятор не
способен разобраться в такой ситуации и сообщит об ошибке. А когда она уже
возникла, для ее устранения придется довольно долго распутывать ссылающиеся
друг на друга разделы, что нередко приводит к новым проблемам и усложнению
структуры программы. Поэтому таких ситуаций - ссылок на другие модули из секции
interface, желательно всегда избегать.
Наш случай -
исключение. Модуль LowLevel предназначен только для реализации ни многоуровневых
функций вывода на информации экран, зависящих от операционной системы, и
поэтому на него достаточно ссылаться из "внутренности" всех других
модулей раздела Implementation.
Таким же
образом добавим процедуру РгераrеМар. Она будет состоять из одного вызова стандартной
функции очистки окна:
procedure РгераrеМар;
begin
СlrScr;
end;
Теперь укажем
в заголовке реализации модуля Map ссылку на этот модуль: implementation uses
LowLevel;
В главной
части программы теперь можно вызвать процедуру генерации карты (первого ровня
пещеры) и вывода ее на экран. По завершении вывода подождем нажатия на клавишу
Enter:
program
LearningRPG;
uses Map;
begin
Randomize;
MapGeneration
(1) ;
ShowMap;
readln;
end.
Прокомпилируем
и запустим программу. Пока она, конечно, ничего не покажет, так как процудура ShowCell
не содержит никакого кода, но тем не менее генерация карты должна выполняться
успешно.
Далее завершаем
визуализацию карты и начинаем программировать главного героя.
|