Урок 8. Программируем главного героя-2
Программируем
главного героя-2
Давайте введем
в программу процедуру, выводящую образ героя на экран. Она будет размещена в
модуле LowLevel, так как зависит от платформы, на которой будет выполняться
игра:
procedure ShowHero(HeroNum: Integer);
begin
GoToXY(
WINDOW_LEFT+Heroes[HeroNum].x-GameMap[CurMap].LocalMapLeft,
WINDOW_TOP+Heroes[HeroNum].y-GameMap[CurMap].LocalMapTop );
TextColor
( White );
Write ( 'X'
)
end;
Герой выводится
белым символом X. Чтобы процедура компилировалась нормально, в список
подключаемых модулей раздела реализации надо добавить модуь Hеrо:
implementation
uses Hero,
{$IFDEF DOS_GAME} CRT;
{$ENDIF}
В каком месте
программы надо выводить образ героя?
Тут мы подходим
к еще одному принципиальному моменту в создании игры. Как уже обсуждалось выше,
процесс отрисовки видимой части карты происходит так - сначала выводится карта
(тайлы), затем предметы, далее - монстры, и наконец герой. Второй и третий
этапы мы пока опускаем, а первый этап уже реализован. Понятно, что для удобства
правильнее всего будет объединить эти этапы в одной процедуре. Эта процедура
ответственна за отображение всей игровой ситуации (возможно, не только видимой части
карты, но и других вспомогательных характеристик), и разместить ее оптимальнее
всего в отдельном модуле, ответственном также за детали управления игрой. Назовем
этот модуль незамысловато - Game:
Unit Game;
Interface
procedure ShowGame;
implementation uses Map,
LowLevel, Hero;
procedure ShowGame;
begin
ShowMap;
Hero (CurHero)
;
End;
End.
Процедура
перерисовки всего экрана ShowGame сначала рисует карту, а потом - героя. В раздел
реализации добавлены ссылки на необходимые модули программы. Процедуру ShowGame теперь можно перенести
в главную программу:
program LearningRPG;
uses Map, LowLevel,
Hero, Game;
begin
randomize;
Videoinitialize;
MapGeration (1);
InitHeroes;
ShowGame;
Readln;
End.
Запустив эту программу, мы получим на экране
уже что-то, напоминающее вид нормальной игры.
Теперь перейдем
к другой важной теме - управлению игрой и прежде всего главным героем. Для
этого нам потребуется функция, принимающая с клавиатуры код нажатой клавиши, и в
зависимости от него вызывающая ту или иную игровую процедуру - в точном
соответствие с принципом событийно-ориентированного программирования. Мы
организуем такой цикл в главной программе, так как прием клавиатурных кодов
выполняется в ДОС-е к мнительно, по ходу выполнения программы, а не в режиме
ожидания уведомляющих сообщений Windows, что требует принципиально другой
организации приложения. При первом приближении реализация управленческого цикла
может выглядеть так (вставляел код восле InitGHeroes, вместо двух последних строк):
while true
do
begin
ShowGame;
k
: ReadKey;
case k of
# 0 : begin
k := ReadKey;
case k of
' К ' : MoveHero( -1,0 ) ;
' M ' : MoveHero( +1,0 );
' H ' : MoveHero ( 0,-1 );
' P ' : MoveHero( 0,+1 );
end;
end;
@27 : break;
end;
end.
Рассмотрим этот
код поподробнее. В начале бесконечного цикла происходит опрос клавиатуры, и в
зависимости от нажатой клавиши происходит вызов той или иной функции программы.
Значение #0 означает, что нажата специальная клавиша (функциональная или
клавиша-стрелка), поэтому необходимо считать дополнительный клавиатурный код.
Значение #27 соответствует клавише Escape, значения К, М, N, Р - стрелкам
"влево", "вправо", "вверх" и "вниз".
Чтобы переместить героя по карте, надо вызвать процедуру MoveHero, которая
получает в качестве параметров сдвиг относительно текущего положения героя по
горизонтали и вертикали. Так, вызов MoveHero( +1,0 ) должен сдвинуть героя на карте на один таил вправо
(координата х увеличится на +1, а координата у не изменится).
Обратите
внимание, что в начале цикла стоит вызов процедуры ShowGame - здесь происходит
перерисовка экрана с учетом всех внесенных после действий пользователя
изменений в игре.
Реализацию
процедуры MoveHero разместим в модуле Game, не зависящем от платформы. Нам
удастся обойтись уже готовыми функциями отрисовки, подготовленными в модуле
LowLevel.
procedure MoveHero( dx,dy:
Integer );
begin
if not
FreeTile{ GameMap[CurMap].Cells[
Heroes[CurHero].x+dx,Heroes[CurHero].y+dy].Tile
) then Exit;
inc(Heroes[CurHero].x,dx);
inc(Heroes[CurHero].y,
dy) ;
SetHeroVisible(CurHero);
end;
Этот код
достаточно тривиален - сначала проверяется, свободен ли таил, в направлении
которого выполняется перемещение героя. Если он свободен, происходит изменение
координат героя. В новом месте уточняется, какие тайлы вокруг героя стали
видимыми. Однако данная процедура несмотря на свою простоту станет одним из
ключевых функциональных элементов в программе. Дело в том, что все игровые
коллизии мы будем определять прежде всего в MoveHero - основной процедуре,
ответственной за перемещение героя и взаимодействие с окружающим миром, в том
числе и сражения с монстрами. Уже в таком виде программа будет работоспособна -
герой будет перемещаться по карте при нажатии на клавиши-стрелки. Однако он
пока будет некорректно выходить за пределы окна видимой части карты, так как
пока не выполняется прокрутка этого окна при приближении героя к его границам.
Поэтому этот аспект программы нам необходимо доработать в первую очередь.
Исходный код
текущей версии: И его замечание:
PS: да, хочу
дать небольшой совет. При размещении основных важных объектов(и особенно
"порталов" для перехода на другие уровни) нужно ставить их так, чтобы
герой смог до них добраться - иначе примерно в 20% случаев он оказывается
"запертым" на уровне - проверено на собственном опыте. Я обхожу это
так - создаю "карту доступа"(двумерный массив с такими же размерами,
как и сама игровая карта; элементы массива имеют тип bool), который показывает
доступность/недоступность ячеек игровой карты. И размещаю объекты (монстры,
etc) в соответствии с этой информацией. Сама функция создания карты доступа
занимает несколько строк - можете посмотреть в исходниках.
Да, конечно, проблема
тупика время от времени возникает. Наш код - учебный, и что вы его развиваете -
замечательно! А вот комментарий Дмитрия:
Процесс
создания игры продолжается, герой уже создан, и отображается на экране. Возникли
следующие предложения:
1) характеристику
"сила зрения" (поле VisLong) включить в список характеристик героя массив
Chars). В этом случае можно будет менять силу зрения героя как другие атрибуты,
например, в случае, если его подвергли действию заклинания "Слепота"
или он выпил бутылочку усилителя зрения или инфравидения.
2) при задании
списка констант их молено описывать следующим образом: значение каждой следующей
= значение предыдущей + 1. Т.е.:
statMin=1;
statSTR=statMin;
statDEX=statStr+1;
statCon=statDex+1;
statInt= statCon+1;
statSignStr= statInt+1;
statHealth=satSignStr+1
statMax = slatHeallh;
Плюсы - всегда
известно минимальное и максимальное значение константы, легко Фильтровать
последовательность значений, например, при добавлении после statCon новой
константы statWill (воля), добавлется одна строка statWill = statCon+1, и
меняется следующая строка statlnt = slatWill+1. Остальные константы меняют свое
значение автоматически. Минусы - надо более тщательно следить за правильной последовательностью
констант и приращений. Например, если забыть изменить в следущем примере
значение statlnt - появятся две константы с разными именами и одинаковым
значением.
Но в то же
время такой подход удобен для циклической обработки всех значений. Например, я использовал
такой подход для вывода характеристик героя:
With FMain.mm_Stats.Lines do
begin
Clear;
for
i:=statMin to statMax do
Add(Format( '
%10s %d ' ,
[StatNames[i],С Stats[i]]));
Этот цикл
работает для любого количества характеристик героя (здесь я пользуюсь значением
Stat вместо Chars). Метод вывода героя на экран помещен в LowLevel. В даном
примере С - переменная героя (передается как параметр). Вывод осуществляется в компонент
Memo (mm_Stats) главной формы (FMain)
Я отказался от
конкретной записи для героя и обобщил на случай любого существа Preatufe, тo есть любой монстр характеризуется теми
же характеристики, что и герой, но другими значениями этих характеристик!).
То есть описывается
отдельный метод для вывода любого существа, и он используется как для вывода
героя, так и для вывода монстра.
Прошу прощения,
что пользуюсь отличающимися от Ваших обозначениями. Если мой усложняет
понимание, напишите - я буду изменять в соответствии с Вашими исходными
текстами.
Над
универсальным сущестном. я думал, но решил все же разделить - у героя и у
монстров могут быть те же специфические потребности, для которых нужны особые
поля. В конкретной модели это возможно реализовывалось бы и легче, но появились
бы другие заморочки
|