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

Создание ролевой компьютерной игры 23) Готовимся к стрельбе из лука


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

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

84) Готовимся к стрельбе из лука

Перед выводом статистики немного улучшим существующий код. Пока у нас не реализован момент роста уровня героя при увеличении опыта (процедура IncXP в модуле Hero). Вот как мы его запрограммируем:

  procedure IncXP( var H: THero; axp: Integer );
  begin
  inc( H.Exp, axp );
  ShowInfo(STR_ADD_EXP + IntToStr(axp));
  if H.Exp > H.MaxExp then
     begin
     H.Exp := 0;
     if H.Level < MaxPlayerLevel then
        begin
        ShowInfo(STR_NEXTLEVEL);
        inc(H.Level);
        end;
     H.MaxExp := ExpLevel_Table[H.Level];
     H.MaxHP := HPLevel_Table[H.Level];
     H.HP := H.MaxHP;
     end;
  end;

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

Информацию о герое мы будем выводить с помощью процедуры ShowHeroFullInfo, которую разместим, конечно в модуле LowLevel, так как это задача, непосредственно связанная с пользовательским интерфейсом.

  procedure ShowHeroFullInfo(HeroNum: Integer);
  begin

  end;

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

  procedure ShowHeroFullInfo;
  begin
  ClrScr;
  GoToXY(1,1);

  ReadLn;
  ShowGame;
  end;

ПО какой клавише будем вызывать ее? Допустим, по нажатию на символ 'c', для чего дополним главный цикл обработки событий в модуле Main еще одним пунктом:

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

       ' c ' : ShowHeroFullInfo;

       ' g ' : GetItemFromMap;
  ...

По герою мы желаем знать, прежде всего, значения всех его базовых параметров и навыков, а также сведения по здоровью и опыту, уровень и другие дополнительные сведения. Сначала в цикле покажем параметры из массива Chars и навыки из Skills (для отображения слотов и несомых предметов у нас уже есть процедуры ShowHeroSlots и ShowHeroItems). Для этого, видимо, нам придется подготовить в модуле Texts массив текстовых констант, описывающий название каждого параметра и каждого навыка (в соответствии с порядоком, заданным константами chr..., skill... в модуле Hero).Здесь мы вновь столкнемся с проблемой, уже знакомой нам ранее по подготовке массива SlotName с названиями слотов - из-за того, что мы не можем ссылаться из интерфейсной части одного модуля на интерфейсную часть другого, а в инструкции подключаемых модулей модуля Hero уже указан модуль Texts, описание констант MaxChars и MaxSkills надо будет перенести в модуль Texts, непосредственно к массивам с названиями соответствующих особенностей героя:

   MaxChars = 6;
   CharsName: array[1..MaxChars] of string[20] =
   (
   ' Сила ' , ' Ловкость ' , ' Тело ' , ' Ум ' , ' Мудрость ' , ' Харизма '
   );

   MaxSkills = 3;
   SkillsName: array[1..MaxSkills] of string[20] =
   (
   ' Ручное оружие ' , ' Поиск оружия ' , ' Защита '
   );

В модуле Hero раздел констант немного сократится:

  const chrSTR = 1;
        chrDEX = 2;
        chrCON = 3;
        chrIQ = 4;
        chrWIS = 5;
        chrCHA = 6;

        skillHandWeapon = 1;
        skillTrapSearch = 2;
        skillDefence = 3;

При этом компилятор нам дополнительно сообщит, что в модуле Tables теперь не определена константа MaxSkills, и поэтому в заголовок надо добавить ссылку и на Texts - новое место этой константы:

  unit Tables;

  interface uses Hero, Texts;

  ...

Теперь нам осталось удостовериться в правильности этих действий, для чего продолжим процедуру отображения статистик выводом значения навыков:

  procedure ShowHeroFullInfo;
  var i: Integer;
  begin
  ClrScr;
  GoToXY(1,1);
  WriteLn(STR_HEROFI);

  for i := 1 to MaxChars do
    WriteLn( CharsName[i], ' : ' , Heroes[CurHero].Chars[i] );

  WriteLn; WriteLn(STR_HEROFI2);
  for i := 1 to MaxSkills do
    WriteLn( SkillsName[i], ' : ' , Heroes[CurHero].Skills[i] );

  ReadLn;
  ShowGame;
  end;

Здесь использована две константы (модуль Texts):

          STR_HEROFI = ' Параметры героя: ' ;
          STR_HEROFI2 = ' Навыки героя: ' ;

