Давайте сделаем рогалик (Обобщаем атрибуты персонажа)
В следующей главе мы будем добавлять зелья в нашу игру. Зелья будут влиять на атрибуты персонажа, но в данный момент доступ к атрибутам не очевиден. Если вы помните, для каждого атрибута мы создали массив размерностью в 3 элемента. Значение с индексом 0 содержит базовое значение характеристики, 1 — бонус и 2 — время действия бонуса заданное в ходах. И хотя это работает, возможно спустя некоторое время увидев в коде программы число 0 мы не будем знать, что это за значение и что оно означает. Вместо использования 0-ля мы можем использовать мнемоническое значение, например idxAttr, которое будет указывать на базовое значение атрибута. Это гораздо яснее и позволяет изменять программу очень легко, просто изменив значение idxAttr.
Так как характеристики персонажа и его боевые факторы у нас содержаться в массивах, то самый простой способ обобщения, это использовать перечисление.
character.bi
'Индексы для массивов атрибутов и факторов. Enum attrindex idxAttr = 0 'Атрибут или фактор idxAttrBon 'Бонус idxAttrCnt 'Счетчик idxArrMax 'Максимальный индекс массива. Добавляйте индексы перед этим. End Enum
Мы могли бы использовать #Define для этих значений, но перечисление даст
нам некоторые преимущества. Предположим что нам понадобиться добавить новый
элемент в массив хранящий значения атрибутов и бонусов. Все что нам нужно будет
сделать, это вставить в перечисление названия индекса нового элемента перед
idxArrmax, что увеличит размерность массивов атрибутов автоматически, так как
цифровое значение idxArrmax автоматически увеличиться на единицу. Нам не
придется вручную вносить изменения в массивы описанные в объекте персонажа. Все
что нам нужно будет сделать, это добавить новые функции, необходимые для
обработки нового элемента массива. Это сделает работу по обновлению объекта
персонажа на одном дыхании.
Хотя у нас довольно много мест в коде где мы обращались к атрибутам персонажа по индексу обновление кода будет не очень затратным. Мы просто используем функцию поиска и замены нашего редактора. Будем заменять (0) на (idxAttr). Это требует некоторой осторожности, но при помощи «поиска и замены» а не глобального «заменить все» мы сможем контролировать процесс и изменять только необходимые строки кода.
Давайте рассмотрим несколько примеров того, как это пойдет нам на пользу.
character.bi
'Определение атрибутов персонажа. Type characterinfo cname As String * 35 'Имя. stratt(idxArrMax) As Integer 'Сила (0), Бонус силы (1), Продолжительность действия бонуса в ходах (2) staatt(idxArrMax) As Integer 'Выносливость dexatt(idxArrMax) As Integer 'Скорость aglatt(idxArrMax) As Integer 'Подвижность intatt(idxArrMax) As Integer 'Интелект currhp As Integer 'Текущее здоровье maxhp As Integer 'Максимальное здоровье currmana As Integer 'Текушая мана maxmana As Integer 'Максимальная мана ucfsk(idxArrMax) As Integer 'Безоружный бой acfsk(idxArrMax) As Integer 'Контактный бой pcfsk(idxArrMax) As Integer 'Бистанционный бой mcfsk(idxArrMax) As Integer 'Магическая атака cdfsk(idxArrMax) As Integer 'Защита mdfsk(idxArrMax) As Integer 'Магическая защита ...
Здесь мы создаем массивы атрибутов и факторов персонажа. Обратите
внимание, что вместо загадочных 3, теперь используется idxArrMax. Если мы
изменим количество значений в перечислении, то размерность массивов поменяется
автоматически и нам не нужно будет ничего менять в данном коде.
character.bi:GenerateCharacter
... 'Отображение характеристик персонажа. If ret = TRUE Then 'Генерируем характеристики персонажа. Do With _cinfo .cname = chname .stratt(idxAttr) = RandomRange (10, 20) 'Значение характеристик. .staatt(idxAttr) = RandomRange (10, 20) .dexatt(idxAttr) = RandomRange (10, 20) .aglatt(idxAttr) = RandomRange (10, 20) .intatt(idxAttr) = RandomRange (10, 20) .stratt(idxAttrBon) = 0 'Очистим все бонусы. .staatt(idxAttrBon) = 0 .dexatt(idxAttrBon) = 0 .aglatt(idxAttrBon) = 0 .intatt(idxAttrBon) = 0 .stratt(idxAttrCnt) = 0 'Очистим счетчики бонусов. .staatt(idxAttrCnt) = 0 .dexatt(idxAttrCnt) = 0 .aglatt(idxAttrCnt) = 0 .intatt(idxAttrCnt) = 0 .currhp = .stratt(idxAttr) + .staatt(idxAttr) .maxhp = .currhp .currmana = .intatt(idxAttr) + .staatt(idxAttr) .maxmana = .currmana .ucfsk(idxAttr) = .stratt(idxAttr) + .aglatt(idxAttr) .acfsk(idxAttr) = .stratt(idxAttr) + .dexatt(idxAttr) .pcfsk(idxAttr) = .dexatt(idxAttr) + .intatt(idxAttr) .mcfsk(idxAttr) = .intatt(idxAttr) + .staatt(idxAttr) .cdfsk(idxAttr) = .stratt(idxAttr) + .aglatt(idxAttr) .mdfsk(idxAttr) = .aglatt(idxAttr) + .intatt(idxAttr) .ucfsk(idxAttrBon) = 0 'Очистим бонусы. .acfsk(idxAttrBon) = 0 .pcfsk(idxAttrBon) = 0 .mcfsk(idxAttrBon) = 0 .cdfsk(idxAttrBon) = 0 .mdfsk(idxAttrBon) = 0 .ucfsk(idxAttrCnt) = 0 'Очистим счетчики. .acfsk(idxAttrCnt) = 0 .pcfsk(idxAttrCnt) = 0 .mcfsk(idxAttrCnt) = 0 .cdfsk(idxAttrCnt) = 0 .mdfsk(idxAttrCnt) = 0 ...
Здесь показан фрагмент кода генерации персонажа. Вместо непонятных 0, 1 и
2 теперь указаны понятные людям теги, которые дают нам гораздо больше
информации. Мы можем сразу сказать что это атрибут или фактор, бонус или его
продолжительность. И если нам нужно будет поменять порядок их следования, то нам
не придется менять этот код, за нас это сделает компилятор.
В качестве примера того, как теперь легко обновлять код, давайте добавим функцию BestArmor, чтобы при создании персонажа выдавать ему наилучшую броню, которую он может носить. Как вы помните, есть ограничение по параметру силы для использования брони, так что для определения наилучшей возможной, мы будем проверять значение атрибута силы персонажа.
character.bi
'Возвращает лучшую броню для текущей силы персонажа. Function character._GetBestArmor() As armorids Dim As armorids ret = armArmorNone If _cinfo.stratt(idxAttr) >= strPlate Then ret = armPlate Elseif _cinfo.stratt(idxAttr) >= strScale Then ret = armScale Elseif _cinfo.stratt(idxAttr) >= strChain Then ret = armChain Elseif _cinfo.stratt(idxAttr) >= strBrigantine Then ret = armBrigantine Elseif _cinfo.stratt(idxAttr) >= strRing Then ret = armRing Elseif _cinfo.stratt(idxAttr) >= strCuirboli Then ret = armCuirboli Elseif _cinfo.stratt(idxAttr) >= strLeather Then ret = armLeather Elseif _cinfo.stratt(idxAttr) >= strCloth Then ret = armCloth Endif Return ret End Function
Подчеркивание в имени функции означает что она приватная и может быть
вызвана только изнутри объекта. Мы начинаем без брони и поочередно сравниваем
силу персонажа с требованиями силы для различных доспехов. Здесь используются
конструкции If — Else для того, чтобы сразу остановить проверку, как только мы
нашли броню с необходимыми требованиями, а не перебирать все варианты.
Требования значения силы для каждого типа брони содержится в inv.bi.
inv.bi
'Требования значения силы для брони. Const strCloth = 10 Const strLeather = 50 Const strCuirboli = 100 Const strRing = 150 Const strBrigantine = 200 Const strChain = 250 Const strScale = 300 Const strPlate = 350
Мы используем определение констант для указаний требуемого значения силы.
Определения #Define сработали бы также хорошо, возможно, даже слишком. Я всегда
использую Const если есть вероятность что данные константы необходимо указать
внутри объекта или пространства имен. Область видимости Const — блок в котором
он объявлен, в отличии от #Define, который глобально влияет на всю программу в
целом.
Теперь, когда мы определили константы для требований силы, мы можем использовать их и при создании брони.
inv.bi:GenerateArmor
'Установим значения для конкретной брони. Select Case item Case armCloth inv.desc = "Cloth Armor" 'Cloth armor: 1% damage reduction. inv.armor.noise = 1 inv.armor.dampct = .01 inv.armor.struse = strCloth Case armLeather 'Leather armor: 5% damage reduction inv.desc = "Leather Armor" inv.armor.noise = 5 inv.armor.dampct = .05 inv.armor.struse = strLeather ...
Также, как мы делали с атрибутами, здесь мы использовали символические
значения переменных. Чтобы сделать обновления значительно проще. Для внесения
изменений нам необходимо будет просто поменять значения констант, которые также
используется и в методе GetBestArmor.
Теперь нам необходимо обновить подпрограмму генерации персонажа, чтобы выдать ему броню.
character.bi:GenerateCharacter
...
'Добавим лучшую бронб.
arm = _GetBestArmor
GenerateArmor inv, 1, arm
SetInvEval inv, TRUE
AddInvItem wArmor, inv
...
Переменная arm будет содержать идентификатор брони, полученный из
GetBestArmor. Создаем броню, передавая этот идентификатор в GenerateArmor и
помещаем ее в слот брони персонажа. Теперь персонаж экипирован наилучшей броней,
доступной ему при текущем показателе силы.
Я надеюсь, что вы видите, как использование символического значения, а не просто номера, добавляет гибкости нашей программе. В каком то смысле это позволяет программе «перепрограммировать себя», когда должны быть сделаны какие либо изменения, что облегчает нашу работу и увеличивает надежность программы в целом.
Перевод на русский: Fantik
содержание | назад | вперед