Урок 6. Добавляем средства визуализации карты-2
Во-первых,
обращаю внимание на изменившийся дизайн. Координаты автора дизайна – в конце
рассылки.
Во-вторых, для
подписчиков данной рассылки напомню, что выросла она из Школы программирования
с нуля, поэтому время от времени мы будем затрагивать и старые темы - во всех
рассылках выходит один и тот же текст.
Вопросы
Один из
главных. Turbo Pascal.
В Дельфи ПОКА
этот код работать не будет. Ни в консольной, ни в другой версии – потому что
для вывода на экран нам потребуются примитивные команды работы с текстовой
видеопамятью TurboPascal, которые в Дельфи отсутствуют. Или вроде есть для
Дельфи библиотека, которая позволяет эмулировать работу модулей CRT, DOS?
Какие-то типа WinCtr, WinDos? Очень прошу, кто знает, подскажите плиз для тех,
кто по каким-то причинам не может разобраться или задействовать TurboPascal.
Есть для Дельфи библиотеки эмуляции текстовой dos-графики? Наибольшие трудности
вызвали директивы условной компиляции. Естественно, так как ранее мы этого в
Школе не проходили :)
Смысл директив
заключается в том, что если компилятор берег исходный текст и транслирует его в
машинный, исполнимый код (фактически - создает ехе-файл), то директивы условной
компиляции обрабатываются так называемым препроцессором. Он берет исходный
текст программы для анализа и на выходе тоже формирует исходный текст этой же
программы.
А зачем тогда
он нужен? Вот в нашем примере мы хотим, чтобы один и тот же исходный текст с
минимальными модификациями работал бы (компилировался) в разных системах. Для
этого в исходном тексте будут части, не имеющие отношения к конкретной
операционной системе, а будут и зависимые от нее. Последние надо включать в
исходный текст только с учетом выбранной целевой платформы исполнения (в
зависимости от некоторых условий). Сами эти условия "включает" и
"выключает" программист – надо сделать программу для Windows, он
активизирует настройки для Windows; надо для Linux - активизирует для Linux.
Сначала о настройках. Команда TP/Delphi {$DEFINE имя-константы}
"включает" в программе константу с указанным именем. То есть
считается, что такая константа определена.
{$DEFINE DOS_GAME}
Препроцессор
теперь будет знать, что определена в проекте константа DOS_GAME. Сама по себе
она ничего не значит, может быть либо определенной, либо не быть вообще. Далее
препроцессор (он всегда работает ДО компилятора - компилятолр получает исходный
текст, обработанный препроцессором) ищет в исходном тексте знакомые команды - все, что НЕ начинается с {$стандартная-команда-препроцессора...} он считает
единым текстом, не нуждающемся в обработке. То есть препроцессору все равно, на
Паскале, Си или еще чем-то написан исходный код, он понимает лишь команды
своего препроцессорного языка. Это как бы дополнительный мета-язык обработки
исходных текстов. Понимает препроцессор и условные операторы Это - пожалуй,
его самая главная заслуга.
Основные нужные
нам формы условных операторов препроцессора такие:
{$IFDEF
имя-константы}
{$ENDIF}
{$IFDEF имя-константы}
Препроцессор
выполняет их так. Встречая "IFDEF константа", он проверяет, была ли
ранее свыше по тексту) определена указанная константа, и если да, то включает в
результирующий I 11 пил"] текст
часть текста, располагающуюся далее до команды ENDIF (по умолчанию любой текст
программы, неохваченный командами препроцессора, целиком и без пений сразу
выдается в результат).
Команда INDEF работает наоборот -
включает следующую часть текста до ENDIF, если константа НЕ была определена.
Вот есть такой
текст.
Implementation uses
{$IFDEF DOS_GAME}
CRT;
{$IFENDIF}
Если ранее мы определили
константу препроцессора DOS_GAME командой
DEFINE, то данный условный
оператор IFDEF включит строку с упоминанием модуля CRT в результирующий
исходный текст. А если бы она не была определена (или если бы мы не пользовали
команду IFNDEF), то результирующий текст получился бы таким (с ошибкой, так как
без окончания):
implementation
uses
Во всех модулях
программы удобно использовать единый набор констант препроцессора, чтобы
переопределив константу в одном месте, сразу получить нужную версию кода для компилятора.
Мы собрали эти константы в файле Defines.Inc. Почему такое название? Просто
такое выбрано :)
Defines - определения. Inc -
от include, включать. Обратите внимание на расширение! Это не Defines.inc.TXT или Defines.Inc.Pas, а именно
Defines.Inc - расширение .inc, скорее всего, в Windows не будет ни с чем ассоциировано.
Впрочем, ничто
не мешает взять например название файла Defines.Pas и любое другое. Тогда текст примеров просто придется
немного подправлять.
Значение
содержимого стороннего файла осуществляется в текущий файл командой
препроцессора {$ имя-файла}
Сторонний файл должен находиться в одном каталоге с pas-файлами проекта.
Команду исключения размещаем в самой первой строчке каждого файла, чтобы единый
набор препроцессорных констант автоматически прописывался в начало каждого
модуля.
Насчет модулей.
Сейчас у нас три модуля - главная программа; модуль (файл) map.pas; модуль
(файл) lowlеvel.pas. Когда я пишу "добавляем процедуру А в модуль
map.pas", это значит, что в файл map.pas мы заносим код процедуры А. При
этом занесение такое разделяется на две части:
Весь код
процедуры мы размещаем в разделе implementation;
Заголовок
процедуры мы размещаем в разделе interface.
Добавление
процедуры ShowCcll в модуль LowLevel означает:
Добавили полное
описание процедуры:
Unit LowLevel;
implementation
procedure ShowCell(t: TMapCell; x,y:
Integer);
begin
end;
2. Добавили заголовок в интерфейсный раздел:
unit
LowLevel;
interface
procedure ShowCell(t:
TMapCell; x,y: Integer);
Добавляем средства
визуализации карты-2
Теперь перейдем
к реализации процесса вывода таила карты на экран. Для этого, очевидно, нам
потребуется выполнить некоторую предварительную инициализацию
текстового/графического режима - чтобы например очистить экран, загрузить
изображения и т. д. Создадим в модуле LowEevel процедуру Videolnitialize. Все,
что она будет делать - это очищать
экран. Данная деятельность потребует подключения стандартного в Turbo Pascal
модуля CRT, ответственного за отрисовку в псевдографике, который нужно указать
в заголовке реализации с учетом текущих настроек условной компиляции:
implementation uses
{$IFDEF D0S_GAME}
CRT;
{$ENDIF}
Сама процедура
будет содержать только один вызов стандартной функции очистки экрана:
procedure
Videolnitialize;
begin
ClrScr;
end;
Кроме того, в
заголовок главной программы потребуется добавить ссылку на модуль LowLevel. В
итоге она примет следующий вид:
program LearningRPG;
uses Map, LowLevel;
begin
Randomize;
Videolnitialize;
MapGeneration(1)
;
ShowMap;
readln;
end.
Отметим, что в
данной главной программе никакие директивы условной компиляции не потребуются,
так как для Delphi и Windows вся главная часть программы будет иметь совсем
другой вид (только она одна будет сильно различаться). Соответствующая настройка
проекта должна выполняться с помощью, например, утилиты-менеджера проекта. Мы
до этого в свое время доберемся.
Как проще и
красивее всего представить таил в текстовом режиме? Выбрать для него подходящий
символ и цвет (фоновый цвет всегда будем считать черным). Поэтому поставим в
соответствие каждому тайлу игры запись, состоящую из двух полей - символа для
отображения и цвета этого символа (из доступных стандартных цветов модуля CRT):
type TTileRecord = record
С:
Char;
Clr: Integer
end;
const TileRecords: array[1..tileLast] of TTileRecord =
(
(C: ‘ . ‘
; Clr:Green),
(C: ‘ _ ‘
; ClrrBrown),
(C: ‘ _ ‘ ; Clr:Brown),
:
' л ' ;
ClrrLightGray),
(С:
' ! '
; ClrrLightGreen)
);
Объявление способа представления тайлов в виде константы
имеет еще одно преимущество, иы захотим добавить в игру новый таил, компилятор
при разборе модуля LowLevel означает ошибку. сообщив, что длина данного массива
образов тайлов не соответствует максимальному числу тайлов (указанному в
константе tileLast). Таким образом нам будет автоматически выдаваться
напоминание о необходимости внести изменения в код программы.
С какой части разместить это определение? Так,
как оно привязано к ДОС-у и не используется ни в каких других модулях, кроме
LowLevel, его правильнее всего ввести сразу за директивой implementation -
точнее, после следующей за ней директивы условной компиляции {SIFDEF DOS_GAME}.
Чтобы программа
компилировалась успешно, укажем в заголовке реализации ссылку на модуль Map:
implementation uses Map,
{$IFDEF DOS_GAME}
CRT;
{$ENDIF}
Ссылка на Map
расположена в "независимой" от платформы части кода, потому что модуль
Map не привязан к конкретной операционной системе.
Единственное, что
нам остается сделать для отображения карты - это подготовить реализацию функции
ShowCell. В текстовом режиме данная задача решается элементарно. Договоримся в
начале окна вывода видимой части карты - допустим, это будет точка экрана с
координатами (10,3), для чего опишем в начале ДОС-раздела реализации модуля
LowLevel две константы:
Const WINDOW_LEFT =
10;
WINDOW_TOP = 3;
К этим
начальным координатам будем прибавлять координаты конкретного таила, предварительно
задав его цвет, и затем выводить в нужную позицию окна с помощью стандартной
функции GoToXY - с учетом сдвига видимой части окна, задаваемой переменными
LocalMapLefVLocalMapTop. Цвет элемента сформируем вызовом стандартной процедуры
TextColor.
procedure ShowCell (t: TMapCell;
x,y: Integer);
begin
GoToXY (WINDOW_LEFT+x-GameMap[CurMap].LocalMapLeft,
WINDOW_TOP+y-GameMap[CurMap].LocalMapTop);
TextColor( TileRecords[t.Tile].Clr );
Write( TileRecords[t.Tile].С
);
End;
Запустим
программу - на экране появится изображение части карты! Каждый раз после нового
запуска оно должно быть неповторимым.
Нажмем Enter, чтобы закрыть нашу
программу. С выводом карты мы почти разобрались.
Почему почти? Потому, что пока мы не учитываем видимость тайлов, которая определяется значением флажка isVisible. Добавим соответствующую
проверку - если таил невидим, то вместо символа таила выведем пробел:
procedure ShowCell (t: TMapCell;
x,y: Integer);
begin
GoToXY(
WINDOW_LEFT+x-GameMap[CurMap].LocalMapLeft,
WINDOW TOP+y-GameMap[CurMap].LocalMapTop
);
TextColor ( TileRecords[t.Tile].Clr
);
If t.isVisible then Write(
TileRecords[t.Tile].С )
else
Write( ' '
);
end;
Если теперь
запустить программу, то, естественно, изображение будет пустым, так как
изначально все тайлы карты невидимы - не исследованы.
Какой аспект
игры будем реализовывать дальше? Можно, конечно, заняться расстановкой
монстров, предметов или ловушек, однако, во-первых, они все равно не будут
видны на неисследованной карте, а во-вторых, желательно все же поскорее ввести
в игру главное действующее лицо - героя, и начать им управлять. Поэтому
следующим этапом совершенствования игры будет создание героя.
|