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

Создание ролевой компьютерной игры 16) Одеваем предметы из рюкзака, бросаем и подбираем


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

Разработка ролевой игры

16) Одеваем предметы из рюкзака, бросаем и подбираем

Письма.

Основываясь на своем опыте, хотелось бы внести ряд предложений:
1) Если представить карту в виде массива ссылок, то ограничения на размер в ДОС полностью снимаются. Точнее в TP6 они составят что-то около 1М (в незащищенном режиме).
2) Предметы (их свойства), монстров (их хар-ки), описание героев лучше держать во внешних файлах (например, стандартных БД). Это позволит сразу абстрагироваться от конкретики и сосредоточиться на алгоритмах работы.
3) Ограничивая себя всего двумя слотами для героя, Вы рискуете потерять качество. В свое время я просмотрел достаточное кол-во игр и оцениваю их с точки зрения заложенных идей. Встречаются случаи перехода кол-ва в кач-во.
4) Рюкзак (субка, торба, баул) героя могут увеличивать размеры, менять форму (в зависимости от потребностей) и т.п. Имеет смысл выделить отдельный объект (структуру).
5) Начальные хар-ки героя (ну и всех волков), описываются слишком маленькими значениями, что затрудняет манипуляцию ими при сложном алгоритме. В пределе значения 0-1 дадут ситуацию Герой либо здоров, либо мертв.
Андрей

Скачать вариант Андрея можно тут:
http://russianenterprisesolutions.com/sbo/download/ssg.zip 23 kb

Напоминаю всем еще раз, что версия, описываемая в данной рассылке - учебная. Я же специально об этом писал, и в контексте про два слота в частности :) А уж вы сами на базе этого кода можете что угодно сделать свое. И, воспользоваться правильными советами Андрея.

От Павла письмо.

Я как-то писал вам, что параллельно с Вами делаю РПГ-игру на Visual C++.
С тех пор у меня многое поменялось, в частности, адрес почтового ящика :). Сама же программа тоже обновилась. Монстры научились бегать, типов монстриков стало больше, увеличилось кол-во артефактов, появились ловушки и источники жизни. И захотел я посмотреть на "код текущей версии". Достал со школьного компьютера Borland Pascal 7.0, загрузил в него Main.pas, и сразу наткнулся на ошибку невозможности найти файл defines.inc. После удаления всех директив препроцессора, программа заработала :) . Но вместо надписей появились "крякозяблики". Практически сразу стало понятно: все тексты в texts.pas написаны ... в win кодировке. Но, как я понимаю, это все-таки программа для dos? Надеюсь, что в дальнейшем таких казусов не будет :) .

Про ДОС-кодировку я тоже писал, - в соответствующем месте :) - что она в win-формате, и ее надо ручками переделывать. Так что тем, кто хочет просто "посмотреть на "код текущей версии"" - также рекомендуется посмотреть и текст текущей версии рассылки :)

Ох, по поводу defines.inc, СПАСИБО! Добавлен этот файл в текущий код. Я обычно архив делаю командой

pkzip xxx.zip *.pas
:)

И присылайте ваш код на С++, опубликуем.

Одеваем предметы из рюкзака

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

  procedure ShowHeroItems;
  var i,n: Integer;
  begin

  while true do
  begin
  ClrScr;
  GoToXY(1,1);
  Write(STR_HERO_ITEMS);
  for i := 1 to MaxHeroItems do
    begin
    GoToXY(1,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,20); Write( ' > ' );
  ReadLn(n); if n = 0 then Break

  end;

  ShowGame;
  end;

Что надо делать при выборе пользователем определенного элемента рюкзака? Необходимо проверить, как и в случае с ShowHeroSlots, не пустой ли это элемент, а также выяснить, имеются ли подходящие для предмета и свободные слоты. Последнюю задачу будет решать функция GetFreeSlot, параметром которой выступит предмет. Функция определит, есть ли у героя подходящий слот, проверив заодно допустимость перемещения - ведь меч нельзя надеть на голову, а броню использовать как оружие. Разместим функцию в модуле Hero:

  function GetFreeSlot( var H: THero; Itm: TGameItem ): Integer;
  var i: Integer;
  begin
  GetFreeSlot := 0;
  for i := 1 to MaxSlots do
    if (H.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: TGameItem): 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,s: Integer;
  begin

  while true do
  begin
  ClrScr;
  GoToXY(1,1);
  Write(STR_HERO_ITEMS);
  for i := 1 to MaxHeroItems do
    begin
    GoToXY(1,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,20); Write( ' > ' );
  ReadLn(n); if n = 0 then break;

  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;

  ShowGame;
  end;

Вы можете попрактиковаться в перемещении предметов между рюкзаком и слотами героя.

