Урок 24.
Стрельба из лука
Начнем с «верха»
с интерфейса ведения стрельбы. Пусть герой можег поражать только врагов,
которые находятся только на одной прямой линии с ним - либо по вертикали. Для
выстрела по одному из этих четырех направлений удобнее всего использовать
клавиши-стрелки, но они уже используются для перемещения персонажа, поэтому за
стрельбу по четырем направлениям будут отвечать клавиши a, d, w и z. Процедурой
MoveHero будем вызывать новую процедуру HeroShot, которой в
ряде параметров зададим единичный вектор направления стрельбы.
Case I of
‘a’:
HeroShot(-1,0);
‘d’:
HeroShot(+1,0);
‘w’:
HeroShot(0,-1);
‘s’: HeroShot(0,+1);
End;
Разместим в модуле Combat (ссылку на него надо
добавить в usеs данного модуля, а заголовок новой процедуры - в интерфейсный
раздел Combat):
procedure
HeroShot(dx,dy: Integer);
begin
end;
Далее нам надо определить, как далеко будет лететь
пущенная из лука стрела. Это расстояние должно зависеть в первую очередь от
самого лука, а во вторую - от ловкости героя. Поэтому условимся, что в
структуре TGameltem, описывающей дальнобойное оружие, второй элемент вложенного
массива Ints будет хранить число тайлов, на которое лук может послать стрелу:
const
intRangedAmmo
= 1;
intRangedRange
= 2;
Как правило, это расстояние в подобных играх
составляет от 3-5 до 15-25 тайлов. Пусть наш единственный лук стреляет на пять
клеток:
(ID:6;
х: 0; у: 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))
Как будет проходить процесс выстрела? Надо
сымитировать движение стрелы в заданном направлении, и прекратить полет либо
успешным поражением объекта, либо неуспешным -если на пути встретилось
непроходимое препятствие, или же стрела исчерпала летный ресурс. А
предварительно, конечно, надо убедиться, что у героя в руках лук и стрелы. Ну
и, как обычно, проверим навык дальнобойной стрельбы.
Однако навыка такого у нас пока нету. Реализацией его
мы сейчас и займемся. Добавим в список констант-навыков модуля Него новое
значение, и одновременно откорректируем новое максимальное число навыков:
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) ),
( (50,20) , (80,15) , (60,20) ),
( (50,20) , (50,20) , (15,10) ),
( (50,20) , (80,15) , (35,20) )
);
Внести дополнения потребует и массив BaseSkillTable
(впрочем, реально этот массив не используется):
Const BaseSkill_Table: array[1..MaxSkills] of
Integer =
(
…, 10, 25 30
);
Поместим обработку нового навыка в процедуру SkillTest
(модуль Него):
Case skl of
SkillHandWeapon,
SkillRandedWeapon,
SkillDefence:
Begin
succesSkillTest(11,skl);
end;
SuccesSkillTest:
Case skl of
SkillRandedWeapon:
Begin
If random(35)=0
then
Begin
showInfo(STR_RANGEDWEAPONSKILL_OK);
Int.H.Skills[skillRangedWeapon];
End;
End;
Саму константу опишем так:
Const RangedWeaponSKILL_OK= ' Навык стрельбы повышен! ' ;
Добавим в процедуру HeroShot проверку того, имеется ли в руках у героя нужное
оружие (кстати, эту проверку надо сделать первой, до анализа/проверки как в
этой процедуре, так и в HeroAttack), и
есть ли в этом оружии заряды:
Procedure HeroShot (dx,dy: Integer) ;
Begin
If Heroes[CurHero].Slots[SLotHands].ITYPE <>
iteraRangedWeapon 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
…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; у := Heroes[CurHero].у;
while n
> 0 do
begin
x :=
x+dx;
у := 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.
Сам процесс атаки будет похож на соответствующие
действия в ходе ручной схватки. Пока мы не определились, какой будет поражающая
способность выстрела. Пусть эта способность определяется луком. Тогда в массиве
hits для предметов-луков задействуем еще два элемента, хранящих общеизвестную
пару чисел "число кубиков - число граней на каждом кубике:
const
intRangedAmmo
= 1;
intRangedRange = 2;
intRangedDices = 3;
intRangedDiceNum = 4;
Наш единственный лук теперь будет выглядеть так (исправлен
массив Ints):
(ID:6; х: 0; у: 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]);
Следующие действия по использованию этой величины
совпадут с действиями из процедуры MeroAttack, поэтому желательно выделить их в
отдельный блок:
Procedure HeroAttackFin(
var H: THero; m, dam: Integer );
Var …:integer;
Begin
…:=RollDice(Monsters[m].dd1,
Monsters[m].dd2);
If …=dam then
Begin
ShowInfo(STR_BIG_SKIN);
Exit;
End;
…Monsters[m].HP,
dam-skin);
If Monsters[m].HP<=0
then
Begin
…(Monsters[m].Name+STR_MON_KILL);
…Monsters[m].HP);
End;
End;
Добавим старый код HeroAttack в
конце.
…RollDice(3,6);
…Chars[chrSTR] then
…:=dam+dam div 2;
HeroAttackFin(H,m,dam);
…
MonsterOnTile(x,y);
If … then
RollDice(….Slots[slotHands].ints(intRangedDice),
….Slots[slotHands].ints[intRangedDiceNum]);
HeroAttackFin(Heroes[CurHero],m,dam);
…
End;
Запустим программу и убедимся, что стрельба ведется
нормально.
Ограничимся двумя дополнительными возможностями.
Во-первых, в зависимости от силы повышать дальность полета стрелы, а во-вторых,
в зависимости от удачи будем давать бонус на поражающую способность способом.
Сначала попробуем добавить бонус на дальность в 30%:
Heroes[curHero].Slots[slotHands].ints[intRangedRange];
…(3,6);
… dam div 2;
Расчитаем силу выстрела:
RollDice(
Heroes [CurHero]
.Slots [slotHands] . Ints"[i3*RangedDice.s] ,
Heroes[CurHero].Slots[slotHands].Ints[intRangedDiceNum]);
d :=
RollDiceO, 6) ;
if d <=
Heroes[CurHero].Chars[chrSTR]
then
dam := dam +
dam div 3;
Данный механизм бонусов, конечно, сильно несовершенен
- в профессионально сделанных ролевых системах нет такого гладкого изменения
способностей. Так, уровень навыка 13 и ниже может вообще никогда не приносить
никаких бонусов, а уровень 17 наоборот может внести уже очень существенный
вклад. Но мы используем его в основном для наглядности, чтобы программа была
работоспособна. Читатель сам может придумать произвольные системы начисления
бонусов, главное - как следует сбалансировать игру. Правда, стрелы у нашего
героя в ходе тестирования класса "лучник" будут быстро кончаться,
поэтому лучше будет все же разместить в пакете со стрелами не 15, как сейчас, а
хотя бы 100 стрел:
(ID:5; х:0; у: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)),
А также сотню боеприпасов придать персонажу в момент
его генерации (процедура Generatellero):
if
Heroes[CurHero].Class = classRanger
then
begin
Heroes[CurHero].Items[1] :=
ItemTypes[6];
Heroes[CurHero].Items[1].Ints[intRangedAmmo] :=
100;
end;
Далее - обучаем Героя Магии.
Источник: delgame.at.ua
|