Меню сайта
Форма входа

Категории раздела
Уроки по созданию игр [38]
Программирование игр разной сложности
Игровые алгоритмы [24]
Алгоритмы, которые уже реализованы для разных жанров игр
Графика [5]
Учимся работать с графикой в Делфи
Мультимедиа [3]
Работа с мультимедийными возможностями Делфи
Другие статьи [18]
Статьи не вошедшие не в один из разделов
Ошибки [4]
Всевозможные ошибки и пути их решения
Четверг, 09.01.2025, 05:56
Приветствую Вас Гость

Статьи по программированию

Главная » Статьи » Уроки по созданию игр

Урок 17. Вступаем в схватку.

Урок 17. Вступаем в схватку.

 

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

 

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

Exit

end;

… Heroes[CurHero].x,dx);

… Heroes[CurHero].y,dy);

Heroes[CurHero]);

 

В MoveHero (перемещение героя, модуль Game) добавлена проверка таила, на который должен ходить – имеетмя ли на тайле монстр. Если да (функция IsMonsterOnTile проверяет наличие монстра в массиве Monsters; эту функцию мы реализуем позже), то надо производить положенные действия по нападению на монстра, после чего завершить процедуру MoveHero – цель передвигать героя на тайл монстра не надо. Функцию разместим конечно в модуле Monster:

 

Procedure IsMonsterOnTile(x,y:Integer):Integer;

Var i:integer;

Begin

IsMonsterOnTile:=0;

For i:=1 to MaxMonsters do

If (Monsters[i].HP>0) and (Monsters[i].x=x) and (Monsters[i].y=y) then

  Begin

  IsMonsterOnTile:=1;

  Exit;

  End;

End;

 

Проверка работает по элементам массива Monsters, проверяя, какой из монстров живой по здоровью в виде НР, положительное значение), и находится ли на он на тайле с нужными координатами. Уже в таком виде игра будет приближена к реальности – герой не сможет проходить "сквозь" монстров. Это связано с местом вызова функции в процедуре MoveHero. Вызов происходит до фактического перемещения героя, происходит изменение его координат. Поэтому, в случае столкновения с
монстром он остается месте. Все, что от него требуется - это нанести удар и отразить
удар монстра. Отметим, что после того, как герой проведет атаку, на него может нападать
не только атакуемый монстр, но и все другие находящиеся поблизости монстры, даже если они не были атакованы.

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

 

IsMonsterOnTile(Heroes[CurHero].x+dx,Heroes[CurHero].y+dy);

If m=0 then

Begin

HeroAttack(Heroes[CurHero], m);

Exit;

End;

 

Процедура модуля Combat:

 

Unit Combat;

Uses Hero;

Procedure HeroAttack(var H:THero; m:integer);

Implementation

 

procedure HeroAttack ( var H: THero; rn: Integer );

begin

end;

end.

 

Первоначально в ходе схватки нам надо проверить успешность нападения героя. Для этого предназначен навык skillHandWeapon. Пока практика его применения не реализована, поэтому запрограммируем ее следующим образом (напомним, что проверка успешности навыков выполняется в процедурах SkillTcst и SuccessSkillTest модуля Него):

 

function SkillTest( var H: THero; ski: Integer ): Boolean;

var xp: Integer;

begin

SkillTest := false;

if random(100)+l > H.Skills[ski] then

Exit;

SkillTest := true;

case ski of

skillHandWeapon:

begin

SuccessSkillTest(H, skillHandWeapon); end; skillTrapSearch: begin

Showlnfo(STRJTRAPOK) ; IncXP(H, H.Level + random(H.Level)); SuccessSkillTest(H, skillTrapSearch);   

end;

end;

end;

 

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

Атака является лишь одним шагом в последовательности взаимных нападений и защит, поэтому обработку ее успешности будем выполнять на более высоком уровне, в процедуре HeroAttack. Здесь же внесем еще одно дополнение в процедуру SuccessSkillTest, где происходит повышение соответствующего навыка. Посмотрим, чему равняется базовое значение навыка атаки (модуль Tables, константа BascSkill_Table)? Оно равняется 30.

Теперь попробуем рассчитать, на сколько может вырасти заданный навык. В среднем на уровне может находиться число монстров, равное 50 (MaxMonsters). Сколько успешных ударов для уничтожения одного монстра потребуется, сказать сложно. Но, по всей видимости оно вряд ли будет больше десяти. Ведь здоровье монстров на первых уровнях составляет 1 -2 пункта, а на старших локациях - до 35 единиц.

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

Итак, для уничтожения всех монстров в локации нам потребуется нанести 50 * 10 = 500 успешных ударов. Всего на четырех локациях пещеры герой сможет ударить врага 500 * 4 = 2000 раз. До какого значения при этом возрастет навык атаки? Давайте возьмем величину 80%. То есть после нанесения 2000 ударов значение элемента Skills[skillHandWeapon] увеличится с 30 до 80. Другими словами, каждый пятидесятый (2000 / (80 - 30)) удар можно считать вносящим свой вклад в увеличение навыка рукопашного боя. Выше уже описывался метод повышения навыка SkillTrapSearch, когда это повышение происходило случайным образом. И таким же образом мы реализуем рост мастерства атаки:

 