Процедуры ShowHeroItems/ShowHeroSlots вводят команды пользователя с помощью стандартного оператора Паскаля ReadLn. При этом введенное в локальную переменную n значение не проверяется как на логическую корректность (оно должно укладываться в допустимые диапазоны массивов Slots/Items), так и на синтаксическую правильность. Если вместо числа мы попробуем ввести строку типа "абв", программа аварийно завершится со стандартной ошибкой 106 системы Turbo Pascal (Invalid numeric format, неверный формат числа). Поэтому читателю предлагается самостоятельно укрепить этот слабый участок программы. Можно, например, реализовать собственный миниатюрный текстовый редактор, отслеживающий нажатия клавиш, отображающий их на экране и допускающий простейшие исправления.

Бросаем предметы

Наконец, последней среди задач обработки предметов персонажем станет возможность поднятия предметов с земли и выбрасывание их на землю. Допустим, выбросить предмет из рюкзака, находясь в рабочем экране процедуры ShowHeroItems.

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

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

  procedure ShowHeroItems;
  var i,n,s: Integer;
  begin

  while true do
  begin
  ClrScr;
  GoToXY(1,1);
  Write(STR_HERO_ITEMS);
  for i := 1 to MaxHeroItems do
    begin
    GoToXY(1,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,20); 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 := GetFreeItemNum;
    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 из модуля GameItem, откорректировав при этом координаты предмета (поля x и y). Отрисовка положенного на землю предмета на карте выполнится автоматически в процедуре ShowGame (точнее, в процедуре ShowItems, вызываемой из нее). Определение свободного места в массиве расположенных на карте предметов происходит в функции GetFreeItemNum. Ее в соответствии с предназначением надо реализовать в модуле GameItem:

  function GetFreeItemNum: Integer;
  var i: Integer;
  begin
  GetFreeItemNum := 0;
  for i := 1 to MaxItems do
    if Items[i].IType = itemNone then
       begin
       GetFreeItemNum := i;
       Exit
       end;
  end;

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

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

Теперь перейдем к процедуре взятия предмета с карты. Допустим, персонаж находится на некотором тайле карты, который был подсвечен, как содержащий предмет (символ +).

Такая процедура - назовем ее GetItemFromMap, будет вызываться при нажатии на клавишу g (главная процедура программы в модуле Main):

  ...
  while true do
    begin
    ShowGame;
    k := ReadKey;
    case k of

       ' g ' : GetItemFromMap;
  ...

Вот как запишется ее реализация (модуль Hero):

  procedure GetItemFromMap;
  var i, n: Integer;
  begin
  n := GetFreeBag(Heroes[CurHero]);
  if n = 0 then Exit;
  for i := 1 to MaxItems do
    if (Heroes[CurHero].x = Items[i].x) and
       (Heroes[CurHero].y = Items[i].y) then
       begin
       Heroes[CurHero].Items[n] := Items[i];
       Items[i].IType := itemNone;
       Exit
       end;
  end;

Прежде всего мы проверяем, есть ли у героя в рюкзаке свободное место (функция GetFreeBag). Если его нет, то работа процедуры заканчивается. Если оно есть, пробегаем по всем элементам массива Items (предметы, размещенные на карте), и как только находится элемент, координаты которого совпадают с координатами героя, выполняется копирование этого предмета в массив Items персонажа (в рюкзак), а элемент глобального массива Items помечается как "пустой".

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

Первое недоразумение устраним так. Добавим в процедуру GetItemFromMap информационное сообщение, извещающее человека о том, что с земли поднят некий предмет.

Константа STR_GETITEM может быть описана в модуле Texts так:

  const STR_GETITEM = ' Вы подобрали ' ;

Второе недоразумение решается не менее просто. Достаточно добавить оператор обнуления поля x освобождаемого элемента массива Items:

  procedure GetItemFromMap;
  var i, n: Integer;
  begin
  n := GetFreeBag(Heroes[CurHero]);
  if n = 0 then Exit;
  for i := 1 to MaxItems do
    if (Heroes[CurHero].x = Items[i].x) and
       (Heroes[CurHero].y = Items[i].y) then
       begin
       ShowInfo( STR_GETITEM + Items[i].Name );
       Heroes[CurHero].Items[n] := Items[i];
       Items[i].IType := itemNone;
       Items[i].x := 0;
       Exit
       end;
  end;

Итак, мы запрограммировали фактически все необходимые возможности управления нашим героем. Остается только реализовать алгоритмы сражения с монстрами, и отладить уровни игры.


Исходный код текущей версии (всегда проверен и работоспособен, главный файл- main.pas):

http://russianenterprisesolutions.com/sbo/download/8115.zip 7900 байтов

Далее: Готовимся к схватке.


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

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

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


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

В избранное