Нужны ли отчеты и будут ли задания на текущем курсе, где мы создаем игру? Отчеты желательны, если у вас что-то не получается или не ясно. Заданий как таковых не будет, но будут уточнения к различным фазам игры - расширение дополнительными возможностями, которые в курсе не рассматриваются.
Дмитрий отметил, что русифицированная версия "Мории" не запускается. Об этом писали и другие, сначала я думал, что не запускается в XP SP2, но когда скачал архив - да, он распаковался с ошибкой и программа не запустилась. Дмитрий проявил настойчивость :) , за что ему благодарность, и теперь проверенный архив можно скачать по старому адресу http://russianenterprisesolutions.com/download/um.zip.
Кроме того, есть замечательный сайт
http://nethack.ru/ целиком посвященный еще одной культовой "рогульке" НетХак (но она не про хакеров, а, конечно, по толкинским мотивам :). Правда, переведенного на русский, там вроде пока немного. Всего было два основных направления в roguelike-играх - Moria, NetHack, и лет десять назад добавился ADOM.
Спрашивали, почему выбрана Delphi/TurboPascal, ведь "про OpenGL говорят, что на Си проги будут действовать быстрее, потому что сама библиотека написана на Си".
а) Надо различать создание игры и создание движка для игры. Последнее я считаю глупым занятием, если только его авторы не собираются потом движок продавать (хотя все равно конкурировать с коллективами десятков математиков сложно, да и продать хоть что-то НА ПОРЯДОК СЛОЖНЕЕ, чем запрограммировать самый уникальный движок), или же у них нету собственных денег на лицензирование коммерческих движков. Хотя и опен-сорсных движков приличного качества достаточно.
б) Движок для 3D-игры реального времени и движок пошаговой Цивилизации - вещи очевидно разные. Для Цивилизации можно хоть на Бейсике писать, все равно будет шустрый :) А хороший 3D и на Си трудно, нужна куча ассемблерных вставок.
Мы НЕ будем делать 3D-движок реального времени, поэтому возможностей Delphi хватит с избытком.
в) На каком языке написана библиотека и из какой программы она вызывается, значения не имеет. Потому что все вызовы (интерфейс библиотеки) выполнены в едином стандарте, и функциям без разницы, из Си или Паскаля они вызываются. Они даже и не знают об этом. На скорость работы библиотечных функций, очевидно, форма их вызова никак не сказывается :) (если не принимать в расчет каких-нибудь долей процента из-за разных способов передачи параметров).
г) Вообще неправильно исходить из низкоуровневого быстродействия. Всю графическую работу сегодня берут на себя готовые библиотеки, а программная логика конкретной создаваемой игры отличается тем, что можно потратить месяц работы на вылизывание кода, или перевод его с Паскаля на Си, получив 5% выигрыша, а можно потратить неделю на то, чтобы ПОДУМАТЬ как следует и изменить сам АЛГОРИТМ работы прикладной программы так, что выигрыш составит 50% или 500%.
Анонсы
На сайте
http://www.alex-world.nm.ru что-то организуется типа альянса рассылок по теме разработки комп. игр. Так, там есть HEROES 5 - НОВЫЕ ИДЕИ, ЧЕГО НАМ НЕ ХВАТАЕТ В ИГРАХ и др.
...В данной процедуре оказался не реализованным такой момент, как способ перемещения между пещерами. Мы договорились, что для этого будут задействованы специальные тайлы-ступеньки, однако таких входов-выходов должно быть немного. Мастера игростроения рекомендуют в качестве оптимального варианта размещать на карте два спуска вниз и один подъем. Последуем этому совету, добавив в конец процедуры следующий код (и описав дополнительную локальную переменную 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 поиска свободной точки на карте будет нужна нам и в других точках программы - например, в поиске подходящего места для предметов, ловушек и т. п. Поэтому вынесем такой алгоритм в отдельную процедуру FreeMapPoint и поместим ее в модуль Map:
procedure FreeMapPoint( var x,y: Integer ); begin
repeat
x := random(MAP_WIDTH - LOCAL_MAP_WIDTH*2) +
LOCAL_MAP_WIDTH;
y := 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 y := 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 и Win1251). Данная возможность также будет крайне полезна при переносе нашей программы на другие языки - например, английский.
Обратите внимание, где поставлен пробел в инструкциях компилятору перед символом $. Если $ следует не сразу за фигурной скобкой, то он уже не воспринимается как инструкция, таким образом "включены" DOS_GAME и RUSDOS_GAME и "выключены" определения препроцессорных констант, подготовленных для Windows.
Добавим ссылку на файл определений в начало модуля LowLevel, и выделим в нем часть, связанную с ДОС-ом:
{$I 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.
Таким же образом добавим процедуру PrepareMap. Она будет состоять из одного вызова стандартной функции очистки окна:
procedure PrepareMap; begin
ClrScr; end;
Теперь укажем в заголовке реализации модуля Map ссылку на этот модуль:
implementation uses LowLevel;
В главной части программы теперь можно вызвать процедуру генерации карты (первого уровня пещеры) и вывода ее на экран. По завершении вывода подождем нажатия на клавишу Enter:
program LearningRPG;
uses Map;
begin
Randomize;
MapGeneration(1);
ShowMap;
readln; end.
Перекомпилируем и запустим программу. Пока она, конечно, ничего не покажет, так как процедура ShowCell не содержит никакого кода, но тем не менее генерация карты должна выполниться успешно.
Далее - завершаем визуализацию карты и начинаем программировать главного героя