Рассылка закрыта
При закрытии подписчики были переданы в рассылку "Особенности национального бизнеса" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Создание ролевой компьютерной игры 17) Вступаем в схватку
Информационный Канал Subscribe.Ru |
Разработка ролевой игры17) Вступаем в схватку
Базы данных по характеристикам предметов, монстров и тайлов хранятся в
отдельных файлах с расширением .dat , что позволяет менять их даже
обычному пользователю, не знакомому с языком программирования. Краткое
писание формата этих файлов смотрите в readme.txt Архив брать тут: http://russianenterprisesolutions.com/sbo/download/rog.rar 42 kb
Вступаем в схватку. В какой момент будет начинаться схватка героя с монстрами? Так как мы остановились на одном типе оружия - ручном, ближнего боя, то схватку разумно начинать, когда герой находится в непосредственном контакте с противником - на соседней клетке с ним. Пока персонаж свободно проходит через тайлы с монстрами, сейчас мы это запретим:
procedure MoveHero( dx,dy: Integer );
m := IsMonsterOnTile(Heroes[CurHero].x+dx, Heroes[CurHero].y+dy);
Exit
inc(Heroes[CurHero].x,dx);
... В процедуру MoveHero (перемещение героя, модуль Game) добавлена проверка тайла, на который тот должен встать - имеется ли на тайле монстр. Если да (функция IsMonsterOnTile возвращает индекс монстра в массиве Monsters; эту функцию мы реализуем позже), то надо выполнить определенные действия по нападению на монстра, после чего завершить выполнение MoveHero - ведь передвигать героя на тайл монстра не надо. Функцию IsMonsterOnTile разместим, конечно, в модуле Monster:
function IsMonsterOnTile(x,y: Integer): Integer; Программа пробегает по элементам массива Monsters, проверяя, какой из монстров живой (его здоровье - поле HP, имеет положительное значение), и находится ли на он на тайле с предложенными координатами. Уже в таком виде игра будет приближена к реальности - теперь герой не сможет проходить "сквозь" монстров. Это связано с местом вызова функции IsMonsterOnTile в процедуре MoveHero. Вызов происходит до фактического перемещения героя, когда выполняется изменение его координат. Поэтому, в случае столкновения с монстром герой остается на месте. Все, что от него требуется - это нанести удар и отразить ответную атаку. Отметим, что после того, как герой проведет атаку, на него может нападать не только атакуемый монстр, но и все другие находящиеся поблизости монстры, даже если они и не атакованы. Для программирования схваток создадим новый модуль нашего приложения. Назовем этот модуль, конечно, Combat. Все, что в нем пока будет - это единственная процедура нападения персонажа на конкретного монстра HeroAttack, вызов которой надо разместить в процедуре MoveHero:
... Это заготовка модуля Combat:
unit Combat;
interface uses Hero;
procedure HeroAttack( var H: THero; m: Integer );
implementation
{ --------------------------- }
end;
end. Первоначально в ходе схватки нам надо проверить успешность нападения героя. Для этого предназначен навык skillHandWeapon. Пока практика его применения не реализована, поэтому запрограммируем ее следующим образом (напомним, что проверка успешности навыков выполняется в процедурах SkillTest и SuccessSkillTest модуля Hero):
function SkillTest( var H: THero; skl: Integer ): Boolean;
case skl of
skillHandWeapon:
skillTrapSearch:
end; В данной процедуре никаких специальных действий по обработке успеха или неудачи атаки в отличие от, например, проверки навыка обнаружения ловушек, не происходит. Дело в том, что проверка успешности атаки представляет собой не законченный в смысловом плане этап программы (обнаружил ловушку - она обезврежена, получен опыт, и на этом все). Атака является лишь одним шагом в последовательности взаимных нападений и защит, поэтому обработку ее успешности будем выполнять на более высоком уровне, в процедуре HeroAttack. Здесь же внесем еще одно дополнение в процедуру SuccessSkillTest, где происходит повышение соответствующего навыка. Посмотрим, чему равняется базовое значение навыка атаки (модуль Tables, константа BaseSkill_Table)? Оно равняется 30. Теперь попробуем рассчитать, на сколько может вырасти заданный навык. В среднем на уровне может находиться число монстров, равное 50 (MaxMonsters). Сколько успешных ударов для уничтожения одного монстра потребуется, сказать сложно. Но, по всей видимости оно вряд ли будет больше десяти. Ведь здоровье монстров на первых уровнях составляет 1-2 пункта, а на старших локациях - до 35 единиц. Сила одного удара не моет быть меньше единицы, значит, слабые монстры будут гибнуть как минимум от одного удара. С учетом того, что сила удара оружия также будет расти, среднее число ударов, равное десяти, можно считать явно избыточным. Итак, для уничтожения всех монстров в локации нам потребуется нанести 50 * 10 = 500 успешных ударов. Всего на четырех локациях пещеры герой сможет ударить врага 500 * 4 = 2000 раз. До какого значения при этом возрастет навык атаки? Давайте возьмем величину 80%. То есть после нанесения 2000 ударов значение элемента Skills[skillHandWeapon] увеличится с 30 до 80. Другими словами, каждый пятидесятый (2000 / (80 - 30)) удар можно считать вносящим свой вклад в увеличение навыка рукопашного боя. Выше уже описывался способ повышения навыка skillTrapSearch, когда это повышение происходило случайным образом. Таким же образом мы реализуем рост мастерства атаки:
procedure SuccessSkillTest( var H: THero; skl: Integer);
skillHandWeapon:
skillTrapSearch: Константа STR_HANDWEAPONSKILL_OK (модуль Texts) может быть записана так:
const STR_HANDWEAPONSKILL_OK = ' Навык ручного боя повышен. ' ; Вернемся к процедуре сражения. Проверка неудачного удара (фактически означающего, что персонаж промазал) запишется так:
procedure HeroAttack( var H: THero; m: Integer );
end; Константу STR_BAD_ATTACK опишем в модуле Texts:
const STR_BAD_ATTACK = ' Вы промазали... ' ; Чтобы программа собиралась нормально, в заголовок реализации модуля Combat надо добавить ссылки на следующие модули:
implementation uses Game, LowLevel, Texts; Если же удар достиг своей цели, в дело вступает монстр. Его шкура (поля Dd1, Dd2 структуры TMonster) принимает на себя определенную часть поражающего воздействия. Величина же этого воздействия будет вычисляться на основе параметров оружия. Расчет величины воздействия для оружия вынесем в отдельную функцию:
function WeaponDamage( Itm: TGameItem ): Integer; Если оружие бьет неточно, повреждений, очевидно, нет. Видно, что эта характеристика оружия практически идентична навыку skillHandWeapon, и реальная вероятность попадания по монстру будет равна произведению вероятности, связанной с навыком рукопашного боя, на вероятность попадания, связанная с характеристиками самого оружия. Так, для топора, у которого поле Ints[intAttackHit] равно 50 (см. массив ItemTypes), и героя с базовым навыком рукопашного боя, равным 30 (таблица BaseSkill_Table), вероятность успешного удара будет равна 0,5 * 0,3 = 0,15, что на самом деле весьма невелико. Правильно ли такое дополнительное снижение суммарной вероятности попадания? Да, правильно. Очень важно разделять эту вероятность на две составляющие, связанные как с личными навыками персонажа, и потому достаточно стабильные, так и связанные с характеристиками оружия, и по ходу игры сильно изменяющиеся. Нашел герой меч - сразу стал попадать по монстру чаще. Купил дорогой топор - каждый удар может завалить героя, а вот вероятность попадания существенно снизилась. Кроме того, учет вероятности попадания необходим, когда в игру будет добавлено дальнобойное оружие. Вероятность поражения цели из лука обычно значительно ниже, чем вероятность попадания с помощью, например, кинжала. Расчет величины поражения выполняется функцией RollDice. Эта функция имитирует бросание игровых кубиков и возвращает сумму выпавших на них значений. RollDice может быть весьма полезной и обращаться к ней нам понадобится из разных модулей, поэтому поместим ее реализацию в игровой модуль Game:
function RollDice( d1, d2: Integer ): Integer; В цикле происходит бросок кубика с числом граней, равным d2. Продолжительность цикла задается параметром d1 (число кубиков). Дополним процедуру сражения проверкой величины наносимого поражения. Для этого нам потребуется определить, какое оружие в данный момент использует герой. Такую проверку вынесем в отдельную функцию GetHeroWeapon, так как она не так проста для реализации, как может показаться. Разместим такую функцию в модуле Hero:
function GetHeroWeapon( var H: THero ): Integer; Эта функция существенно зависит от количества слотов персонажа, а также от их назначения. В нашем случае проверять на наличие оружия надо только один слот (slotHands). Если предмета в нем нет, значит, герой не может вести сражение (ему нечем это делать). Функция возвращает номер слота, в котором хранится предмет-оружие, или ноль, если подходящего оружия нет. Вот как будет выглядеть очередная версия процедуры HeroAttack:
procedure HeroAttack( var H: THero; m: Integer );
i := GetHeroWeapon(H);
dam := WeaponDamage( H.Slots[i] ); Текстовые константы описаны в модуле Texts:
const STR_NONE_WEAPONS = ' У вас нет оружия. ' ; Сначала определяется, есть ли у героя оружие. Если его нет, выдается сообщение (STR_NONE_WEAPONS) и работа процедуры заканчивается. В противном случае рассчитывается величина поражения, наносимого оружием героя, а также определяется, какой удар может выдержать шкура монстра (это поля dd1, dd2 в структуре TMonster). Если размер повреждения меньше или равен толщине шкуры, значит, шкуру монстра пробить не удалось (сообщение STR_BIG_SKIN). В данном коде видно, что толщина шкуры (значение переменной skin) случайная величина и будет меняться при каждом новом ударе. Это не ошибка. Такая переменчивость толщины имитирует удар оружием в разные точки тела монстра. Ведь удар по толстому спинному панцирю нанесет ему очень слабое повреждение, а вот удар в незащищенное горло может быть смертельным. Такую задачу и решает колеблющееся значение переменной skin. Продолжение комбата следует. Исходный код текущей версии (всегда проверен и работоспособен, главный файл- main.pas): http://russianenterprisesolutions.com/sbo/download/15115.zip 8758 байтов
(c) 2004-2005 Сергей Бобровский bobrovsky@russianenterprisesolutions.com
Все предыдущие выпуски базового курса тут:
Дизайн рассылки: Алексей Голубев - Web-дизайн и web-программирование |
Subscribe.Ru
Поддержка подписчиков Другие рассылки этой тематики Другие рассылки этого автора |
Подписан адрес:
Код этой рассылки: comp.soft.prog.prognull.game Архив рассылки |
Отписаться
Вспомнить пароль |
В избранное | ||