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

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


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

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

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

Начнем с самого "верха" - с интерфейса ведения стрельбы. Пусть герой может поражать монстров, которые находятся только на одной прямой линии с ним - либо по вертикали, либо по горизонтали. Для выстрела по одному из этих четырех направлений удобнее всего задействовать клавиши-стрелки, но они уже используются для перемещения персонажа, договоримся, что за стрельбу по четырем направлениям будут отвечать клавиши a, d, w и z. По аналогии с процедурой MoveHero будем вызывать новую процедуру HeroShot, которой в качестве параметров также зададим единичный вектор направления стрельбы (модуль Main):

    case k of

  ...

       ' a ' : HeroShot(-1,0);
       ' d ' : HeroShot(+1,0);
       ' w ' : HeroShot(0,-1);
       ' z ' : HeroShot(0,+1);

Процедуру HeroShot разместим в модуле Combat (ссылку на него надо добавить в uses-перечень главного модуля, а заголовок новой процедуры - в интерфейсный раздел Combat):

  { --------------------------- }
  procedure HeroShot(dx,dy: Integer);
  begin

  end;

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

  const
  ...
    intRangedAmmo = 1;
    intRangedRange = 2;

Как правило, это расстояние в подобных играх составляет от 3-5 до 15-25 тайлов. Пусть наш единственный лук стреляет на пять клеток:

  (ID:6;
   x:0; y:0;
   IType:itemRangedWeapon;
   Name:STR_CROSS;
   Ints: (0,5,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))

Как будет проходить процесс выстрела? Надо сымитировать движение стрелы в заданном направлении, и прекратить полет либо успешным поражением объекта, либо неуспешным - если на пути встретилось непроходимое препятствие, или же стрела исчерпала летный ресурс. А предварительно, конечно, надо убедиться, что у героя в руках лук и стрелы. Ну и, как обычно, проверим навык дальнобойной стрельбы.

Однако навыка такого у нас пока нету. Реализацией его мы сейчас и займемся. Добавим в список констант-навыков модуля Hero новое значение, и одновременно откорректируем новое максимальное число навыков:

  const
  ...
   skillMin = 1;
   skillHandWeapon = 1;
   skillTrapSearch = 2;
   skillDefence = 3;
   skillRangedWeapon = 4;
   skillMax = 4;

Надо также увеличить MaxSkills в модуле Texts и дополнить соответствующий массив новым названием навыка:

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

Если теперь вызвать компилятор, то выдастся ошибка о неполном массиве соотношений класс-навык - ее мы исправим, например, так:

  const ClassSkill_Table:
        array[ skillMin..skillMax, classWarrior..classMage, 1..2 ] of Integer =
        (
        ((80,15),(50,20),(25,10)),
        ((20,20),(80,15),(60,20)),
        ((70,20),(50,20),(15,10)),
        ((20,20),(80,20),(35,20))
        );

Кроме того, дополнения потребует и массив BaseSkillTable (впрочем, реально этот массив уже не используется):

  const BaseSkill_Table: array[1..MaxSkills] of Integer =
    (
    30, 20, 25, 30
    );

Далее, добавим обработку нового навыка в процедуру SkillTest (модуль Hero):

  ...
  case skl of

     skillHandWeapon,
     skillRangedWeapon,
     skillDefence:
         begin
         SuccessSkillTest(H, skl);
         end;
  ...

и SuccessSkillTest:

  case skl of
  ...
  skillRangedWeapon:
      begin
      if random(35) = 0 then
         begin
         ShowInfo(STR_RANGEDWEAPONSKILL_OK);
         inc(H.Skills[skillRangedWeapon]);
         end;
      end;

Текстовую константу опишем так:

  STR_RANGEDWEAPONSKILL_OK= ' Навык стрельбы повышен! ' ;

Вернемся к процедуре HeroShot. Добавим в нее проверку того, имеется ли в руках у героя дальнобойное оружие (кстати, эту проверку надо сделать первой, до анализа/проверки навыка, причем как в этой процедуре, так и в HeroAttack), и есть ли в этом оружии заряды:

  { --------------------------- }
  procedure HeroShot(dx,dy: Integer);
  begin
  if Heroes[CurHero].Slots[slotHands].IType <> itemRangedWeapon then
        begin
        ShowInfo(STR_NONE_WEAPONS);
        Exit
        end;
  if Heroes[CurHero].Slots[slotHands].Ints[intRangedAmmo] = 0 then
        begin
        ShowInfo(STR_NONE_AMMO);
        Exit
        end;

  if not SkillTest(Heroes[CurHero], skillRangedWeapon) then
        begin
        ShowInfo(STR_BAD_RANGED_ATTACK);
        Exit
        end;

  end;

