Давайте сделаем рогалик (Монстры)
Теперь у нас есть броня и оружие и, для того чтобы сражаться, осталось только добавить некоторое количество монстров. Мы добавим в проект новый файл 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.bi
Case 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.bi
Case 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.
Монстры, которых персонаж не видит в настоящее время, не будут отображены. Монстры могут передвигаться по карте, поэтому их расположение персонаж не может запомнить и не знает, остался ли монстр на месте или переместился куда либо, если не видит его, что добавляет некоторую напряженность в игру.
Теперь у нас есть монстры на кате и в следующей главе мы заставим их передвигаться.
Перевод на русский: Fantik
содержание | назад | вперед