Procedure PowerskillTest(   var  H:   THero;   ski:   Integer);

Var m:integer;

Begin

Case ski of

  SkillHandWeapon:

  Begin

  If random(50) = 0 then

    Begin

     ShowInfo(STR_HANDWEAPONSKILL_OK);

     Inc(H.Skills[skillHandWeapon]);

    End;

  End;

  SkillTrapSearch:

  Begin

   Rnd := round( 20/MaxDungeonLevel*100 );

  If random(100)+1 <= Rnd then

    Begin

    ShowInfo(STR_TRAPSKILL_OK);

    Inc(H.Skills[skillTrapSearch]); 

    End;

  End;

End;

End;

 

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

 

Const STR_HANDWEAPONSKILL_OK = ‘ Навык ручного боя повышен!’;

 

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

 

Procedure HeroAttack(var H:THero; m:integer);

Begin

  If NOT  SkillTest(Heroes[CurHero], skillHandWeapon) then

  Begin

  ShowInfo(STR_BAD_ATTACK);

  Exit;

  End;

End;

 

Переменную опишем в в модуле TEXTS:

 

Const STR_BAD_ATTACK = ‘ Вы промазали’;

 

Чтобы программа собиралась нормально, в заголовок реализации модуля Combat надо
            добавить ссылки на следующие модули:

 

uses  Game,   LowLevel,   Texts;

 

Если удар своей цели, в дело вступает монстр. Его шкура (поля Ddl, Dd2 типа TMonster) принимает на себя определенную часть поражающего воздействия. Часть этого воздействия будет вычисляться на основе параметров оружия. Расчет поражающего воздействия для оружия вынесем в отдельную функцию:

 

Function WeaponDamage(   Itm:   TGameltem   ):   Integer;

Begin

WeaponDamage:=0;

If random(100)+1   >   Itm.Ints[intAttackHit]   then 

Exit;

WeaponDamage     :=   RollDice (Itm. Ints [intAttack_dl] ,

Itm.Ints[intAttack_d2]);

end;

 

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

Так, для топора, у которого поле IntsfintAttackHit равно 50 (см. массив ItemTypes), и героя с базовым навыком рукопашного боя, равным 30 (таблица BascSkill Table), вероятность успешного удара будет равна 0,5 * 0,3 = 0,15, что на самом деле весьма невелико. Правильно ли такое дополнительное снижение суммарной вероятности попадания? Да, правильно.

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

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

 

function  RollDice(   dl,   d2:   Integer   ):   Integer;

var   i,s:   Integer;

begin

s   :=  0;

for   i   :=   1   to  dl  do

s   :=   s   +   random(d2)+1;

RollDice   :=  s;

end;

 

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

Дополним процедуру сражения проверкой величины наносимого поражения. Для этого нам потребуется определить, какое оружие в данный момент использует герой. Такую проверку вынесем в отдельную функцию GetHeroWeapon, так как она не так проста для реализации, как может показаться. Разместим такую функцию в модуле Неrо:

 

function  GetHeroWeapon(   var  H:   THero   ):   Integer;

begin

GetHeroWeapon := 0;

if H.Slots[slotHands].IType = itemNone then Exit;

GetHeroWeapon := slotHands;

end;

 

Эта функция существенно зависит от количества слотов персонажа, а также от их назначения. В нашем случае проверять на наличие оружия надо только один слот (slotHands). Если предмета в нем пет, значит, герой не может вести сражение (ему нечем это делать). Функция возвращает номер слота, в котором хранится предмет-оружие, или ноль, если подходящего оружия нет. Вот как будет выглядеть очередная версия процедуры HeroAttack:

 

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;

 

…Weapon[n]:

if … then

begin

ShowInfo(STR_NONE_WEAPONS);

Exit;

 

WeaponDamage(H.Slots[i]);

… Monsters[m].dd1, Monsters[m].dd2);

If … then

Begin

ShowInfo(STR_BIG_SKIN);

Exit;

End;

 

Эти константы описаны в модуле Texts:

 

Const STR_NONE_WEAPON =  '   у  вас нет   оружия.    '    ;

Const STR_BIG_SKIN  = '   Шкура монстра   выдержала удар...    '    ;

 

Следует проверять есть ли  у героя оружие. Если его нет, выдается сообщение STR_NONE_WEAPON и работа процедуры заканчивается. В противном случае отображается урон (размер) поражения, наносимого оружием героя, а также определяется, сколько может відержать шкура монстра (это поля ddl, dd2 в структуре TMonsler). Если
урон менше или равен толщине шкуры, значит, шкуру монстра пробить не удалось( STR_BIG_SKIN).

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

Продолжение следует…


Источник: Delgame.at.ua

Категория: Уроки по созданию игр | Добавил: Armageddets (11.12.2013) | Автор: Урок 17. Вступаем в схватку.
Просмотров: 1059 | Теги: программирование драки, делфи игра, Вступаем в схватку, Урок по созданию игры | Рейтинг: 1.0/1
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Наш опрос
Оцените мой сайт
Всего ответов: 103
Мини-чат
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0