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

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


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

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

18) Монстры наносят ответный удар

Забавно - на сайте www.bookshelf.ru

нашел электронную (пиратскую, конечно :) свою книгу.

"Самоучитель прграммирования на языке C++ в системе Borland C++ Builder 4.0"
http://www.bookshelf.ru/modules.php?name=Files&d_op=get_file_details&files_id=76

Мало того, что сайт тормозной, так автор сайта, еще, видимо, такой чайник, что не смог даже набрать название системы грамотно :)
"система визуального программирования Dorland C++ Builder 4.0"

Dorland - проверю в яндексе... Надо же, 2205 страниц!

Хм, ну пусть с пиратом боевики издателя разбираются. Ух! Очень свирепые ребята.
Ибо как минимум надо спрашивать разрешения автора.



Монстры наносят ответный удар

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

  procedure HeroAttack( var H: THero; m: Integer );
  var i, dam, skin: Integer;
  begin
  if not SkillTest(Heroes[CurHero], skillHandWeapon) then
        begin
        ShowInfo(STR_BAD_ATTACK);
        Exit
        end;

  i := GetHeroWeapon(H);
  if i = 0 then
        begin
        ShowInfo(STR_NONE_WEAPONS);
        Exit
        end;

  dam := WeaponDamage( H.Slots[i] );
  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;

Текстовые константы могут быть описаны в модуле Texts так:

  const STR_ATTACK = ' Герой наносит повреждение ' ;
  const STR_MON_KILL = ' завален! ' ;

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

Монстры наносят ответный удар

Где будет находится процедура ответных ударов окружающих героя монстров? Она будет вызываться сразу после процедуры HeroAttack. Назовем ее MonstersAttack:

  procedure MoveHero( dx,dy: Integer );
  var m, dam: Integer;
  begin
  if not FreeTile( GameMap[CurMap].Cells[
  Heroes[CurHero].x+dx,Heroes[CurHero].y+dy].Tile ) then
     Exit;

  m := IsMonsterOnTile(Heroes[CurHero].x+dx, Heroes[CurHero].y+dy);
  if m > 0 then
     begin
     HeroAttack(Heroes[CurHero], m);
     MonstersAttack;
     Exit
     end;
  ...

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

Реализуем процедуру MonstersAttack в модуле Combat.

  procedure MonstersAttack;
  var i: Integer;
  begin
  for i := 1 to MaxMonsters do
    if (Monsters[i].HP > 0) and
       CanAttack(i,CurHero) then
       begin
       MonsterAttack(i, Heroes[CurHero]);
       end;

  end;

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

  function CanAttack(mi, hi: Integer): Boolean;
  begin
  CanAttack := Distance( Heroes[hi].x,Heroes[hi].y,
                         Monsters[mi].x,Monsters[mi].y ) = 1;
  end;

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

  function Distance(xf,yf, xt,yt: Integer): Integer;
  begin
  Distance := abs(xf-xt) + abs(yf-yt);
  end;

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

  procedure MonsterAttack(m: Integer; var H: THero );
  var dam, def: Integer;
  begin
  if SkillTest(H, skillDefence) then
     begin
     ShowInfo(Monsters[m].Name + STR_MON_STOP);
     Exit
     end;

  dam := RollDice( Monsters[m].Ad1, Monsters[m].Ad2 );
  def := GetHeroDefence(H);
  if dam <= def then
     begin
     ShowInfo(Monsters[m].Name + STR_MON_DEF);
     Exit
     end;

  ShowInfo(Monsters[m].Name + STR_MON_ATTACK);
  IncHP( H, -(dam-def) );
  end;

Этот текст требует пояснений. Вначале происходит проверка умения героя уклоняться от ударов. Пока у нас умения skillDefence нет. Поэтому выполним процедуру добавления нового навыка. Делается это так. В модуле Hero увеличиваем значение константы MaxSkills до 3, и вводим новую константу skillDefence:

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

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

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

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

Далее исправим процедуру SkillTest:

  function SkillTest( var H: THero; skl: Integer ): Boolean;
  var xp: Integer;
  begin
  SkillTest := false;
  if random(100)+1 > H.Skills[skl] then Exit;
  SkillTest := true;

  case skl of

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

     skillTrapSearch:
         begin
         ShowInfo(STR_TRAPOK);
         IncXP(H, H.Level + random(H.Level));
         SuccessSkillTest(H, skillTrapSearch);
         end;

  end;
  end;

Здесь проверки навыков, не требующих дополнительных действий, объединены в один обработчик.

А рост навыка обороны добавим в процедуру SuccessSkillTest. Как часто нам надо будет повышать этот навык? Вспомним аналогичные подсчеты, связанные с умением наносить удары (skillHandWeapon). За игру персонаж, как мы подсчитали, может атаковать монстров около двух тысяч раз. Количество ответных нападений монстров будет, конечно, большим. Ведь для героя мы считали только успешные атаки, а обороняться ему придется от всех нападений противника. Поэтому выберем приближенное значение - например, пять тысяч. Пусть навык возрастет с 25 до 75%. Большее, чем 75, значение брать неразумно - ведь тогда герой станет слишком неуязвимым и способным отражать все без исключения атаки противника. Теперь мы можем рассчитать вероятность повышения этого навыка на единицу - она будет равна 5000 / (75 - 25) или такое повышение должно происходить в одном случае из ста.

  procedure SuccessSkillTest( var H: THero; skl: Integer);
  var rnd: Integer;
  begin
  case skl of

     skillDefence:
         begin
         if random(100) = 0 then
            begin
            ShowInfo(STR_DEFENCESKILL_OK);
            inc(H.Skills[skillDefence]);
            end;
         end;

  ...

После того, как выяснено, что герою не удалось уклониться от нападения, выполняется вычисление величины поражения, которое наносит монстр. Эта величина зависит от соответствующих характеристик монстра (поля Ad1, Ad2). Наконец, вычисляется бронезащита персонажа (величина, аналогичная шкуре монстра). Такая бронезащита непосредственно связана с количеством надетой на героя брони. Расчет данного параметра происходит в функции GetHeroDefence:

  function GetHeroDefence( var H: THero ): Integer;
  var d: Integer;
  begin
  d := 0;
  if H.Slots[slotBody].IType <> itemNone then
     d := d + H.Slots[slotBody].Ints[intArmorDefence];
  GetHeroDefence := d;
  end;

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

  function GetHeroDefence( var H: THero ): Integer;
  var d: Integer;
  begin
  d := 0;
  if H.Slots[slotBody].IType <> itemNone then
     d := d + H.Slots[slotBody].Ints[intArmorDefence];
  if H.Slots[slotHelm].IType <> itemNone then
     d := d + H.Slots[slotHelm].Ints[intArmorDefence];
  GetHeroDefence := d;
  end;

Константу добавим, для слота шлема:

   const slotBody = 1;
          slotHands = 2;
          slotHelm = 3;

И ее описание, в модуле Texts:

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

В заключение в MonsterAttack происходит снижение уровня здоровья героя - при попадании.

Константы в упомянутых процедурах выглядят так:

  const STR_DEFENCESKILL_OK = ' Вы отбили удар! ' ;
  const STR_MON_STOP = ' : атака отбита! ' ;
  const STR_MON_DEF = ' наносит удар, но не пробивает вашу
  защиту... ' ;
  const STR_MON_ATTACK = ' наносит удар! ' ;


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

http://russianenterprisesolutions.com/sbo/download/22115.zip 9348 байтов

Далее - Монстры бросаются в погоню.


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

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

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


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

В избранное