Теперь у нас есть броня и оружие и, для того чтобы сражаться, осталось только добавить некоторое количество монстров. Мы добавим в проект новый файл monster.bi, который будет содержать весь код связанный с монстрами.
Первое что нужно сделать, это определить идентификаторы монстров.
monster.bi'Идентификаторы монстров.
Enum monids
monNone
monDarkangel
monGiantbat
monGiantscorpion
monDragon
monElfwarrior
monWisp
monGiant
monHarpy
monIncubus
monJadegolem
monKraken
monLamia
monManticore
monNaga
monOgre
monPhantomfungus
monQuorn
monRockgolem
monSkeleton
monTroll
monUruk
monVampire
monWombat
monXerth
monYeek
monZombie
monFlameangel
monWerebear
monGiantcentipede
monDemonspawn
monElemental
monFlamegolem
monGolem
monHobgoblin
monInterloper
monRovingjelly
monKobold
monLich
monMage
monNazgul
monOrc
monPulsingeye
monTwinhead
monRogue
monShurik
monGianttarantula
monGiantbeetle
monVarghoul
monWraith
monXorn
monYekki
monGriffon
End EnumВсе эти монстры будут у нас в игре. Есть 52 монстра — по одному на каждую букву алфавита верхнего и нижнего регистра. Далее нам нужно определить структуру данных для монстров. Мы будем подходить к монстрам также, как и к предметам инвентаря. Монстры станут частью объекта уровня, так что программный код для монстров будет походить на код для предметов инвентаря.
monster.bi'Определение типа данных для монстров.
Type montype
id As monids 'Идентификатор монстра.
mname As String * 15 'Имя монстра.
micon As String * 1 'Иконка.
mcolor As UInteger 'Цвет иконки.
ismagic As Integer 'Может использовать магию.
spell As Integer 'Атакующее/Зашитное заклинание
cd As Integer 'Фактор защиты
cf As Integer 'Фактор атаки.
md As Integer 'Фактор магической защиты
mf As Integer 'Фактор магической атаки.
currhp As Integer 'Текущее здоровье
xp As Integer 'Получаемый за монстра опыт.
dropcount As Integer 'Количество предметов в инвентаре.
dropitem(1 To 4) As invtype 'Предметы, оставшиеся после смерти монстра.
atkdam As Integer 'Наносимые монстром повреждения.
armval As Single 'Значение защиты в процентах.
atkrange As Integer 'Дистанционная атака.
psighted As Integer 'Указывает что монстр увидел персонажа.
plastloc As mcoord 'Последняя позиция персонажа, нде его видел монстр.
currcoord As mcoord 'Ткущая позиция монстра.
flee As Integer 'Монстр убегает.
isdead As Integer 'Монстр мертв.
End TypeУ каждого монстра есть собственный набор оборонительных и боевых факторов, предметы, содержащиеся в массиве dropitem, значение для наносимого монстром урона и его брони. Флаг psighted указывает на то, что монстр увидел персонажа, а flee, на то, что монстр в данный момент убегает, а не нападает.
Используя данное определение типа мы можем создать монстра.
monster.bi'Генерируем монстра.
Sub GenerateMonster(mon As montype)
Dim As invtype inv
Dim scaling As Integer 'Коэффициент масштабирования атрибутов монстра.
Dim stratt As Integer 'Сила.
Dim staatt As Integer 'Выносливость.
Dim dexatt As Integer 'Ловкость.
Dim aglatt As Integer 'Подвижность.
Dim intatt As Integer 'Интеллект.
Dim ucfsk As Integer 'Навык безоружного боя.
Dim acfsk As Integer 'Навык использования оружия ближнего боя.
Dim pcfsk As Integer 'Навык использования дистанционного оружия.
'Установим характеристи монстра.
mon.id = RandomRange(monDarkangel, monGriffon)
scaling = pchar.CurrStr / 2
stratt = RandomRange(pchar.CurrStr - scaling, pchar.CurrStr + scaling)
scaling = pchar.CurrSta / 2
staatt = RandomRange(pchar.CurrSta - scaling, pchar.CurrSta + scaling)
scaling = pchar.CurrDex / 2
dexatt = RandomRange(pchar.CurrDex - scaling, pchar.CurrDex + scaling)
scaling = pchar.CurrAgl / 2
aglatt = RandomRange(pchar.CurrAgl - scaling, pchar.CurrAgl + scaling)
scaling = pchar.CurrInt / 2
intatt = RandomRange(pchar.CurrInt - scaling, pchar.CurrInt + scaling)
'Рассчитаем боевые факторы.
acfsk = stratt + dexatt
pcfsk = dexatt + intatt
'Установим боевые факторы.
mon.cd = stratt + aglatt
mon.md = aglatt + intatt
mon.mf = intatt + staatt
mon.cf = stratt + aglatt
'Зададим уровень здоровья.
mon.currhp = stratt + staatt
'Не убегает.
mon.flee = FALSE
'Не видит персонажа.
mon.psighted = FALSE
'Не знает о последнем местонахождении персонажа.
mon.plastloc.x = 0
mon.plastloc.y = 0
'Цвет иконки.
mon.mcolor = fbRedBright
'Монст мертв.
mon.isdead = FALSE
'Очистим массив предметов монстра.
For i As Integer = LBound(mon.dropitem) To UBound(mon.dropitem)
ClearInv mon.dropitem(i)
Next
...Эта первая часть процедуры генерации монстров, она устанавливает значения характеристик и устанавливает начальные значения общих флагов. Обратите внимание, что каждая характеристика масштабируется в зависимости от характеристик персонажа. Для значения характеристики монстра мы используем значение характеристики персонажа и прибавляем или отнимаем половину ее значения. Это будет держать монстров приблизительно одного уровня с персонажем по силе, некоторые будут немного слабее персонажа, некоторые немного сильнее. Это просто путь сохранения баланса игры, что бы монстры не стали пушечным мясом, когда мощь персонажа вырастет.
Параметры монстров — временные значения. Мы не храним их в структуре данных монстра, они нужны нам только для расчета защитных и боевых факторов, которые будут использоваться в нашей процедуре боя. Боевые факторы рассчитываются по той же формуле что и при генерации персонажа, поэтому во время боя их значения будут означать одно и тоже для обоих актеров. Для нас это означает что мы не будем иметь дело с какими то абстрактными значениями показателей характеристик монстров, которые нужно подвергать преобразованию перед использованием во время боя, когда мы дойдем до реализации боевых действий. Это не только делает игру более честной, но и упрощает процесс программирования.
Следующий шаг в процедуре GenerateMonster, это задание значений параметрам, зависящим от идентификатора монстра.
monster.bi'Установим индивидуальные атрибуты.
Select Case mon.id
Case monDarkangel
mon.mname = "Dark Angel" 'Имя.
mon.micon = "A" 'Иконка.
mon.ismagic = TRUE 'Использует ли магию.
mon.spell = 0 'Заклинание.
mon.atkrange = 1 'Дальность атаки.
mon.dropcount = 1 'Количество предметов в инвентаре.
GenerateWeapon inv, currlevel, RandomRange(wpSmallsword, wpBishopsflail) 'Оружие.
mon.dropitem(1) = inv 'Добавим предмет в инвентарь.
mon.atkdam = mon.dropitem(1).weapon.dam 'используем повреждения от оружия.
mon.armval = .8 'Рейтинг брони.
mon.cf = acfsk 'Фактор атаки.
Case monGiantbat
mon.mname = "Giant Bat" 'Имя.
mon.micon = "B" 'Иконка.
mon.ismagic = FALSE 'Использует ли магию .
mon.spell = 0 'Заклинание .
mon.atkrange = 1 'Дальность атаки.
mon.dropcount = 0 'Количество предметов в инвентаре.
mon.atkdam = stratt / 4 'Сколько повреждений наносит.
mon.armval = .1 'Рейтинг брони.
Case monGiantscorpion
mon.mname = "Giant Scorpion" 'Имя.
mon.micon = "C" 'Иконка.
mon.ismagic = FALSE 'Использует ли магию.
mon.spell = 0 'Заклинание.
mon.atkrange = 2 'Дальность атаки.
mon.dropcount = 0 'Кол-во предметов в инвентаре.
mon.atkdam = stratt / 4 'Сколько повреждений наносит.
mon.armval = .9 'Рейтинг брони.
...Здесь у нас приведен пример установки характеристик Темному ангелу, Гигантской летучей мыши и Гигантскому скорпиону. У каждого из них есть уникальные черты, которые задают индивидуальность монстра. Обратите внимание, что у Темного ангела есть в инвентаре один предмет, это оружие, генерируемое случайным образом. Урон Темного использует сгенерированное оружие, поэтому наносимый им урон зависит от урона, наносимого этим оружием. Броню он не носит, но у него есть естественная броня, которая поглощает 80% урона и задается параметром armval. Также он может использовать магию, и, когда мы дойдем до реализации магии, мы добавим соответствующие заклинания в параметр spell.
Гигантская летучая мышь и Гигантский скорпион являются являются "естественными" существами и не имеют никаких предметов инвентаря. Это указано в поле dropcount, которое, для этих существ, равно 0. Каждый монстр имеет переменный рейтинг атаки, в результате нет двух Гигантский летучих мышей или двух Гигантских скорпионов, которые наносили бы одинаковое количество повреждений.
Существуют также и «гуманоидные» монстры различных рас, которые попали в темницу в Подземелье Судьбы и перешли на сторону зла. Они будут создаваться с большим количеством предметов инвентаря, что бы больше походить на гуманоидных актеров.
monster.biCase monElfwarrior
mon.mname = "Elf Warrior" 'Имя
mon.micon = "E" 'Иконка.
mon.ismagic = TRUE 'Использует ли магию.
mon.spell = 0 'Заклинание.
mon.atkrange = 1 'Дальность атаки.
mon.dropcount = 3 'Кол-во предметов в инвентаре.
GenerateWeapon inv, currlevel, RandomRange(wpSmallsword, wpBishopsflail) 'Оружие.
mon.dropitem(1) = inv 'Добавим предметы в инвентарь.
mon.atkdam = mon.dropitem(1).weapon.dam
ClearInv inv
GenerateArmor inv, currlevel, RandomRange(armCloth, armPlate)
mon.dropitem(2) = inv
mon.armval = mon.dropitem(2).armor.dampct 'Armor rating.
ClearInv inv 'Extra item.
GenerateSupplies inv, currlevel
mon.dropitem(3) = inv
mon.cf = acfsk 'Фактор атаки.Здесь, у воина эльфа, есть как оружие, так и броня. Поэтому мы используем оба предмета для того, чтобы задать параметры наносимых повреждений и брони монстра.
Поле atkrange задает дальность атаки монстра. Для большинства оно будет равно единице, но некоторые монстры будут способны достать персонажа и на расстоянии.
monster.biCase monDragon
mon.mname = "Dragon" 'Name
mon.micon = "D" 'Map icon.
mon.ismagic = FALSE 'Magic flag.
mon.spell = 0 'If true then spell.
mon.atkrange = 6 'Attack range.
mon.dropcount = 0 'Number of items in inventory.
mon.atkdam = stratt / 4 'How much damage mon does.
mon.armval = .9 'Armor rating.
mon.cf = pcfsk 'Combt attack factor.У Дракона дальность атаки 6, кроме того, его естественная броня поглощает 90% повреждений, поэтому он будет очень опасным противником, даже если его характеристики будут близки к характеристикам персонажа.
Как вы можете видеть, даже используя масштабирование параметров монстров, у нас для каждого монстра создаются различные вариации в пределах своего типа. Это сделает монстров разнообразней и интересней, а также обеспечит игроку разумные проблемы.
Это все, что есть в monster.bi на данный момент. Для того чтобы разместить монстров на карте, мы должны создать их во время генерации нового уровня подземелья.
map.bi'Ячейка уровня (тайл)
Type mapinfotype
terrid As terrainids 'Тип местности.
monidx As Integer 'Индекс в массиве монстров.
visible As Integer 'Персонаж может видеть ячейку.
seen As Integer 'Персонаж уже видел ячейку.
doorinfo As doortype 'Параметры двери.
End Type
'Информация о уровне подземелья.
Type levelinfo
numlevel As Integer 'Ткущий уровень подземелья.
lmap(1 To mapw, 1 To maph) As mapinfotype 'Массив карты.
linv(1 To mapw, 1 To maph) As invtype 'Инвентарь карты.
nummon As Integer 'Кол-во монстров на карте: от 10 до maxmonster.
moninfo(1 To nroommax) As montype 'Массив монстров.
End TypeМы обновили структуру mapinfotype добавив в нее поле содержащее индекс монстра из массива монстров, находящихся на карте. Помните, что mapinfotype — это ячейка двумерного массива карты подземелья, которая содержит в себе параметры типа местности, видимости и т. д. В levelinfo мы добавили два новых поля, это nummon — которое содержит количество всех монстров на уровне подземелья, и moninfo, которое представляет из себя массив значений типа montype и содержит характеристики конкретного монстра. Вопрос в том, какое максимальное количество монстров мы должны добавить на уровень? Что бы облегчить задачу, мы поместили монстров в комнатах, в результате. Вполне естественно, что максимальное количество монстров зависит от количества комнат на текущем уровне. Поэтому мы использовать nroommax как верхнюю границу массива монстров.
Для размещения монстров мы начнем с процедуры GenerateDungeonLevel.
map.bi'Создает новый уровень подземелья.
Sub levelobj.GenerateDungeonLevel()
Dim As Integer x, y, i
'Clear level
For x = 1 To mapw
For y = 1 To maph
_level.lmap(x, y).terrid = twall 'Установим стены.
_level.lmap(x, y).visible = FALSE 'Не видима персонажем.
_level.lmap(x, y).seen = FALSE 'Персонаж не видел.
_level.lmap(x, y).monidx = 0 'Нет монстра.
_level.lmap(x, y).doorinfo.locked = FALSE 'Дверь не заперта.
_level.lmap(x, y).doorinfo.lockdr = 0 'Не указа сложность.
_level.lmap(x, y).doorinfo.dstr = 0 'Не указана сила.
_level.nummon = 0 'Кол-во монстров 0.
ClearInv _level.linv(x,y) 'Очистим слоты предметов.
Next
Next
_InitGrid
_DrawMapToArray
_GenerateItems
End SubМы устанавливаем monindex в 0 для каждой клетки на карте и задаем общее количество монстров на карте (nummon) так же равным нулю. Создавать монстров мы будем в подпрограмме DrawMapToArray.
map.bi'Поместим данные из сетки в массив карты уровня.
Sub levelobj._DrawMapToArray()
Dim As Integer i, x, y, pr, rr, rl, ru, kr, nid, moncnt
Dim As mcoord ncoord
'Рисуем в массиве первую комнату
For x = _rooms(1).tl.x + 1 To _rooms(1).br.x - 1
For y = _rooms(1).tl.y + 1 To _rooms(1).br.y - 1
_level.lmap(x, y).terrid = tfloor
Next
Next
'Рисуем остальные комнаты и соединяем их.
For i = 2 To _numrooms
For x = _rooms(i).tl.x + 1 To _rooms(i).br.x - 1
For y = _rooms(i).tl.y + 1 To _rooms(i).br.y - 1
_level.lmap(x, y).terrid = tfloor
Next
Next
'Получим центр комнаты.
ncoord.x = _rooms(i).roomdim.rcoord.x + 1
ncoord.y = _rooms(i).roomdim.rcoord.y + 1
'Добавим в комнату монстра.
If RandomRange(-5, maxlevel) <= currlevel Then
moncnt += 1
If moncnt <= nroommax Then
_level.nummon = moncnt
GenerateMonster _level.moninfo(_level.nummon)
_level.lmap(ncoord.x, ncoord.y).monidx = _level.nummon
_level.moninfo(_level.nummon).currcoord = ncoord
End If
End If
_ConnectRooms i, i - 1
Next
'Добавим двери в выбранную комнату.
_AddDoors
'Зададим местоположение персонажа.
x = _rooms(1).roomdim.rcoord.x + (_rooms(1).roomdim.rwidth \ 2)
y = _rooms(1).roomdim.rcoord.y + (_rooms(1).roomdim.rheight \ 2)
pchar.Locx = x - 1
pchar.Locy = y - 1
'Добавим лестницу вверх.
_level.lmap(pchar.Locx, pchar.Locy).terrid = tstairup
'Добавим лестницу вниз в последней комнате.
x = _rooms(_numrooms).roomdim.rcoord.x + (_rooms(_numrooms).roomdim.rwidth \ 2)
y = _rooms(_numrooms).roomdim.rcoord.y + (_rooms(_numrooms).roomdim.rheight \ 2)
_level.lmap(x - 1, y - 1).terrid = tstairdn
End Sub
Количество монстров в подземелье основывается на количестве комнат и глубине уровня. Код RandomRange (-5, maxlevel) <= currlevel устанавливает вероятность появления в комнате монстра. На глубине нескольких первых уровней монстров будет немного, но, по мере того как персонаж будет спускаться все глубже и глубже, монстры будут встречаться все чаще. Это даст игроку ощущение опасности на более глубоких уровнях подземелья.
map.bi'Получим центр комнаты.
ncoord.x = _rooms(i).roomdim.rcoord.x + 1
ncoord.y = _rooms(i).roomdim.rcoord.y + 1
'Добавим в комнату монстра.
If RandomRange(-5, maxlevel) <= currlevel Then
moncnt += 1
If moncnt <= nroommax Then
_level.nummon = moncnt
GenerateMonster _level.moninfo(_level.nummon)
_level.lmap(ncoord.x, ncoord.y).monidx = _level.nummon
_level.moninfo(_level.nummon).currcoord = ncoord
End If
End If
Как только мы добавили комнату, мы можем добавить монстра. Для этого мы вызываем процедуру GenerateMonster, которая его создаст монстра, записываем его идентификатор в ячейку карты по координатам ncoord, а также добавляем эти координаты в поле currcoor информации о монстре. Это расставит наших монстров на карте.
Последнее, что нам осталось сделать, это отобразить монстров на карте локации, что мы и сделаем в процедуре DrawMap.
map.bi'Если в текущей ячейке есть монстр — нарисуем его.
If _level.lmap(i + x, j + y).monidx > 0 Then
monid = _level.lmap(i + x, j + y).monidx
mtile = _level.moninfo(monid).micon
tilecolor = _level.moninfo(monid).mcolor
PutText acBlock, y + 1, x + 1, fbBlack
PutText mtile, y + 1, x + 1, tilecolor
EndIfЕсли монстр находится в зоне видимости персонажа, то нам нужно его отобразить. Как вы можете видеть, процесс достаточно прост. Если monidx больше 0 то мы получаем иконку монстра и ее цвет из массива монстров и выводим ее на экран, предварительно очистив ячейку: PutText acBlock, y + 1, x + 1, fbBlack.
Монстры, которых персонаж не видит в настоящее время, не будут отображены. Монстры могут передвигаться по карте, поэтому их расположение персонаж не может запомнить и не знает, остался ли монстр на месте или переместился куда либо, если не видит его, что добавляет некоторую напряженность в игру.
Теперь у нас есть монстры на карте и в следующей главе мы заставим их передвигаться.

Комментариев нет:
Отправить комментарий