Следующий шаг - снижение числа боеприпасов:

  dec(Heroes[CurHero].Slots[slotHands].Ints[intRangedAmmo]);

Теперь начинается собственно полет стрелы:

  n := Heroes[CurHero].Slots[slotHands].Ints[intRangedRange];
  x := Heroes[CurHero].x; y := Heroes[CurHero].y;
  while n > 0 do
    begin
    x := x+dx; y := y+dy;
    if not FreeTile( GameMap[CurMap].Cells[x,y].Tile ) then
       Exit;

     m := IsMonsterOnTile(x,y);
     if m > 0 then
        begin

        // атакуем монстра...

        MonstersStep;
        Exit
        end;

    dec(n)
    end;

Обратите внимание, что после выстрела, который попал в монстра, вызывается процедура ответного удара разъяренных тварей MonstersStep.

Сам процесс атаки будет похож на соответствующие действия в ходе ручной схватки.

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

  const
  ...
  intRangedAmmo = 1;
  intRangedRange = 2;
  intRangedDices = 3;
  intRangedDiceNum = 4;

Наш единственный лук теперь будет выглядеть так (исправлен массив Ints):

  (ID:6;
   x:0; y:0;
   IType:itemRangedWeapon;
   Name:STR_CROSS;
   Ints: (0,5,1,4,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))

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

С учетем вышесказанного величина поражения будет рассчитываться так:

  dam := RollDice( Heroes[CurHero].Slots[slotHands].Ints[intRangedDices],
                   Heroes[CurHero].Slots[slotHands].Ints[intRangedDiceNum]);

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

  { --------------------------- }
  procedure HeroAttackFin( var H: THero; m, dam: Integer );
  var skin: Integer;
  begin

  skin := RollDice( Monsters[m].dd1, Monsters[m].dd2 );
  if skin >= dam then
        begin
        ShowInfo(STR_BIG_SKIN);
        Exit
        end;

  dec( Monsters[m].HP, dam-skin );
  ShowInfo(STR_ATTACK + IntToStr(dam-skin) );

  if Monsters[m].HP <= 0 then
     begin
     ShowInfo(Monsters[m].Name + STR_MON_KILL);
     IncXP( H,Monsters[m].XP );
     end;

  end;

Тогда изменим старый код HeroAttack - в конце:

  ...
  d := RollDice(3,6);
  if d <= H.Chars[chrSTR] then
     dam := dam + dam div 2;

  HeroAttackFin( H, m, dam );

  end;

И новый код:

  m := IsMonsterOnTile(x,y);
  if m > 0 then
  begin

  dam := RollDice( Heroes[CurHero].Slots[slotHands].Ints[intRangedDices],
                   Heroes[CurHero].Slots[slotHands].Ints[intRangedDiceNum]);

  HeroAttackFin( Heroes[CurHero], m, dam );

  MonstersStep;
  Exit
  end;

Проверим программу и убедимся, что стрельба ведется нормально.

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

Сделаем это уже известным способом. Сначала попробуем добавить бонус на дальность стрельбы - допустим, на 30%:

  ...
  n := Heroes[CurHero].Slots[slotHands].Ints[intRangedRange];
  d := RollDice(3,6);
  if d <= Heroes[CurHero].Chars[chrDEX] then
     n := n + n div 3;
  ...

После чего - бонус на силу выстрела:

  ...
  dam := RollDice( Heroes[CurHero].Slots[slotHands].Ints[intRangedDices],
                   Heroes[CurHero].Slots[slotHands].Ints[intRangedDiceNum]);

  d := RollDice(3,6);
  if d <= Heroes[CurHero].Chars[chrSTR] then
     dam := dam + dam div 3;
  ...

Данный механизм бонусов, конечно, сильно несовершенен - в профессионально сделанных ролевых системах нет такого гладкого изменения способностей. Так, уровень навыка 13 и ниже может вообще никогда не приносить никаких бонусов, а уровень 17 наоборот может внести уже очень существенный вклад. Но мы используем его в основном для наглядности, чтобы программа была работоспособна. Читатель сам может придумать произвольные системы начисления бонусов, главное - как следует сбалансировать игру.

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

  ...
  (ID:5;
   x:0; y:0;
   IType:itemAmmo;
   Name:STR_AMMO;
   Ints: (100,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)),
  ...

а также сотню боеприпасов придать персонажу в момент его генерации (процедура GenerateHero):

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

Далее - обучаем Героя Магии.


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

http://russianenterprisesolutions.com/sbo/download/1116.zip 11841 байт (c) 2004-2006 Сергей Бобровский bobrovsky@russianenterprisesolutions.com

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

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


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

В избранное