Давайте сделаем рогалик (Ближний бой)
Сейчас мы достигли точки, где уже возможно участвовать в ближнем бою. Чтобы атаковать, мы просто врезаться в монстра, в результате чего, нападаем на него с тем, что у нас есть под рукой в данный момент. Реализовано все это в подпрограмме MoveChar.
dod.bas
'Передвижение персонажа, основанное на направлении по компасу. Function MoveChar(comp As compass) As Integer Dim As Integer ret = FALSE, block Dim As vec vc = vec(pchar.Locx, pchar.Locy) 'Creates a vector object. Dim As terrainids tileid Dim As Integer snd vc+= comp 'Убедимся, что не вышли за границы карты. If (vc.vx >= 1) And (vc.vx <= mapw) Then If (vc.vy >= 1) And (vc.vy <= maph) Then 'Проверим, заблокирована ли ячейка карты. block = level.IsBlocking(vc.vx, vc.vy) 'Проверим, есть ли на ячейке монстр. If block = FALSE Then block = level.IsMonster(vc.vx, vc.vy) 'Проверим столкновение с монстром. If block = TRUE Then 'Атакуем монстра. DoMeleeCombat vc.vx, vc.vy ret = TRUE Endif Endif
Функция IsMonster является частью объекта уровня и возвращает TRUE если
по координатам x, y карты уровня находится монстр.
map.bi
'Возвращает «истина» если по указанным координатам находится монстр. Function levelobj.IsMonster(x As Integer, y As Integer) As Integer Dim As Integer ret = FALSE If _level.lmap(x, y).monidx > 0 Then ret = TRUE Endif Return ret End Function
Если возвращается значение TRUE, то для боя с монстром мы вызываем
подпрограмму DoMeleeeCombat.
dod.bas
'Ближний бой. Sub DoMeleeCombat(mx As Integer, my As Integer) Dim As Integer cf, df, croll, mroll, dam, isdead, midx, mxp, xp Dim As String txt, mname Dim As Single marm 'Получим фактор защиты монстра. df = level.GetMonsterDefense(mx, my) mname = level.GetMonsterName(mx, my) 'Получим здоровье монстра. mxp = level.GetMonsterXP(mx, my) 'Убедимся что есть что атаковать. If df > 0 Then 'Получим боевой фактор, основываясь на том, что у персонажа в руках. cf = pchar.GetMeleeCombatFactor() 'Получим случайные значения, зависяцие от факторов защиты и атаки. croll = RandomRange(1, cf) mroll = RandomRange(1, df) 'Если у персонажа выпало большее значение. If croll > mroll Then 'Получим повреждение, наносимое оружием. dam = pchar.GetWeaponDamage() 'Получим фактор брони монстра. marm = level.GetMonsterArmor(mx, my) 'Рассчитаем нанесенные повреждения. dam = dam - (dam * marm) If dam <= 0 Then dam = 1 'Изменим здоровье монстра. isdead = level.ApplyDamage(mx, my, dam) 'Напечатаем сообщение. If isdead = TRUE Then 'Добавим персонажу опыт. xp = pchar.CurrXP xp += mxp pchar.CurrXP = xp xp = pchar.TotXP xp += mxp pchar.TotXP = xp 'Печатаем сообщение. txt = pchar.CharName & " killed the " & mname & " with " & dam & " damage points." PrintMessage txt Else txt = pchar.CharName & " hit the " & mname & " for " & dam & " damage points." PrintMessage txt Endif Else txt = pchar.CharName & " missed the " & mname & "." PrintMessage txt Endif Endif End Sub
Как вы можете видеть, тут нет ничего сложного. Первое что мы делаем это
получаем значения боевого наступательного фактора персонажа и оборонительного
фактора монстра. Далее мы получаем 2 случайных числа, зависящих от этих
факторов, и, если у персонажа выпало большее значение, то он попал по
монстру.
Если персонаж попал, то рассчитываются нанесенные им повреждения, которые уменьшаются в зависимости от значения параметра брони монстра. Так как после корректировки повреждений в зависимости от брони у нас может получиться значение 0, то мы должны это проверить и скорректировать, чтобы при попадании в монстра ему наносились хотя бы минимальные повреждения.
Для нанесения монстру повреждений мы используем новую подпрограмму ApplyDamage и, если монстр умер, мы добавляем персонажу опыт. Результат боя отображается на экране при помощи подпрограммы PrintMessage. Ячейка карты на которой находится монстр является блокирующей движение ячейкой. поэтому персонаж не переместится на позицию, занимаемую монстром, как можно было бы подумать. Давайте рассмотрим вспомогательные процедуры, используемые в подпрограмме сражения.
Первое что мы должны сделать, это получить фактор защиты монстра.
map.bi
'Возвращает фактор зашиты монстра. Function levelobj.GetMonsterDefense(mx As Integer, my As Integer) As Integer Dim As Integer ret = 0, midx = 0 'Убедимся что монстр есть в этих координатах. If _level.lmap(mx, my).monidx > 0 Then midx = _level.lmap(mx, my).monidx ret = _level.moninfo(midx).cd Endif Return ret End Function
В функцию передаются x и y координаты в которых на карте находится
монстр. Используя их мы получаем id монстра из массива монстров на карте и уже
из него получаем значение возвращаемого функцией параметра cd у конкретного
монстра.
Далее нам необходимо получить имя монстра, для использования его в выводимых игроку сообщения.
map.bi
'Возвращает имя монстра Function levelobj.GetMonsterName(mx As Integer, my As Integer) As String Dim As String ret Dim As Integer midx 'Убедимся что монстр есть в этих координатах. If _level.lmap(mx, my).monidx > 0 Then midx = _level.lmap(mx, my).monidx ret = _level.moninfo(midx).mname Endif Return ret End Function
Эта функция аналогичны предыдущей, только возвращает не фактор защиты
монстра а значение параметра mname в котором хранится его имя.
Так как персонаж может убить монстра, то мы должны получить количество опыта, которое добавится персонажу после его смерти.
map.bi
'Возвращает кол-во опыта, начисляемого за убийство монстра. Function levelobj.GetMonsterXP(mx As Integer, my As Integer) As Integer Dim As Integer midx, ret 'Убедимся что монстр присутствует. If _level.lmap(mx, my).monidx > 0 Then midx = _level.lmap(mx, my).monidx ret = _level.moninfo(midx).xp Endif Return ret End Function
Количество начисляемого опыта генерируется во время создания монстра и
содержится в поле xp.
После получения данных о монстре, нам необходимо получить данные о атакующем факторе персонажа, который зависит от того, есть ли у него что либо в руках. Если нет никакого оружия то для параметра атаки мы берем значение поля UCF из объекта персонажа, если же у него в руках какой либо меч, то значение поля ACF и т. д. Мы должны проверить слоты экипированных на персонажа предметов чтобы узнать, какой фактор использовать.
character.bi
'Возвращает текущтй боевой фактор персонажа основываясь на его вооружении. Function character.GetMeleeCombatFactor() As Integer Dim As Integer ret 'Безоружный бой. If (_cinfo.cwield(wPrimary).classid <> clWeapon) And (_cinfo.cwield(wSecondary).classid <> clWeapon) Then ret = CurrUcf + BonUcf Else 'Проверим главный оружейный слот. If _cinfo.cwield(wPrimary).classid = clWeapon Then 'Орудие ближнего боя. If _cinfo.cwield(wPrimary).weapon.id < wpSling Then ret = CurrAcf + BonAcf Else 'Возвращаем фактор безоружного боя если в руках нет контактного оружия. ret = CurrUcf + BonUcf Endif Else If _cinfo.cwield(wSecondary).classid = clWeapon Then 'Орудие ближнего боя. If _cinfo.cwield(wSecondary).weapon.id < wpSling Then ret = CurrAcf + BonAcf Else 'Возвращаем фактор безоружного боя если в руках нет контактного оружия. ret = CurrUcf + BonUcf End If Endif End If Endif Return ret End Function
Мы проверяем слоты персонажа чтобы узнать что находится у него в руках.
Если персонаж безоружен или держит в руках дистанционное оружие, то мы
возвращаем фактор безоружного боя. Если же в руках персонажа оружие ближнего
боя, т. е. меч, посох, жезл и т. д. то возвращаем фактор атаки с использованием
оружия.
Если персонаж попал по монстру, то мы должны получить вулицину урона, наносимого используемым оружием.
character.bi
'Возвращает величину урона наносимого оружием. Function character.GetWeaponDamage() As Integer Dim As Integer ret = 0 'Посмотрим, есть ли оружие. If (_cinfo.cwield(wPrimary).classid <> clWeapon) And (_cinfo.cwield(wSecondary).classid <> clWeapon) Then 'Если оружия нет, то повреждения зависят от силы и бонуса силы. ret = (_cinfo.stratt(0) + _cinfo.stratt(1)) / 2 Else 'Есть одно или более одного оружия. If _cinfo.cwield(wPrimary).classid = clWeapon Then 'Получим повреждения от текущего оружия. ret = _cinfo.cwield(wPrimary).weapon.dam Endif If _cinfo.cwield(wSecondary).classid = clWeapon Then ret += _cinfo.cwield(wSecondary).weapon.dam Endif Endif Return ret End Function
Вначале мы проверяем, вооружен ли персонаж. И если нет, то рассчитываем
величину повреждения как половину силы персонажа. Если у него в руках оружие, то
получаем значение наносимых повреждений данным оружием, если в обоих руках
персонажа по оружию, то наносимые атакой повреждения складываются.
Для получения величины повреждений, наносимых монстру, мы также должны учитывать его броню. Поэтому нам необходима функция для получения ее значения.
map.bi
'Возвращает рейтинг брони монстра. Function levelobj.GetMonsterArmor(mx As Integer, my As Integer) As Single Dim As Single ret Dim As Integer midx 'Убедимся что монстр есть по указанным координатам. If _level.lmap(mx, my).monidx > 0 Then midx = _level.lmap(mx, my).monidx ret = _level.moninfo(midx).armval Endif Return ret End Function
Значение брони храниться в поле armval и мы возвращаем его в подпрограмму
боя. Броня указана в процентах и поглощает часть наносимых повреждений. Точно
также работает и броня персонажа.
dod.bas
'Получим фактор брони монстра. marm = level.GetMonsterArmor(mx, my) 'Рассчитаем нанесенные повреждения. dam = dam - (dam * marm) If dam <= 0 Then dam = 1 'Изменим здоровье монстра. isdead = level.ApplyDamage(mx, my, dam)
Мы умножаем значение фактора брони монстра на величину наносимых
повреждений чтобы узнать какое количество было поглощено броней. Оставшуюся
величину урона монстр получает в функции ApplyDamage.
map.bi
'Наносит повреждения монстру. Возвращает «истина» если монстр умер. Function levelobj.ApplyDamage(mx As Integer, my As Integer, dam As Integer) As Integer Dim As Integer midx, i, ret = FALSE Dim As vec v 'Проверим что монстр находится здесь. If _level.lmap(mx, my).monidx > 0 Then midx = _level.lmap(mx, my).monidx _level.moninfo(midx).currhp = _level.moninfo(midx).currhp - dam 'Проверка на переход в состояние «убегает». If _level.moninfo(midx).currhp < 2 Then _level.moninfo(midx).flee = TRUE 'Проверим умер ли монстр. If _level.moninfo(midx).currhp <= 0 Then 'Монстр мертв. ret = TRUE 'Установим флаг смерти монстра. _level.moninfo(midx).isdead = TRUE 'Уберем его с карты подземелья. _level.lmap(mx, my).monidx = 0 'Выбросим предметы находящиеся у него. If _level.moninfo(midx).dropcount > 0 Then For i = 1 To _level.moninfo(midx).dropcount For j As compass = north To nwest v.vx = mx v.vy = my v += j 'Если есть пустое место на карте. If (_level.lmap(v.vx, v.vy).terrid = tFloor) And (_level.linv(v.vx, v.vy).classid = clNone) Then PutItemOnMap v.vx, v.vy, _level.moninfo(midx).dropitem(i) Exit For Endif Next ClearInv _level.moninfo(midx).dropitem(i) Next Endif Endif Endif Return ret End Function
Мы следуем той же процедуре что и в других подпрограмма работающих с
монстрами. Вначале мы получаем индекс монстра и вычитаем количество наносимых
повреждений из его поля currhp, в котором храниться текущий уровень его
здоровья. Если персонаж убил монстра, то мы устанавливаем флаг isdead в значение
TRUE, что бы не пытаться ходить этим монстром во время следующего хода игры.
Также необходимо в массиве ячеек карты установить индекс монстра на данной
ячейке в значение 0, чтобы не он не отображался, когда мы будем перерисовывать
карту. Наконец мы проверяем значение параметра dropcount монстра, и если оно не
равно 0, то размешаем на карте предметы которые у него находились, если на карте
есть достаточно места.
Конечно, не только игрок может атаковать монстров, но и монстры игрока. Монстры получают свою очередь для атаки в цикле их передвижения.
map.bi:MoveMonsters
... 'Проверим расстояние до персонажа. pdist = CalcDist(_level.moninfo(i).currcoord.x, pchar.Locx, _level.moninfo(i).currcoord.y, pchar.Locy) 'Проверим дальность атаки монстра. If pdist <= _level.moninfo(i).atkrange Then 'Атакуем персонажа. MonsterAttack _level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y Else ...
Мы уже рассматривали эту процедуру в предыдущей главе, сейчас мы просто
добавили вызов подпрограммы MonsterAttack.
map.bi
'Монстр атакует персонажа. Sub levelobj.MonsterAttack(mx As Integer, my As Integer) Dim As Integer midx, cd, mc, rollc, rollm, chp, dam Dim As String txt Dim As Single arm 'Убедимся что монстр есть в необходимых координатах. If _level.lmap(mx, my).monidx > 0 Then midx = _level.lmap(mx, my).monidx 'Получим фактор защиты персонажа. cd = pchar.GetDefenseFactor() 'Получим фактор атаки монстра. mc = _level.moninfo(midx).cf 'Получим случайные значения. rollc = RandomRange(1, cd) rollm = RandomRange(1, mc) 'Сравним значения монстра и персонажа. If rollm > rollc Then 'Получим величину повреждений. dam = _level.moninfo(midx).atkdam 'Получим броню щитов. arm = pchar.GetShieldArmorValue() 'Поглотим часть повреждений щитами. If arm > 0 Then dam = dam - (dam * arm) End If 'Получим броню доспехов. arm = pchar.GetArmorValue () 'Поглотим часть повреждений доспехами. If arm > 0 Then dam = dam - (dam * arm) End If If dam < 1 Then dam = 1 'Получим здоровье персонажа. chp = pchar.CurrHP 'Вычтем повреждения. chp -= dam If chp < 0 Then chp = 0 'Обновим здоровье персонажа. pchar.CurrHP = chp txt = "The " & _level.moninfo(midx).mname & " hits for " & dam & " damage points." Else txt = "The " & _level.moninfo(midx).mname & " misses." Endif PrintMessage txt End If End Sub
Подпрограмма MonsterAttack практически идентична DoMeleeCombat. Мы
получаем значения атакующего боевого фактора монстра и защитного фактора
персонажа. На основе их генерируем случайные числа и сравниваем, что бы
определить — попал монстр по персонажу или нет. Если монстр попал, то необходимо
получить значение брони персонажа и шита (если есть), причем расчет поглощаемых
повреждений происходит в 2 этапа. Вначале повреждения поглощаются значением
брони щита, так как это первая лини обороны, оставшиеся повреждения участвуют в
расчете поглощаемых повреждений доспехами. Так же как и для повреждений монстра,
повреждения персонажа должны быть минимум 1 единица здоровья.
Для реализации этой подпрограммы мы добавили несколько дополнительных функций к объекту персонажа. Первая возвращает текущий фактор зашиты с бонусами.
character.bi
'Возвращает фактор зашиты персонажа. Function character.GetDefenseFactor () As Integer Return currCdf + BonCdf End Function
Также нам необходимо получить значение брони персонажа.
character.bi
'Возвращает текущее значение брони персонажа. Function character.GetArmorValue() As Single Dim As Single ret = 0.0 'Проверим, использует ли персонаж броню. If _cinfo.cwield(wArmor).classid <> clNone Then ret = _cinfo.cwield(wArmor).armor.dampct Endif Return ret End Function
Мы проверяем слот персонажа для доспехов. И если персонаж использует
какие либо доспехи, то возвращаем значения их их брони. Также нам необходимо
получить броню щита.
character.bi
'Возвращает значение брони щита, используемого персонажем. Function character.GetShieldArmorValue () As Single Dim As Single ret = 0.0 Dim As Integer cnt 'Проверим возможные слоты расположения щита. If _cinfo.cwield(wPrimary).classid = clShield Then ret += _cinfo.cwield(wPrimary).shield.dampct cnt = 1 Endif If _cinfo.cwield(wSecondary).classid = clShield Then ret += _cinfo.cwield(wSecondary).shield.dampct cnt += 1 Endif 'Возьмем среднее значение если используется более одного. If cnt > 0 Then ret = ret / cnt Endif Return ret End Function
Если персонаж использует один или более щитов, то мы возвращаем их
среднее значение брони в подпрограмму атаки. Это выглядит разумным, предполагая
что удар персонаж блокирует обоими щитами одновременно.
Проверка на смерть персонажа добавлена в основной цикл программы.
dod.bas
'Так как надата клавиша обработаем необходимое действие. pchar.DoTimedActions 'Проверим, умер ли персонаж?. If pchar.CurrHP <= 0 Then isdead = TRUE Endif
Мы добавили эту проверку ранее, во время добавления флага отравления
персонажа.
Наконец то мы достигли еще одной важной вехи в создании нашей игры, это реализация ближнего боя. Если вы попробуете поиграть в то что у нас уже получилось, то обнаружите что долго продержаться живым не удастся, но это изменится, как только мы добавим восстановление здоровья при отдыхе и магию.
Перевод на русский: Fantik
содержание | назад | вперед