Урок 16. Одеваем предметы из рюкзака,
бросаем и подбираем Письма.
Основываясь
на своем опыте, хотелось бы внести ряд предложений:
Если
представить карту в виде массива ссылок, то ограничения на размер в ДОС
полностью снимаются. Точнее в ТР6 они составят что-то около 1М (в незащищенном
режиме).
Предметы (их
свойства), монстров (их хар-ки), описание героев лучше держать во внешних
файлах (например, стандартных БД). Это позволит сразу абстрагироваться от
данных и сосредоточиться на алгоритмах работы.
Ограничивши
себя всего двумя слотами для героя, Вы рискуете потерять качество. В свои годы
я просмотрел достаточное кол-во игр и оцениваю их с точки зрения заложенных функций
и случаются случаи перехода кол-ва в качество.
Рюкзак
(торба, баул) героя могут увеличивать размеры, менять форму (в зависимости от
потребностей) и т.п. Имеет смысл выделить отдельный объект
Характеристики
героя (ну и всех волков), описываются слишком маленькими … это затрудняет
манипуляцию ими при сложном алгоритме. В пределе значения представим ситуацию,
что герой либо здоров, либо мертв.
Вспомним еще
раз, что версия, описываемая в данной рассылке - учебная. Я же об этом писал, и
в контексте про два слота в частности А уж вы сами на базе изученного
сможете что угодно сделать свое. И, воспользоваться правильными советами
Так же
замечу, что параллельно с Вами делаю РПГ-игру на Visual C++. У меня многое
поменялось, в частности, адрес почтового ящика :). Сама же игра тоже обновилась.
Монстры научились бегать, типов монстриков стало больше, увеличилось кол-во
артефактов, появились ловушки и источники жизни. И захотел все перенести на код
текущей версии. Достал со школьного компьютера Borland Pascal, добавил в него Main.pas, и сразу наткнулся на ошибку
невозможности найти файл. После удаления всех директив препроцессора, программа
заработала . Но появились "крякозяблики". Практически сразу стало
понятно: все тексты реализованы в win кодировке. Но, как я понимаю, это
все-таки программа для dos? Надеюсь в дальнейшем таких казусов не будет .
Про кодировку
я тоже писал, - в соответствующем месте - что она в win-формате, и нужно
переделывать. Так что тем, кто хочет просто "посмотреть на "код
текущей статьи рекомендуется посмотреть и текст текущей версии рассылки :)
За файл Defines.inc, СПАСИБО! Добавлен этот файл в
текущий код. Я обычно архив …
Достаем
предметы из рюкзака
Переходим к
перемещению предметов из рюкзака в слоты героя. Расширим процедуру
по схеме, аналогичной
ShowHeroSlots, добавив в нее обработчик нажатий на слоты:
Procedure ShowHeroItems;
Var I,n:integer;
Begin
While true do
Begin
…
…(1,1);
…_Hero_Itmes);
For i:=1 to MaxHeroItems do
Begin
…(…, i+2);
If Heroes[CurHero].Items[i].IType=itemNone then Write(i, ‘ ) ’ ,
STR_EMPTY_ITEM)
Else Write (i, ‘ ) ’ , Heroes[CurHero].Items[i].Name);
End;
end;
GoToXY(1,2 0); Write (
' > ' );
ReadLn(n); if n
= 0 then
Break
end;
ShowGame;
end;
Что надо
делать при выборе пользователем определенного элемента рюкзака? Необходимо
проверить, как и в случае с ShowHeroSlots, не пустой ли это элемент, а также
выяснить, имеются ли подходящие для предмета и свободные слоты. Последнюю
задачу будет решать функция GetFreeSlot, параметром которой выступит предмет.
Функция определит, есть ли у героя подходящий слот, проверив заодно
допустимость перемещения - ведь меч нельзя надеть на голову, а броню использовать
как оружие. Разместим функцию в модуле Неrо:
function GetFreeSlot(
var H: THero;
Itm: TGameltem ):
Integer;
var
i: Integer;
begin
GetFreeSlot :=
0;
for
i := 1 to
MaxSlots do
if
(II. Slots [i] .IType = itemNone)
and GoodSlot (i, Itm) then begin
GetFreeSlot : =
i; Exit end;
end;
В ней
происходит перебор слотов персонажа, и при нахождении свободного слота
(свойство IType имеет значение, отличное от itemNone) выполняется
дополнительная проверка -допустимо ли поместить данный предмет в слот с
индексом i (эти индексы мы обозначили константами с префиксом "slot";
так, слот тела имеет значение индекса slotBody, равное единице). Такую проверку
в соответствии с принципами проектирования "сверху-вниз" вынесем в
новую фуанкцию GoodSlot, размещенную в этом же модуле:
function GoodSlot{Slot: Integer; Itm: TGameltem): Boolean;
begin
GoodSlot := false;
case Slot of slotBody : GoodSlot •=
Itm.IType in [itemArmor]; slotHands: GoodSlot := Itm,IType in [itemHandWeapon];
end;
end;
Для каждого
значения переменной Slot проверим, подходит ли тип предмета назначению данного
слота. На тело героя (slotBody) можно одевать предметы типа "броня"
(itemArmor), в руки (slotHands) можно брать ручное оружие (itemHandWeapon).
Значения itemArmor и itemHandWeapon вынесены в константы-множества, чтобы
данные проверки можно было легко расширять и дополнять. Например, если мы
добавим дальнобойное оружие, то соответствующая проверка перепишется так:
slotHands: GoodSlot
:= Itm.IType in
[itemHandWeapon, itemRangedWeapon];
При добавлении
предметов-плащей (накидок на тело) изменится другая проверка:
slotBody :
GoodSlot := Itm.IType
in [itemArmor, itemCloack];
И так далее.
Теперь
определим, что будет делать программа, когда человек выбрал в рюкзаке предмет
для одевания. Этот предмет надо скопировать из массива Items в массив Slots,
удалив затем его из рюкзака. Procedure ShowHeroItems ;
Var I,n:integer;
Begin
While true do
Begin
…
…
…_Hero_Items);
For i:=1 to MaxHeroItems do
Begin
..ToXY(I, i+2);
If
Heroes[CurHero].Items[i].IType=ItemNonel
then Write(i, ‘ ) ’ ,
STR_EMPTY_ITEM)
Else Write (i, ‘ ) ’ ,
Heroes[CurHero].Items[i].Name);
End;
…(1,20);
Write(‘>’);
…
If n=0 then break;
… FreeSlot(Heroes[CurHero], Heroes[CurHero].Items[n]);
If n>0 then
Begin
Heroes[CurHero].Slots[s]:=Heroes[CurHero].Items[n];
Heroes[CurHero].Items[n]:=IType:=itemNone;
End;
End;
SHowGame;
End;
Пришло время
попрактиковаться в перемещении предметов между рюкзаком и слотами героя. ShowHeroItems/ShowHeroSlots
вводят команды пользователя с помощью оператора Паскаля RеadLn. При этом
введенное в локальную переменную n значение проверяется как на логическую корректность (оно должно
укладываться в определенные диапазоны массивов Slots/Items), так и на
синтаксическую правильность. Если мы попробуем ввести строку типа
"абв", программа аварийно завершится со сопровождающей ошибкой 106
системы Turbo Pascal (Invalid numeric format, неверный формат). Поэтому
читателю предлагается самостоятельно укрепить этот слабый участок кода. Можно например,
реализовать собственный миниатюрный текстовый редактор, обрабатывающий нажатия клавиш,
отображающий их на экране и допускающий простейшие действия.
Последней
среди задач обработки предметов персонажем станет возможность подбирания
предметов с земли и выбрасывание их на землю. Допустим, выбросить предмет из
рюкзака, находясь в рабочем экране процедуры ShowHeroItems.
Ну а
выбрасывать надетые предметы из слотов персонажа мы не будем. Ведь для этого
необходимо предварительно переместить ненужный предмет в рюкзак. В коммерческих
играх приходится перемещать предметы на землю не из рюкзака, а как раз из
слотов – из рук. При желании вы можете
реализовать такую возможность в программе.I
Нужно определить,
что хочет пользователь - переместить предмет из рюкзака в слот или положить его
на землю? Разделим эти два действия, позволив вводить в качестве элемента
рюкзака как положительные, так и отрицательные значения. Введенное значение,
как и раньше, будет означать выбранный номер элемента рюкзака перемещения в
слот, а вот отрицательный (точнее, его модуль, абсолютное значение) предмета в
рюкзаке для выкладывания его на землю.
procedure ShowHeroItems;
var i,n,s: Integer;
begin
while true do
begin
ClrScr;
GoToXY(l,l);
Write(STR_HERO_ITEMS);
for i := 1 to MaxHeroItems do
begin
GoToXY(l,i+2);
if
Heroes[CurHero].Items[i].IType
= itemNone
then Write(i, '
) ' ,STR_EMPTY_ITEM)
else
Write(i, ' )
'
,Heroes[CurHero].Items[i].Name)
end; GoToXY(1,201; Write(
* > '
);
ReadLn(n);
if
n = 0
then
break;
if
n > 0
then
begin
s
:= GetFreeSlot(Heroes[CurHero],Heroes[CurHero].Items[n]); if s
> 0 then
begin
Heroes[CurHero] .Slots [s] :=
Heroes[CurHero].Items[n];
Heroes [CurHero] . Items [n] .
IType :=* itemNone;
end;
end
else
begin
s
:= GetFreeltemNum; if s >
0 then
begin
Items[s] := Heroes[CurHero].Items[abs(n)];
Items[s].x :=
Heroes[CurHero].x;
Items[s].y :=
Heroes[CurHero].y;
Heroes[CurHero].Items[abs(n)].IType :=
itemNone;
end;
end;
end;
ShowGame;
end;
Если
введенное значение положительно, выполняются ранее запрограммированные
действия. Если оно отрицательно, значит, нам надо переместить выбранные предмет
на землю, то есть убрать его из рюкзака и добавить в глобальный массив Items из
модуля Gameltem, откорректировав при этом координаты предмета (поля х и у).
Отрисовка положенного на землю предмета на карте выполнится автоматически в
процедуре ShowGame (точнее, в процедуре Showltems, вызываемой из нее).
Определение свободного места в массиве расположенных на карте предметов
происходит в функции GetFreeltemNum. Ее в соответствии с предназначением надо
реализовать в модуле Gameltem:
function GetFreeltemNum: Integer;
var i:
Integer;
begin
GetFreeltemNum :=
0;
for
i := 1 to
MaxItems do
if Heroes[i].IType = itemNone then
begin
…ItemNum :=
i;
…
End;
Производит
перебор элементов массива Items, и как только находится незанятый – выполнение
функции оканчивается. Отметим, что эта функция может иногда принимать нулевые
значения (свободного места нет из-за ограничений на оперативную память
программы), хотя логика игры полагает, что в данном месте локации вполне можно
располагать предмет на земле.
Очевидного
выхода из такой ситуации нет. Можно только создавать массив Items с достаточно
солидным запасом, что возможно при варианте игры в среде Windows.
При
размещении предмета на карту возможны игровые конфликты, связанные с тем, что
на тайл карты кладется несколько предметов, и при последующих попытках размещения
на карте возникнет неоднозначность (непонятно, какой именно предмет в играх разрешается
класть на один таил сразу много предметов. Вы можете самостоятельно внести в
программу подобное улучшение. Перейдем к процедуре взятия предмета с карты.
Допустим, персонаж находится на тайле карты, который был подсвечен, как
содержащий предмет (символ +).
Создадим
процедуру – назовем ее GetltemFromMap,
будет вызываться при нажатии на клавишу g. Размещаем процедуру в модуле Main):
While true do
Begin
…;
I:=readkey;
Case I of
‘g’: GetItemFromMap;
Начинается ее
реализация (модуль Hero):
Procedure getItemFromMap;
Var I,n:integer;
Begin
…FreeBag(Heroes[CueHero]);
If n=0 then exit;
For i:=1 to MaxItems do
If (Heroes[CurHero].x=Items[i].x)
and (Heroes[CurHero].x=Items[i].x)
then
begin
Heroes[CurHero].Items[n]:=items[i];
Items[i].IType:=itemNone;
(//lt)…
End;
Проверяем,
есть ли у героя в рюкзаке свободное место (функция), если его нет – работа
процедуры заканчивается. Если оно есть, пробегаем по элементам массива Items (предметы,
размещенные на карте), и как только находится место, координаты которого
совпадают с координатами героя, выполняется копирование в массив Items персонажа (в рюкзак), а
элемент глобального массива Items пустой.
Вы можете
протестировать игру, перемещая предметы между слотами и рюкзаком, а также между
рюкзаком и картой - в обе стороны. При этом может выявиться следующие недочеты:
во-первых, в
моменты подъема предметов с земли играющий не получает никакой информации о
том, изменилось ли что-то в игровом пространстве.
Во-вторых,
после того, как персонаж положил предмет на землю, а потом поднял его в том же
месте, и отошел в сторону, оказывается, что на карте в этом месте возникает
пустое место - вместо таила карты выводится пробел.
Первое
недоразумение устраним так. Добавим в процедуру GetltemFromMap информационное
сообщение, извещающее человека о том, что с земли поднят некий
предмет.Константа
STR_GETITEM может быть описана в модуле Texts так:
const STR_GETITEM = ' Вы
подобрали ' ;
Второе
недоразумение решается не менее просто. Достаточно добавить оператор обнуления
поля х освобождаемого элемента массива Items:
procedure GetltemFromMap;
var
i, n: Integer;
begin
n
:= GetFreeBag(Heroes[CurHero]);
if n = 0
then Exit;
for
i := 1 to
Maxltems do
if
(Heroes[CurHero].x = Items[i].x)
and (Heroes[CurHero].у = Items[i].у)
then
begin
Showlnfo( STR__GETITEM
+ Items [i] . Name );
Heroes[CurHero].Items[n] := Items[i];
Items[i].IType := itemNone;
Items[i].x := 0;
Exit
end;
end;
Итак, мы запрограммировали
фактически все необходимые возможности управления нашим героем. Остается только
реализовать алгоритмы сражения с монстрами, и отладить уровни игры Источник: Delgame.at.ua
|