Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Создание ролевой компьютерной игры 6) Добавляем средства визуализации карты-2


Информационный Канал Subscribe.Ru

Создание ролевой компьютерной игры


6) Добавляем средства визуализации карты-2

Во-первых, обращаю внимание на изменившийся дизайн. Координаты автора дизайна - в конце рассылки.

Во-вторых, для подписчиков данной рассылки напомню, что выросла она из Школы программирования с нуля, поэтому время от времени мы будем затрагивать и старые темы - во всех рассылках выходит один и тот же текст.

Вопросы

Один из главных. Turbo Pascal.

Сейчас мы делаем программу, которая будет работать ТОЛЬКО в среде TurboPascal. Это текстовая среда разработки для DOS, и скачать ее официальную версию (она небольшая, мегабайта три) можно свободно например отсюда:

http://community.borland.com/article/0,1410,20803,00.html

Возможно, будет работоспособна программа и в схожей среде FreePascal:

http://freepascal.ru

В Дельфи ПОКА этот код работать не будет. Ни в консольной, ни в другой версии - потому что для вывода на экран нам потребуются примитивные команды работы с текстовой видеопамятью TurboPascal, которые в Дельфи отсутствуют.

Или вроде есть для Дельфи библиотека, которая позволяет эмулировать работу модулей CRT, DOS? Какие-то типа WinCtr, WinDos?

Очень прошу, кто знает, подскажите плиз для тех, кто по каким-то причинам не может разобраться или задействовать TurboPascal. Есть для Дельфи библиотеки эмуляции текстовой dos-графики?

*

Наибольшие трудности вызвали директивы условной компиляции. Естественно, так как ранее мы этого в Школе не проходили :)

Смысл директив заключается в том, что если компилятор берет исходный текст и транслирует его в машинный, исполнимый код (фактически - создает exe-файл), то директивы условной компиляции обрабатываются так называемым препроцессором. Он берет исходный текст программы для анализа и на выходе тоже формирует исходный текст этой же программы.

А зачем тогда он нужен? Вот в нашем примере мы хотим, чтобы один и тот же исходный текст с минимальными модификациями работал бы (компилировался) в разных системах. Для этого в исходном тексте будут части, не имеющие отношения к конкретной операционной системе, а будут и зависимые от нее. Последние надо включать в исходный текст только с учетом выбранной целевой платформы исполнения (в зависимости от некоторых условий). Сами эти условия "включает" и "выключает" программист - надо сделать программу для Windows, он активизирует настройки для Windows; надо для Linux - активизирует для Linux.

Сначала о настройках. Команда TP/Delphi

{$DEFINE имя-константы}

"включает" в программе константу с указанным именем. То есть считается, что такая константа определена.

{$DEFINE DOS_GAME}

Препроцессор теперь будет знать, что определена в проекте константа DOS_GAME. Сама по себе она ничего не значит, может быть либо определенной, либо не быть вообще.

Далее препроцессор (он всегда работает ДО компилятора - компилятолр получает исходный текст, обработанный препроцессором) ищет в исходном тексте знакомые команды :) - все, что НЕ начинается с

{$стандартная-команда-препроцессора ... }

он считает единым текстом, не нуждающемся в обработке. То есть препроцессору все равно, на Паскале, Си или еще чем-то написан исходный код, он понимает лишь команды своего препроцессорного языка. Это как бы дополнительный мета-язык обработки исходных текстов.

Понимает препроцессор и условные операторы :) Это - пожалуй, его самая главная заслуга. Основные нужные нам формы условных операторов препроцессора такие:

{$IFDEF имя-константы}

{$ENDIF}

и

{$IFNDEF имя-константы}

{$ENDIF}

Препроцессор выполняет их так. Встречая "IFDEF константа", он проверяет, была ли ранее (выше по тексту) определена указанная константа, и если да, то включает в результирующий исходный текст часть текста, располагающуюся далее до команды ENDIF (по умолчанию любой текст программы, не охваченный командами препроцессора, целиком и без изменений сразу выдается в результат).

Команда IFNDEF работает наоборот - включает следующую часть текста до ENDIF, если константа НЕ была определена.

Вот есть такой текст.

  implementation uses
  {$IFDEF DOS_GAME}
          CRT;
  {$ENDIF}

Если ранее мы определили константу препроцессора 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 и любое другое. Тогда текст примеров просто придется немного подправлять.

Включение содержимого стороннего файла осуществляется в текущий файл командой препроцессора

{$I имя-файла}

Сторонний файл должен находиться в одном каталоге с pas-файлами проекта. Команду включения размещаем в самой первой строчке каждого файла, чтобы единый набор препроцессорных констант автоматически прописывался в начало каждого модуля.

*

Насчет модулей. Сейчас у нас три модуля - главная программа; модуль (файл) map.pas; модуль (файл) lowlevel.pas. Когда я пишу "добавляем процедуру А в модуль map.pas", это значит, что в файл map.pas мы заносим код процедуры А. При этом занесение такое разделяется на две части:

1. Весь код процедуры мы размещаем в разделе implementation;

2. Заголовок процедуры мы размещаем в разделе interface.

Так, добавление процедуры ShowCell в модуль LowLevel означает:

1. Добавили полное описание процедуры:

  unit LowLevel;

...

  implementation

  procedure ShowCell(t: TMapCell; x,y: Integer);
  begin

  end;

...

2. Добавили заголовок в интерфейсный раздел:

  unit LowLevel;

  interface

  procedure ShowCell(t: TMapCell; x,y: Integer);

*

По старой памяти

"В опубликованнщм письме одного ученика (№10), он пишет, что "многие задачи решаются только с привлечением Асамблера" . Я так понял, что Асамблер - это что-то очень трудное и ужаное. Немогли бы Вы в крадце рассказать об этом языке: его возможностях, особенностях, и за что его так многие не любят".

