Давайте сделаем рогалик (Инвентарь персонажа)
Сейчас у нас есть разбросанные по карте предметы, и мы хотим добавить возможность персонажу их поднять. Нам необходимо добавить в нашу программу код инвентаря персонажа. Так же, как и с предметами на карте, вначале, мы добавим коллекцию предметов в объект описывающий нашего персонажа.
character.bi
'Определение типа данных характеристик персонажа. Type characterinfo cname As String * 35 'Имя персонажа. stratt(3) As Integer 'Сила (0), бонус силы (1), продолжительность действия бонуса в ходах (2) staatt(3) As Integer 'Выносливость dexatt(3) As Integer 'Ловкость aglatt(3) As Integer 'Проворство intatt(3) As Integer 'Интеллект currhp As Integer 'Текущее здоровье maxhp As Integer 'Максимальное здоровье currmana As Integer 'Текущая мана maxmana As Integer 'Максимальная мана ucfsk(3) As Integer 'Рукопашный бой acfsk(3) As Integer 'Оружие ближнего боя pcfsk(3) As Integer 'Дистанционное оружие mcfsk(3) As Integer 'Магическая атака cdfsk(3) As Integer 'Защита mdfsk(3) As Integer 'Магическая защита currxp As Integer 'Текущий, расходуемый опыт. totxp As Integer 'Общая сумма опыта, за время жизни персонажа. currgold As Integer 'Текущее количество золота. totgold As Integer 'Общая сумма золота за время жизни персонажа. ploc As mcoord 'Ткущие x и y координаты персонажа. cinv(97 To 122) As invtype 'Инвентарь персонажа для индексации использует значения asii кодов. End Type
Последнее определение в описании персонажа, это наша коллекция предметов
инвентаря. Обратите внимание на странный диапазон индексов. Это ASCII коды
символов, заканчивающиеся буквой z. Персонаж будет иметь 26 слотов для предметов
инвентаря, в дополнение к тем предмета, которые на нем одеты и которые он держит
в руках. Так как индекс массива инвентаря у нас является кодом символов, то для
выбора какого либо предмета нам необходимо только получить код символа клавиши
клавиатуры, которую нажал пользователь. Здесь мы воспользуемся гибкостью языка
FreeBasic и большую часть работы по выбору предметов из инвентаря за нас
выполнить компилятор. Многие программисты не пользуются гибкостью языка, и, в
конечном итоге, получают громоздкий, трудно поддерживаемый, код.
Что нам нужно сделать сейчас, это позволить игроку поднять предмет, находящийся на карте. Для этого мы добавляем действие «Поднять», если игрок нажимает клавишу «g».
dod.bas
'Поднять предмет с пола и положить в инвентарь персонажа. If ckey = "g" Then 'Inventory type. Dim inv As invtype 'Если под персонажем есть предмет. If level.HasItem(pchar.Locx, pchar.Locy) = TRUE Then 'проверить на залото. Добавляем золото и сколько всего было золота. Dim iclass As classids = level.GetInvClassID(pchar.Locx, pchar.Locy) If iclass = clGold Then 'Поднимаем золото с карты. level.GetItemFromMap pchar.Locx, pchar.Locy, inv 'Добавим к золоту персонажа. pchar.CurrGold = pchar.CurrGold + inv.gold.amt pchar.TotGold = pchar.TotGold + inv.gold.amt 'Добавим опыт. pchar.CurrXP = pchar.CurrXP + inv.gold.amt pchar.TotXP = pchar.TotXP + inv.gold.amt 'Выведем сообщение для игрока. PrintMessage inv.gold.amt & " gold coins collected." DrawMainScreen Else 'Проверим, есть ли свободный слот в инвентаре. Dim As Integer cidx = pchar.GetFreeInventoryIndex 'Если слот найден, поместим туда предмет. If cidx <> -1 Then level.GetItemFromMap pchar.Locx, pchar.Locy, inv 'Добавим в инвентарь персонажа. pchar.AddInvItem cidx, inv PrintMessage "Item added to inventory." Else 'Нет свободных слотов. PrintMessage "No free inventory slots." Endif Endif Else PrintMessage "Nothing to get." Endif Endif
Когда игрок нажимает клавишу «g», мы проверяем, вначале, стоит ли
персонаж на предмете. Если предмет присутствует на той же ячейке карты, что и
персонаж, то, далее, мы получаем идентификатор класса этого предмета. Это нам
нужно для того, что бы отличить золото от других предметов. Золото не
добавляется в инвентарь персонажа а изменяет значение переменно общего
количество денег персонажа, а также, в качестве бонуса, добавляет некоторое
количество опыта. Если предмет не золото, то мы добавляем его в инвентарь.
При добавлении предмета в инвентарь нам нужно проверить, если ли в нем свободное место. Мы делаем это в функции GetFreeInventoryIndex.
character.bi
'Возвращает индекс свободного слота инвентаря или -1. Function character.GetFreeInventoryIndex() As Integer Dim As Integer ret = -1 'Ишем пустой слот. For i As Integer = Lbound(_cinfo.cinv) To Ubound(_cinfo.cinv) 'Прверяем идентификатор класса. If _cinfo.cinv(i).classid = clNone Then 'Пустой слот. ret = i Exit For Endif Next Return ret End Function
Эта функция перебирает массив предметов инвентаря и ищет clNone
записанное в полн ClassID предмета, что говорит о том, что данная ячейка
инвентаря пуста. Если функция находит свободный слот, то она возвращает его
индекс, или -1, если все слоты заняты. Как только мы получили индекс свободного
слота в инвентаре, мы можем добавить в него предмет, используя процедуру
AddInvItem.
character.bi
'Добавляет предмет в слот инвентаря персонажа. Sub character.AddInvItem(idx As Integer, inv As invtype) 'Проверим правильность индекса массива. If idx >= Lbound(_cinfo.cinv) And idx <= Ubound(_cinfo.cinv) Then 'Очистим слот инвентаря. ClearInv _cinfo.cinv(idx) 'Добавим предмет. _cinfo.cinv(idx) = inv End If End Sub
Мы проверяем, чтобы убедиться, что индекс валиден (так как данная
процедура может быть вызвана из других мест программы), очистим текущий слот
инвентаря (опять же, в случае, если процедура вызывается из другого места) и
установим в слот инвентаря предмет, который игрок взял с карты. Чтобы сообщить
игроку о том, что предмет успешно подобран, мы добавляем вывод сообщения об этом
в основной код обработки команды «Поднять». Теперь, когда предмет находится в
инвентаре персонажа, нам нужно иметь возможность его отобразить, а также другие
предметы, которые персонаж подобрал ранее. Это подводит нас к экрану инвентаря
персонажа.
dod.bas
'Нарисуем экран инвентаря. If ckey = "i" Then ManageInventory 'Необходимо перерисовать фон. DrawBackground mainback() DrawMainScreen Endif
Когда игрок наживает клавишу «i», мы вызываем подпрограмму
ManageInventory, которая обрабатывает команды инвентаря. Как только игрок
выходит из инвентаря, основной экран перерисовывается. Итак, все действия над
инвентарем персонажа происходят в ManageInventory.
dod.bas
'Управление инвентарем персонажа. Sub ManageInventory() Dim As String kch, ich Dim As Integer ret DrawInventoryScreen Do kch = Inkey kch = Ucase(kch) 'Проверим, нажата ли какая либо клавиша. If kch <> "" Then 'Обработка команды опознания предмета. If kch = "E" Then ret = ProcessEval() 'Экран изменен. If ret = TRUE Then DrawInventoryScreen Endif Endif Endif Sleep 1 Loop Until kch = key_esc ClearKeys End Sub
Вначале мы рисуем экран инвентаря персонажа, на котором и будут
обрабатываться все команды работы с инвентарем. На данный момент активна только
одна команда «оценить», которая вызывается при нажатии клавиши «e». После того,
как игрок сделал все что ему нужно было сделать с инвентарем, мы возвращаем
управление основной программе. Функцию ProcessEval() мы рассмотрим немного
позже, а пока, давайте посмотрим на процедуру DrawInventoryScreen.
dod.bas
'Выводит на экран инвентарь персонажа. Sub DrawInventoryScreen() Dim As Integer col, row, iitem, ret, srow, ssrow, cnt, i Dim As String txt, txt2, desc Dim As invtype inv Dim As Uinteger clr Screenlock 'Установим фон для экрана инвентаря. DrawBackground leatherback() 'Добавим заголовок. txt = "Current Inventory for " & Trim(pchar.CharName) col = CenterX(txt) row = 1 'Выведем заголовок с тенью. PutTextShadow txt, row, col, fbYellowBright 'Добавми текущую экипировку. col = 2 row += 3 txt = "1 Primary: " PutTextShadow txt, row, col, fbWhite col = txcols / 2 txt = "4 Neck: " PutTextShadow txt, row, col, fbWhite col = 2 row += 2 txt = "2 Secondary: " PutTextShadow txt, row, col, fbWhite col = txcols / 2 txt = "5 Ring Right: " PutTextShadow txt, row, col, fbWhite col = 2 row += 2 txt = "3 Armor: " PutTextShadow txt, row, col, fbWhite col = txcols / 2 txt = "6 Ring Left: " PutTextShadow txt, row, col, fbWhite 'Разделительная черта. row += 2 col = 1 txt = String(80, Chr(205)) Mid(txt, 2) = " Equipment (*) = Not Evaluated " txt2 = " Gold: " & pchar.CurrGold & " " Mid(txt, 80 - Len(txt2)) = txt2 PutTextShadow txt, row, col, fbYellowBright row += 2 col = 2 srow = row 'Выведем предметы в инвентаре. For i = pchar.LowInv To pchar.HighInv 'Проверим, есть ли предмет в слоте. iitem = pchar.HasInvItem(i) If iitem = TRUE Then 'Получим предмет из слота. pchar.GetInventoryItem i, inv 'Проверим, опознан или нет. ret = IsEval(inv) 'Получим описание предмета. desc = GetInvItemDesc(inv) 'Получим цвет предмета. clr = inv.iconclr 'Создадим строку с описанием. txt = Chr(i) & " " & desc & " " 'Если предмет не опознан, то отметить его, чтобы игрок знал, что данный предмет нужно опознать. If ret = FALSE Then txt &= "(*)" Endif Else txt = Chr(i) clr = fbWhite Endif 'Перейдем на другой столбец, если достигли середины списка инвентаря. cnt += 1 If cnt = 14 Then col = txcols / 2 'Сохраним номер последней строки, чтобы потом наисовать разделитель. ssrow = row row = srow Endif 'Выведем текст. PutTextShadow txt, row, col, clr row += 2 Next 'Выведем разделительную линию. row = ssrow + 1 col = 1 txt = String(80, Chr(205)) Mid(txt, 2) = " Spells Learned " PutTextShadow txt, row, col, fbYellowBright 'Нарисуем слоты заклинаний. col = 2 row += 2 cnt = 0 srow = row For i = 65 To 78 txt = Chr(i) 'Перейдем на следующую колонку если достигли середины заклинаний. cnt += 1 If cnt = 8 Then col = txcols / 2 'Сохраним номер последней строки. Чтобы знать, откуда продолжить. ssrow = row row = srow Endif 'Выведем текст. PutTextShadow txt, row, col, clr row += 2 Next 'Разделитель. row = ssrow + 1 col = 1 txt = String(80, Chr(205)) Mid(txt, 2) = " Commands " PutTextShadow txt, row, col, fbYellowBright 'Выведем список команд row += 2 txt = "(D)rop - (E)val - (W)ield/Wear - (R)ead - (E)at/Drink - (I)spect" col = CenterX(txt) PutTextShadow txt, row, col, fbWhite Screenunlock End Sub
Как вы видите, процедура аналогична процедуре вывода главного экрана.
Экран разделен на 3 области. В верхней части отображаются предметы одеты на
персонаж, и те, что он держит в руках. Средняя часть, это инвентарь персонажа
(представьте себе рюкзак, и вещи лежащие в нем), и, наконец, нижняя секция
отображает заклинания, которые выучил персонаж читая книги заклинаний.
Единственный раздел, который содержит какую либо информацию на данном этапе
разработки, это рюкзак, отображающий предметы из инвентаря персонажа.
dod.bas:DrawInventoryScreen
'Проверим, есть ли предмет в слоте. iitem = pchar.HasInvItem(i) If iitem = TRUE Then 'Получим предмет из слота. pchar.GetInventoryItem i, inv 'Проверим, опознан или нет. ret = IsEval(inv) 'Получим описание предмета. desc = GetInvItemDesc(inv) 'Получим цвет предмета. clr = inv.iconclr 'Создадим строку с описанием. txt = Chr(i) & " " & desc & " " 'Если предмет не опознан, то отметить его, чтобы игрок знал, что данный предмет нужно опознать. If ret = FALSE Then txt &= "(*)" Endif Else txt = Chr(i) clr = fbWhite Endif
При заполнения экрана, мы проверяем, есть ли в слоте инвентаря предмет,
используя функцию персонажа HasInvItem, и, если она возвращает «истина»,
получаем предмет из данного слота инвентаря при помощи функции GetInventoryItem.
Далее, необходимо проверить, опознан ли данный предмет, используя функцию
IsEval, так как неопознанные предметы мы будем отмечать при их отображении, а
также получаем описание предмета при помощи функции GetInvItemDesc. Цвет,
используемый для вывода описания предмета, соответствует цвету символа предмета
при отображении на карте. Это улучшит у игрока связь между предметом и его
описанием. Мы рисуем предмет на экране в соответствующем слоте инвентаря. Если
слот пуст, то будет отображен только номер слота, в нашем случае, это одна из
букв, от 'a' до 'z'. Давайте пройдемся по каждой из функций, которые мы здесь
называли.
character.bi
'Возвращает «истина», если в слоте инвентаря есть предмет. Function character.HasInvItem(idx As Integer) As Integer 'Проверим на правильность индекса. If idx >= Lbound(_cinfo.cinv) And idx <= Ubound(_cinfo.cinv) Then 'Проверим идентификатор класса предмета. If _cinfo.cinv(idx).classid = clNone Then Return FALSE Else Return TRUE Endif Else Return FALSE End If End Function
Здесь необходимо небольшое пояснение. Мы проверяем, равен ли
идентификатор класса предмета clNone и если это верно, то возвращаем «ложь»,
иначе «истина».
inv.bi
'Возвращает «истина», если предмет опознан. Function IsEval(inv As invtype) As Integer Dim As Integer ret 'Если нечего помечать, как неопознанное. If inv.classid = clNone Then ret = TRUE Else 'Проверим предметы различного типа. Select Case inv.classid 'Золото ненужно распознавать. Case clGold ret = TRUE Case clSupplies 'Вернуть значение поля eval. ret = inv.supply.eval End Select Endif Return ret End Function
Функуия IsEval просто возвращает значение флага eval предмета из
инвентаря. Так как сейчас у нас есть только предметы класса Supply, то мы
проверяем только их. Позже мы будем расширять данную функцию.
character.bi
'Получить предмет из слота инвентаря. Sub character.GetInventoryItem(idx As Integer, inv As invtype) 'Очистим переменную для предмета. ClearInv inv 'Проверим правильность индекса. If idx >= Lbound(_cinfo.cinv) And idx <= Ubound(_cinfo.cinv) Then inv = _cinfo.cinv(idx) End If End Sub
Мы уже видели это ранее. Мы проверяем индекс, чтобы убедится что он лежит
в нужном диапазоне, и возвращаем предмет, находящийся в слоте по этому индексу.
Мы не проверяем — свободный слот или нет, чтобы использовать данную функцию как
для получения значений из пустых слотов инвентаря (как мы делали это в
предыдущей главе), так и содержащих предметы, как мы делаем здесь.
inv.bi
'Возвращает описание предмета, находящегося по координатам x,y. Function GetInvItemDesc(inv As invtype) As String Dim As String ret = "None" 'Если идентификатор класса предмета clNone, то ничего не делаем. If inv.classid <> clNone Then 'Возбмем описание для золота. If inv.classid = clGold Then ret = inv.desc Endif Endif 'Описание для «ады». If inv.classid = clSupplies Then 'Если не опознано, то вернуть основное описание. If inv.supply.eval = FALSE Then ret = inv.desc Else 'Вернем секретное описание. ret = inv.supply.sdesc Endif Endif Return ret End Function
Опять же, тут можно наблюдать уже знакомый вам код. Если элемент был
опознан, то выводим тайное описание, в противном случае — основное. Для того
чтобы освежить вашу память, напомню: магические предметы будут иметь секретное
описание, описывающее их магические свойства, для не магических предметов
секретное и основное описания совпадают.
После того, как все данные инвентаря будут отображены на экране, игрок может производить манипуляции с предметами нажимая клавиши соответствующие командам, которые перечислены в низу экрана. Единственная команда, которую мы реализовали на данный момент, это команда опознания предмета.
dod.bas
'Команда распознания предмета. Function ProcessEval() As Integer Dim As String res, mask, desc Dim As Integer i, iret, iitem, evaldr, pint, rollp, rolle, ret = FALSE Dim As invtype inv Dim As tWidgets.btnID btn Dim As tWidgets.tInputbox ib 'Убедимся, что у нас есть что распознавать. For i = pchar.LowInv To pchar.HighInv iitem = pchar.HasInvItem(i) If iitem = TRUE Then 'получим предмет инвентаря. pchar.GetInventoryItem i, inv 'распознан ли он. iret = IsEval(inv) 'необходимо распознать. If iret = FALSE Then 'Построим маску слотов. mask &= Chr(i) End If Endif Next If Len(mask) = 0 Then ShowMsg "Evaluate", "Nothing to evaluate.", tWidgets.MsgBoxType.gmbOK Else 'Написовать строку ввода на экране. ib.Title = "Evaluate" ib.Prompt = "Select item(s) to evaluate (" & mask & ")" ib.Row = 39 ib.EditMask = mask ib.MaxLen = Len(mask) ib.InputLen = Len(mask) btn = ib.Inputbox(res) 'Распознаем каждый жлемент в списке. If (btn <> tWidgets.btnID.gbnCancel) And (Len(res) > 0) Then 'Список на распознание. For i = 1 To Len(res)о iitem = Asc(res, i) 'Получим индекс слота в инвентаре персонажа. 'Получим предмет из инвентаря pchar.GetInventoryItem iitem, inv 'Получим сложность распознавания. evalDR = GetEvalDR(inv) 'Используя интеллект получим возможность распознавания. pint = pchar.CurrInt + pchar.BonInt 'Случайное число для сложности распознавания. rolle = RandomRange(0, evalDR) 'Случайное сичло для возможности распознавания игроком. rollp = RandomRange(0, pint) 'Получим описание предмета. desc = GetInvItemDesc(inv) 'Если случайное число у игрока > числу необходимому для распознания - распознаем If rollp > rolle Then desc &= " was succesfully evaluated." ShowMsg "Evaluate", desc, tWidgets.MsgBoxType.gmbOK SetInvEval inv, TRUE 'Set eval to true. pchar.AddInvItem iitem, inv 'Put item back into inv. ret = TRUE 'Flag caller that screen has changed. Else desc &= " was not evaluated." ShowMsg "Evaluate", desc, tWidgets.MsgBoxType.gmbOK Endif Next Endif Endif Return ret End Function
Во первых, вы заметите некоторые определения из пространства имен
tWidgets. Они содержатся в файле tWidgets.bi. Twidgets содержит в себе набор
виджетов, которые реализуют GUI (Графические Интерфейс Пользователя) в текстовом
видео режиме. Я не буду обсуждать здесь виджеты, но они полностью описаны в
разделе приложений.
Первое, что мы должны сделать, это проверить — есть ли предметы, которые должны быть распознаны. Список не распознанных предметов создается в переменной маски, для показа игроку, если же таких предметов нет, то отображается соответствующее сообщение.
Как только у нас есть список предметов, игрок выбирает предмет, который хочет
распознать. Процесс распознавания выглядит следующим образом:
Получить evalDR
(рейтинг сложности) предмета для распознания: evalDR = GetEvalDR
(INV).
Получить значение интеллекта персонажа вместе с бонусом: pint =
pchar.CurrInt + pchar.BonInt
Получить случайное число от 0 до evalDR: rolle =
RandomRange(0, evalDR)
Получить случайное число от 0 до полученного
интеллекта персонажа с бонусом: rollp = RandomRange(0, pint)
Если число,
выпавшее персонажу от его интеллекта больше числа, выпавшего от рейтинга
сложности, распознавание прошло успешно. If rollp > rolle
Если
распознавание успешно, то выставим у предмета поле eval в состояние «истина» и
информируем об этом игрока. SetInvEval inv, TRUE
Если распознавание прошло не
удачно, то сообщаем об этом игроку.
Мы получаем Рейтинг сложности используя функцию GetEvalDR
inv.bi
'Возвразает рейтинг сложности распознания предмета. Function GetEvalDR(inv As invtype) As Integer Dim As Integer ret 'Если нечего распознавать, то вернем 0. If inv.classid = clNone Then ret = 0 Else 'Выберем тип предмета. Select Case inv.classid Case clGold 'Золото не нужно распознавать . ret = 0 Case clSupplies ret = inv.supply.evaldr 'Вернем значение evaldr для clSupplies. End Select Endif Return ret End Function
Мы просто возвращаем значение поля evaldr если есть предмет, требующий
распознавания, иначе возвращаем 0. После того, как предмет распознан, нам нужно
изменить у него значение поля evaldr, для чего мы используем процедуру
SetInvEval.
inv.bi
'Устанавливает значение eval, для предмета inv. Sub SetInvEval(inv As invtype, state As Integer) 'проверим, есть ли предмет If inv.classid <> clNone Then 'Выберем предмет. Select Case inv.classid Case clSupplies inv.supply.eval = state 'Установим значение eval. End Select Endif End Sub
Здесь мы просто устанавливаем значение поля eval в то, которое передали в
процедуру. Это дает нам возможность установить значение в «не распознано», если
мы захотим реализовать эту функциональность. Мы могли бы это сделать, как
результат заклинания монстра или воздействия чего либо еще. Мы пока еще не
решили, нужно ли нам это, но лучше оставить свободу выбора, если позже данный
функционал нам понадобиться.
Функция распознавания возвращает «истина» в ManageInventory, для того, чтобы мы могли перерисовать экран для отображения обновленного состояния предметов. Это даст игроку все время видеть в инвентаре текущее состояние всех предметов.
dod.bas
'Управление инвентарем персонажа. Sub ManageInventory() Dim As String kch, ich Dim As Integer ret DrawInventoryScreen Do kch = Inkey kch = Ucase(kch) 'Проверим, была ли нажата клавиша. If kch <> "" Then 'Команда распознавания предметов. If kch = "E" Then ret = ProcessEval() 'Экран изменен. If ret = TRUE Then DrawInventoryScreen Endif Endif Endif Sleep 1 Loop Until kch = key_esc ClearKeys End Sub
Есть и другие команды для инвентаря, которые мы пока не реализовали, так
как у нас пока ограниченный набор предметов. Например команда «Взять в
руки/Одеть» относится только к оружию, броне, ожерельям и кольцам. Команда
«Прочитать» действительна только для свитков и книг с заклинаниями, которых у
нас также нет на данный момент. Тем не менее, команды «Выбросить»,
«Съесть/Выпить» и «Осмотреть» мы можем реализовать уже с теми предметами,
которые у нас есть на данный момент. Этим мы и займемся в следующей главе.
Перевод на русский: Fantik
содержание | назад | вперед