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

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


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

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

15) Рюкзак и инвентарь-2

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

  procedure InitHero(HeroNum: Integer);
  var i: Integer;
  begin

  with Heroes[HeroNum] do
    begin
    for i := 1 to MaxChars do
      Chars[i] := 0;
    for i := 1 to MaxSkills do
      Skills[i] := BaseSkill_Table[i];
    for i := 1 to MaxHeroItems do
      Items[i].IType := itemNone;
    Items[1] := ItemTypes[1];
    Items[2] := ItemTypes[4];

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

Для этого нам также потребуется еще один массив, который мы назовем Slots, элементы которого будут соответствовать предметам, непосредственно используемым героем. Это может быть броня или оружие. В коммерческих играх список носимых предметов обычно расширяется амулетами, кольцами, цепочками, колчаном, оружейным поясом, отдельными элементами брони для рук (наручи), ног (поножи), специализированными видами оружия и т. д. Читатель может самостоятельно реализовать в игре такую возможность по описанной далее схеме.

Опишем массив Slots в структуре THero:

  const MaxSlots = 2;
        slotBody = 1;
        slotHands = 2;

  type THero = record
       Chars : array[1..MaxChars] of Integer;
       Skills: array[1..MaxSkills] of Integer;
       Items : array[1..MaxHeroItems] of TGameItem;
       Slots : array[1..MaxSlots] of TGameItem;
  ...

Пока мы отвели герою два слота (броня на теле и оружие в руках), доступ к которым может происходить с помощью индексов slotBody и slotHands. Проинициализируем этот массив в процедуре InitHero:

  procedure InitHero(HeroNum: Integer);
  var i: Integer;
  begin

  with Heroes[HeroNum] do
    begin
    for i := 1 to MaxChars do
      Chars[i] := 0;
    for i := 1 to MaxSkills do
      Skills[i] := BaseSkill_Table[i];
    for i := 1 to MaxHeroItems do
      Items[i].IType := itemNone;
    Items[1] := ItemTypes[1];
    Items[2] := ItemTypes[4];
    for i := 1 to MaxSlots do
      Slots[i].IType := itemNone;

Как и в случае с рюкзаком, массив слотов просто представим набором "пустых" предметов.

Для отображения носимых героем предметов подготовим процедуру ShowHeroSlots (модуль LowLevel):

  procedure ShowHeroSlots;
  var i: Integer;
  begin
  ClrScr;
  GoToXY(1,1);
  Write(STR_HERO_SLOTITEMS);
  for i := 1 to MaxSlots do
    begin
    GoToXY(1,i+2);
    Write(SlotName[i], ' : ' );
    if Heroes[CurHero].Slots[i].IType = itemNone
       then Write(STR_EMPTY_ITEM)
       else Write(Heroes[CurHero].Slots[i].Name)
    end;
  GoToXY(1,20);
  ReadLn;
  ShowGame;
  end;

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

  const STR_HERO_SLOTITEMS = ' Используемые героем предметы: ' ;

Массив констант SlotName (названия слотов героя) опишем также в модуле Texts:

  const SlotName: array[1..MaxSlots] of string[20] =
          (
          ' Тело ' , ' В Руках '
          );

Внесение этой константы в программу может вызвать определенные трудности. Дело в том, что она использует другую константу - MaxSlots, которая описана в модуле Hero. Попытка добавить ссылку на модуль Hero в interface-заголовок модуля Texts закончится неудачно из-за того, что возникает замкнутая кольцевая цепочка ссылок interface-разделов друг на друга. Модуль Texts ссылается на модуль Hero, а тот в свою очередь - на модуль GameItem, которому снова требуется сслыка на Texts, так как в его интерфейсной части расположены описания массива типовых предметов ItemTypes, поле Name которого требует указания константы из модуля Texts.

Сейчас мы решим эту проблему "в лоб". Переместим описание константы MaxSlots, из-за которой разгорелся весь сыр-бор, в модуль Texts (хотя это и не совсем корректно в смысловом плане):

  const MaxSlots = 2;
          SlotName: array[1..MaxSlots] of string[20] =
          (
          ' Тело ' , ' В Руках '
          );

Добавим в список подключенных модулей модуля Hero ссылку на модуль Texts, чтобы оставалась доступной константа MaxSlots:

  unit Hero;

  interface uses GameItem, Texts;

одновременно убрав ее из раздела реализации.

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

Вызов процедуры ShowHeroSlots введем в обработчик нажатий на клавиши в главном модуле программы Main. Пусть список надетых героем предметов показывается при нажатии на клавишу 'e':

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

       ' e ' : ShowHeroSlots;

       ' i ' : ShowHeroItems;

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

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

Носимые предметы - в рюкзак

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

  procedure ShowHeroSlots;
  var i,n: Integer;
  begin

  while true do
  begin
  ClrScr;
  GoToXY(1,1);
  Write(STR_HERO_SLOTITEMS);
  for i := 1 to MaxSlots do
    begin
    GoToXY(1,i+2);
    Write(i, ' ) ' ,SlotName[i], ' : ' );
    if Heroes[CurHero].Slots[i].IType = itemNone
       then Write(STR_EMPTY_ITEM)
       else Write(Heroes[CurHero].Slots[i].Name)
    end;
  GoToXY(1,20);Write( ' > ' );
  ReadLn(n); if n = 0 then break;
  end;

  ShowGame;
  end;

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

  function GetFreeBag( var H: Thero ): Integer;
  var i: Integer;
  begin
  GetFreeBag := 0;
  for i := 1 to MaxHeroItems do
    if H.Items[i].IType = itemNone then
       begin
       GetFreeBag := i;
       Exit
       end;
  end;

В ней происходит перебор предметов, хранящихся в рюкзаке, и как только обнаруживается элемент, у которого значение поля IType соответствует значению типа "пустого" предмета, функция возвращает его индекс в массиве Items, входящего в структуру Hero.

С учетом этих нововведений процедура ShowHeroSlots запишется так:

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

  while true do
  begin
  ClrScr;
  GoToXY(1,1);
  Write(STR_HERO_SLOTITEMS);
  for i := 1 to MaxSlots do
    begin
    GoToXY(1,i+2);
    Write(i, ' ) ' ,SlotName[i], ' : ' );
    if Heroes[CurHero].Slots[i].IType = itemNone
       then Write(STR_EMPTY_ITEM)
       else Write(Heroes[CurHero].Slots[i].Name)
    end;
  GoToXY(1,20);Write( ' > ' );
  ReadLn(n); if n = 0 then break;

  s := GetFreeBag(Heroes[CurHero]);
  if (Heroes[CurHero].Slots[n].IType <> itemNone) and (s > 0) then
     begin
     Heroes[CurHero].Items[s] := Heroes[CurHero].Slots[n];
     Heroes[CurHero].Slots[n].IType := itemNone;
     end;

  end;

  ShowGame;
  end;

После того, как введенный номер слота проверен и выяснено, что он корректен, скопируем соответствующий элемент массива Slots в найденный незанятый элемент рюкзака (массив Items), после чего пометим элемент Slots[n] как "пустой".


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

http://russianenterprisesolutions.com/sbo/download/1115.zip 7299 байтов

Далее: Одеваем предметы из рюкзака.


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

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

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


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

В избранное