Ассемблер (от англ. assembly) - это язык программирования, в котором используются команды конкретного процессора (Intel, если типовая персоналка). Эти команды очень просты, поэтому сделать из них более-менее сложную программу сложно.

Команды - уровня "взять число из ячейки 123, сложить с числом из ячейки 345 и результат поместить в ячейку 678. если результат больше нуля, то перейти к команде с номером 162534". итд.

Прогрмма на Паскале фактически и переводится компилятором в набор таких команд. То, что записывавется на Паскале пятью операторам, на ассемблере потребует сотен команд.

Основной плюс ассемблера - что программы на нем получаются очень быстрыми. Ведь транслятор переводит Паскаль на ассемблер (точнее, в двоичный код - каждой команде ассемблера соответствует своя кодировка) "автоматически" :) , не думая, поэтому то, что записано на Паскале и потом переведено в ассемблер, человек, хорошо знающий ассемблер, запрограммировал бы гораздо эффективнее. Но это трудоемко.

На ассемблере пишутся разные системные утилиты, драйверы, программы для встроенных систем, где быстродействие - главное. Хакеры любят ассемблер - ведь чтобы взломать программу - exe-код, представляющий двоичные команды процессора, надо хорошо в них разбираться и понимать, что и как и на что заменить.

Язык Си был придуман именно как удобная высокоуровневая замена ассемблеру, и не зависящая от конкретного процессора. Но и неудобства его определенные также связаны со стремлением поддержать такой, немного ассемблерно-подобный стиль программирования.


Добавляем средства визуализации карты-2

Теперь перейдем к реализации процесса вывода тайла карты на экран. Для этого, очевидно, нам потребуется выполнить некоторую предварительную инициализацию текстового/графического режима - чтобы например очистить экран, загрузить изображения и т. д. Создадим в модуле LowLevel процедуру VideoInitialize. Все, что она будет делать - это очищать экран. Данная деятельность потребует подключения стандартного в Turbo Pascal модуля CRT, ответственного за отрисовку в псевдографике, который нужно указать в заголовке реализации с учетом текущих настроек условной компиляции:

  implementation uses
  {$IFDEF DOS_GAME}
          CRT;
  {$ENDIF}

Сама процедура будет содержать только один вызов стандартной функции очистки экрана:

  procedure VideoInitialize;
  begin
  ClrScr;
  end;

Кроме того, в заголовок главной программы потребуется добавить ссылку на модуль LowLevel. В итоге она примет следующий вид:

  program LearningRPG;
  uses Map, LowLevel;

  begin
  Randomize;
  VideoInitialize;
  MapGeneration(1);
  ShowMap;
  readln;
  end.

Отметим, что в данной главной программе никакие директивы условной компиляции не потребуются, так как для Delphi и Windows вся главная часть программы будет иметь совсем другой вид (только она одна будет сильно различаться). Соответствующая настройка проекта должна выполняться с помощью, например, утилиты-менеджера проекта. Мы до этого в свое время доберемся.

Как проще и красивее всего представить тайл в текстовом режиме? Выбрать для него подходящий символ и цвет (фоновый цвет всегда будем считать черным). Поэтому поставим в соответствие каждому тайлу игры запись, состоящую из двух полей - символа для отображения и цвета этого символа (из доступных стандартных цветов модуля CRT):

  type TTileRecord = record
        C: Char;
        Clr: Integer;
        end;

  const TileRecords: array[1..tileLast] of TTileRecord =
     (
     (C: ' . ' ; Clr:Green),
     (C: ' _ ' ; Clr:Brown),
     (C: ' _ ' ; Clr:Brown),
     (C: ' ^ ' ; Clr:LightGray),
     (C: ' ! ' ; Clr:LightGreen)
     );

Объявление способа представления тайлов в виде константы имеет еще одно преимущество. Когда мы захотим добавить в игру новый тайл, компилятор при разборе модуля LowLevel выдаст ошибку, сообщив, что длина данного массива образов тайлов не соответствует реальному числу тайлов (указанному в константе tileLast). Таким образом нам будет автоматически выдаваться напоминание о необходимости внести изменения в код программы.

В какой части разместить это определение? Так как оно привязано к ДОС-у и не используется ни в каких других модулях, кроме LowLevel, его правильнее всего ввести сразу за директивой Implementation - точнее, после следующей за ней директивы условной компиляции {$IFDEF 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 - с учетом сдвига видимой части окна, задаваемой переменными LocalMapLeft/LocalMapTop. Цвет элемента сформируем вызовом стандартной процедуры 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].C );
  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].C )
     else Write( ' ' );
  end;

Если теперь запустить программу, то, естественно, изображение будет пустым, так как изначально все тайлы карты невидимы - не исследованы.

Какой аспект игры будем реализовывать дальше? Можно, конечно, заняться расстановкой монстров, предметов или ловушек, однако, во-первых, они все равно не будут видны на неисследованной карте, а во-вторых, желательно все же поскорее ввести в игру главное действующее лицо - героя, и начать им управлять. Поэтому следующим этапом совершенствования игры будет создание героя.


(c) 2004-2005 Сергей Бобровский bobrovsky@russianenterprisesolutions.com

Школа программирования с нуля
Все предыдущие выпуски базового курса тут:
http://russianenterprisesolutions.com/sbo/

Дизайн рассылки: Алексей Голубев - Web-дизайн и web-программирование


Subscribe.Ru
Поддержка подписчиков
Другие рассылки этой тематики
Другие рассылки этого автора
Подписан адрес:
Код этой рассылки: comp.soft.prog.prognull.game
Отписаться
Вспомнить пароль

В избранное