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

Создание ролевой компьютерной игры 26) Первые заклинания


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

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

87) Первые заклинания

Для демонстрации возможностей магической системы создадим два заклинания. Первое, назовем его "Огненный шторм", будет наносить повреждения от 1 до 4 единиц каждому монстру, находящемуся на любой соседней с героем клеток, а второе - "Самоисцеление", позволит быстро восстановить собственное здоровье. За счет маны, конечно.

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

Мы воспользуемся вторым подходом. Добавим в список навыков новый skillThrowSpell, не забыв исправить константы skillMax и MaxSkills:

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

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

Компилятор подскажет нам, какие строки кода нуждаются в дополнении:

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

  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)),
        ((0,0),(20,20),(45,25))
        );

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

Вызов процесса заклинания будем осуществлять по нажатию на клавишу 't':

  case k of
  ...
     ' t ' : ThrowSpell;

Для магов подготовим отдельный модуль, назовем его Magik:

  unit Magik;

  interface

  implementation

  end.

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

  interface

  procedure ThrowSpell;

  implementation uses Hero, LowLevel, Texts;

  { ----------------- }
  procedure ThrowSpell;
  begin
  if not SkillTest(Heroes[CurHero], skillThrowSpell) then
     begin
     ShowInfo(STR_MAGIK_BADTEST);
     Exit
     end;

Тут мы использовали текстовую константу (размещаем ее, как обычно, в Texts):

  STR_MAGIK_BADTEST = ' Попытка броска заклинания неудачна. ' ;

Расширим процедуру SkillTest новым навыком:

  ...
  case skl of

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

Добавим код увеличения навыка в процедуру SuccessSkillTest:

  skillThrowSpell:
      begin
      if random(50) = 0 then
         begin
         ShowInfo(STR_THROWSPELLSKILL_OK);
         inc(H.Skills[skillThrowSpell]);
         end;
      end;

Текстовая константа:

  STR_THROWSPELLSKILL_OK = ' Навык заклинаний повышен! ' ;

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

  const MaxSpellInt = 10;

  type TSpell = record
       Name: String[30];
       Mana: Integer;
       Ints: array[1..MaxSpellInt] of Integer;
       end;

Далее сразу подготовим массив-константу описаний типов заклинаний (по аналогии с массивом описаний типов предметов):

  const Spells: array[1..MaxSpells] of TSpell =
   (
   (Name:STR_SPL_FIRESTORM; Mana:3; Ints:(0,0,0,0,0,0,0,0,0,0)),
   (Name:STR_SPL_SELFHEALING; Mana:1; Ints:(0,0,0,0,0,0,0,0,0,0))
   );

КОнстанты:

  MaxSpells = 2;

  STR_SPL_FIRESTORM= ' Огненный Шторм ' ;
  STR_SPL_SELFHEALING= ' СамоИсцеление ' ;

(в модуле Texts)

Следующий шаг - создание интерфейсной функции, которая будет запрашивать выполняемое заклинание. Пусть она вернет номер заклинания в массиве Spells или 0, если ничего не выбрано. Назовем эту функцию GetSpellNo, а разместим ее в модуле LowLevel.

  { ----------------- }
  function GetSpellNo: Integer;
  var i: Integer;
  begin
  ClrScr; GoToXY(1,2);

  for i := 1 to MaxSpells do
    WriteLn(i, ' ) ' ,Spells[i].Name, ' : ' ,Spells[i].Mana);
  WriteLn(STR_SPL_GETNUM);
  ReadLn(i);

  GetSpellNo:=0;
  if not (i in [1..MaxSpells]) then
     Exit;

  GetSpellNo:=i;

  end;

Текстовая константа:

  STR_SPL_GETNUM = ' Введите номер функции или 0: ' ;

Затем необходимо проверить, достаточно ли у героя маны:

  { ----------------- }
  procedure ThrowSpell;
  var n: Integer;
  begin

  n := GetSpellNo;
  if n=0 then Exit;

  if Heroes[CurHero].Mana < Spells[n].Mana then
     begin
     ShowInfo(STR_MAGIK_BADMANA);
     Exit
     end;
  dec(Heroes[CurHero].Mana, Spells[n].Mana);

Текстовая константа:

  STR_MAGIK_BADMANA= ' Недостаточно маны! ' ;

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

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

  const
  ...
  splFireStorm = 1;
  splSelfHealing = 2;
  ...

  case n of

    splFireStorm:
      begin

      end;

    splSelfHealing:
      begin

      end;

  end;

Как будет работать огненный шторм? Надо перебрать все окружающие героя клетки, проверить, есть ли на них монстры, и нанести каждому некоторое поражение. Напомним, что у нас есть удобная процедура HeroAttackFin, которая учитывает воздействие заданной величины поражения на монстра. Только ее заголовок надо записать в интерфейсном разделе модуля Combat, и подключить сам модуль в раздел реализации Magik. Кроме того, после атаки монстров надо передать им ход - вызвать процедуру MonstersStep. Только делать это надо не каждый раз после удара по конкретному монстру, а после обработки всех попавших под атаку монстров. Кроме того, если ни один монстр под влияние огненного шторма не попал, ответный ход противника не нужен.

Для реализации такого алгоритма добавим локальную переменную-флажок f, которая будет принимать true, когда какой-нибудь монстр попадет под удар:

  splFireStorm:
    begin

    f := false;
    for x := Heroes[CurHero].x-1 to Heroes[CurHero].x+1 do
    for y := Heroes[CurHero].y-1 to Heroes[CurHero].y+1 do
      begin
      n := IsMonsterOnTile(x,y);
      if n > 0 then
         begin
         dam := random(4)+1;
         HeroAttackFin(Heroes[CurHero],n,dam);
         f := true;
         end;
      end;

    if f then
       MonstersStep;

    end;

Механизм исцеления пусть работает так. Герой теряет одну единицу маны, и восстанавливает одну единицу здоровья. При этом данное заклинание злости у монстров не вызовет, то есть его можно будет выполнять сколько угодно раз подряд (сколько позволит мана), не опасаясь атаки стоящих рядом врагов.

  splSelfHealing:
    begin
    dec(Heroes[CurHero].Mana);
    IncHP(Heroes[CurHero], +1);
    ShowInfo(STR_SELFHEALING);
    end;

Текстовая константа:

  STR_SELFHEALING = ' Самоисцеление успешно! ' ;

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

  ...
  if GameMap[CurMap].Cells[ Heroes[CurHero].x,Heroes[CurHero].y].Tile in
  LiveTileSet then
     begin
     ShowInfo(STR_LIVE);
     IncHP( Heroes[CurHero], Heroes[CurHero].MaxHP );
     IncMana( Heroes[CurHero], Heroes[CurHero].MaxMana );
     end;

(код, расширенный вызовом IncMana в процедуре MoveHero модуля Game).

А новая процедура IncMana (увеличение уровня маны героя) запишется в модуле Hero:

  { ----------------- }
  procedure IncMana( var H: THero; mad: Integer );
  begin
  H.Mana := H.Mana + mad;
  if H.Mana > H.MaxMana then H.Mana := H.MaxMana;
  end;

Теперь при вступлении в источник у персонажа восстановится как уровень здоровья, так и уровень маны - до максимума.

Далее - обучаемся работе с Магическими предметами.


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

http://russianenterprisesolutions.com/sbo/download/2416.zip 13148 байт


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

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

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


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

В избранное