Что еще будет нас интересовать? Это, без сомнения, уровень героя, текущий набранный опыт и остаток до следующего уровня. Эти данные уже хранятся в структуре THero, поэтому итоговый вариант процедуры, не нуждающийся в комментариях, запишется так:

  procedure ShowHeroFullInfo;
  var i: Integer;
  begin
  ClrScr;
  GoToXY(1,1);
  WriteLn(Heroes[CurHero].Name, ' , ' ,
          RaceName[Heroes[CurHero].Race], ' ' ,
          ClassName[Heroes[CurHero].Class]);
  WriteLn(STR_HEROFI_LEVEL, Heroes[CurHero].Level);
  WriteLn(STR_HEROFI_EXP, Heroes[CurHero].Exp, ' /
  ' ,Heroes[CurHero].MaxExp);
  WriteLn;

  WriteLn(STR_HEROFI);
  for i := 1 to MaxChars do
    WriteLn( CharsName[i], ' : ' , Heroes[CurHero].Chars[i] );

  WriteLn; WriteLn(STR_HEROFI2);
  for i := 1 to MaxSkills do
    WriteLn( SkillsName[i], ' : ' , Heroes[CurHero].Skills[i] );

  ReadLn;
  ShowGame;
  end;

Стрельба из лука

Следующим шагом по улучшению нашей игры станет реализация дальнобойного оружия. Такое оружие позволит герою вести огонь по противнику с расстояния, однако такая возможность давала бы персонажу слишком большие преимущества перед монстрами, которые бьются только в рукопашную, поэтому на стрельбу нам придется наложить определенные ограничения. Какими они могут быть?

Для стрельбы нужны патроны, стрелы, камни, - другими словами, боеприпасы. Для этого нам потребуется два новых типа предметов, назовем их itemAmmo (боеприпасы) и itemRangedWeapon (дальнобойное оружие):

  type TGameItemType = (itemHandWeapon, itemArmor,
        itemAmmo, itemRangedWeapon, itemNone);

(модуль GameItem).

Мы делали наш код таким, чтобы дополнение тех или иных понятий новыми сущностями вызывало какие-то минимальные сообщения об отсутствии их реализации автоматически. Так, если теперь, добавив две новые константы в описание типа TGameItemType, мы попробуем скомпилировать программу, то сразу получим сообщение об ошибке.

В модуле LowLevel имеется массив констант ItemRecords, содержащих символы-представления соответствующих предметов. Расширим его двумя новыми позициями. Пусть боеприпасы рисуются символом '|', а дальнобойное оружие (лук) - символом '}'.

  const ItemRecords: array[TGameItemType] of TTileRecord =
     (
     (C: ' + ' ; Clr:LightCyan),
     (C: ' [ ' ; Clr:LightGreen),
     (C: ' | ' ; Clr:LightCyan),
     (C: ' { ' ; Clr:LightGreen),
     (C: ' ' ; Clr:Black)
     );

Предметы у нас разбрасываются по карте равномерно и автоматически, так что в нашем виртуальном мире сразу можно будет найти и стрелы, и луки.

Сами описания предметов добавляются в массив ItemTypes. Прежде всего увеличим на два значение константы MaxItemTypes - до 6:

const MaxItemTypes = 6;

а следом - сам массив:

         ItemTypes: array[1..MaxItemTypes] of TGameItem =
  ...

         (ID:5;
          x:0; y:0;
          IType:itemAmmo;
          Name:STR_AMMO;
          Ints: (15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
          Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
         (ID:6;
          x:0; y:0;
          IType:itemRangedWeapon;
          Name:STR_CROSS;
          Ints: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
          Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))
         );

КОнстанты в модуле Texts описывают название новых типов предметов:

          STR_AMMO = ' Стрелы ' ;
          STR_CROSS = ' Лук ' ;

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

  const
  ...
         intAmmo = 1;
         intRangedAmmo = 1;

Взятие лука с земли и перемещение его из инвентаря в руки будет происходить так же, как и с другими вещами. Возможность перемещения в руки у нас задается специально для этого предназначенной функцией GoodSlot (модуль Hero). Нам только надо дополнить список типов предметов, которые можно держать в руках, новым типом дальнобойного оружия:

  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,
  itemRangedWeapon];

  end;
  end;

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

  ...

  if Heroes[CurHero].Class = classRanger then
     begin
     Heroes[CurHero].Items[1] := ItemTypes[6];
     end;

  end;

Здесь мы перезаписываем уже размещенный первым топор на лук (6-й в массиве типов предметов). Кроме того, этому луку мы придаем 10 стрел, используя константу intRangedAmmo.

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

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

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

  function GetItemName(Itm: TGameItem): string;
  begin
  case Itm.IType of

    itemAmmo: GetItemName := Itm.Name +
    ' ( ' +IntToStr(Itm.Ints[intAmmo])+ ' ) ' ;

    itemRangedWeapon:
              GetItemName := Itm.Name +
  ' ( ' +IntToStr(Itm.Ints[intRangedAmmo])+ ' ) ' ;

  else GetItemName := Itm.Name
  end;
  end;

Не забудем добавить заголовок функции в раздел интерфейса, а в раздел реализации подключим модуль LowLevelдля доступа к функции IntToStr. Теперь следом за названием оружия программа будет сообщать число стрел, в круглых скобочках.

Обращение к данной функции разместим в процедурах работы с инвентарем и экипировкой, вместо простых старых выводов названий предметов напрямую.

В процедуре ShowHeroSlots:

  ...
  if Heroes[CurHero].Slots[i].IType = itemNone
     then Write(STR_EMPTY_ITEM)
     else Write( GetItemName(Heroes[CurHero].Slots[i]))
  ...

В процедуре ShowHeroItems:

  ...
  if Heroes[CurHero].Items[i].IType = itemNone
     then Write(i, ' ) ' ,STR_EMPTY_ITEM)
     else Write(i, ' ) ' , GetItemName(Heroes[CurHero].Items[i]))
  ...

После того, как лук находится в руках, и для него приготовлены стрелы, герой должен научиться стрелять из него и поражать противника на расстоянии.

Далее - обучаем меткости и силе стрельбы.


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

http://russianenterprisesolutions.com/sbo/download/27125.zip 11295 байта


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

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

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


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

В избранное