вторник, 2 сентября 2014 г.

Давайте сделаем рогалик. Глава 35: Допиливаем игру 2

Существует одна вещь, которую мы обязательно должны сделать, это убедиться что при нажатии на клавишу Escape игрок действительно хочет выйти из игры. Нет способа расстроить игрока легче, чем закрыть нашу игру, когда игрок случайно, в пылу сражения, нажмет клавишу Escape. Мы можем избежать этого путем простой замены.

dod.bas
'Проверим нажатие клавиши Escape.
  If ckey = key_esc Then
    Dim As tWidgets.tMsgbox conf
    Dim As tWidgets.btnID btn
            
    'Спросим игрока, действительно ли он хочет выйти из игры.
    conf.MessageStyle = tWidgets.MsgBoxType.gmbYesNo
    conf.Title = "Confirm Exit"
    btn = conf.MessageBox("Do you wish to quit?")
     If btn = tWidgets.btnID.gbnYes Then
       done = TRUE
     EndIf
  EndIf

Здесь мы просто выводим окно сообщения с вопросом о подтверждении выхода из игры, и даем возможность игроку подтвердить выход, выбрав «Yes» или отменить «No». Если игрок подтвердит свое желаний выйти то мы устанавливаем флаг выхода, иначе, просто продолжим главный цикл. Это позволит предотвратить случайны выходы из из игры испортив игроку настроение. Мы также будем использовать этот код, для того чтобы сохранить игру при выходе, так что, добавив его, мы сразу убьем двух зайцев.

Наш следующий апгрейд не только косметический, но и позволит немного упростить код работы с инвентарем, что даст нам некоторые варианты для более поздних усовершенствований. Сейчас, когда персонаж берет в руки двуручное оружие, в одном слоте экипировки будет содержаться название этого оружия, а второй будет отображаться как пустой, хотя и будет не доступен. Это может ввести в заблуждение новых игроков, поэтому, было бы неплохо, отметить этот слот как недоступный. Мы не только можем это сделать, но, заодно, и упростим немного наш код.

Мы начнем с добавления нового класса предмета инвентаря — clUnavailable.

inv.bi
'Идентификаторы классов предметов.
  Enum classids
    clNone
    clGold
    clSupplies 
    clArmor
    clShield
    clWeapon
    clAmmo
    clPotion
    clRing
    clNecklace
    clSpellBook
    clSpell
    clUnavailable
  End Enum

Как вы можете догадаться, новый класс предмета будет вести себя точно также, как и остальные предметы инвентаря. В результате нам не придется делать дополнительных проверок на невозможность использования второго слота, все будет обрабатываться обычными функция для работы с инвентарем, что сделает наш код более прямолинейным и надежным. Так как мы добавили новый объект инвентаря, нам нужна для него программа генерации.

inv.bi
'Генерирует «недоступный» инвентарный обхект.
  Sub GenerateUnavail (inv As invtype)
    ClearInv inv
    inv.classid = clUnavailable
    inv.desc = "Unavailable"
    inv.iconclr = fbWhite
  End Sub

На самом деле не очень много параметров. Все что нам нужно, это указать идентификатор типа предмета, его описание и цвет значка, необходимые для отображения этого предмета на главном экране и экране инвентаря.

Единственное место, где сейчас будет использоваться этот предмет, это во время экипировки двуручного оружия. Для этого необходимо внести небольшое изменение в метод AddInvItem объекта персонажа.

character.bi
'Добавляет предмет из инвентаря в экипировочный слот персонажа.
  Sub character.AddInvItem(idx As Integer, inv As invtype)
    Dim As invtype inv2
    
    'Проверим в допустимых ли рамках индекс.
    If idx >= LBound(_cinfo.cwield) And idx <= UBound(_cinfo.cwield) Then
      'Очистим слот инвентаря.
      ClearInv _cinfo.cwield(idx)
      'Назначим предмет в слот экипировки.
      _cinfo.cwield(idx) = inv
      'Проверим на двуручный предмет и если это так, установим во вторую руку «недоступный» предмет.
      If inv.classid = clWeapon Then
         'Если двуручное оружие, займем вторую руку.
         If inv.weapon.hands = 2 Then
            'Получим «недоступный» предмет.
            GenerateUnavail inv2
            'Поместим предмет в слот.
            If idx = wPrimary Then
               ClearInv _cinfo.cwield(wSecondary)
               _cinfo.cwield(wSecondary) = inv2
            Else
               ClearInv _cinfo.cwield(wPrimary)
               _cinfo.cwield(wPrimary) = inv2
            EndIf
         EndIf
      End If
    Else
      'Проверим индексы инвентаря.
      If idx >= LBound(_cinfo.cinv) And idx <= UBound(_cinfo.cinv) Then
         'Очистим слот инвентаря.
         ClearInv _cinfo.cinv(idx)
         'Назначим предмет в слот.
         _cinfo.cinv(idx) = inv
      End If   
    EndIf
  End Sub

Вначале мы проверяем, оружие ли это, и если это так, то смотрим — сколько рук необходимо для его использования. Если оружие двуручное, то во второй слот мы помещаем «недоступный» предмет. В результате в первом слоте у нас будет отображаться название используемого оружия, во втором: «Недоступно».

И как же это нам поможет упростить работу с инвентарем? Давайте взглянем на код функции ProcessEquip.

dod.bas
'Экипировка предметов.
  Function ProcessEquip() As Integer
    Dim As String res, mask, msg
    Dim As Integer i, iitem, iret, ret = FALSE
    Dim As invtype inv
    Dim As tWidgets.btnID btn
    Dim As tWidgets.tInputbox ib
    Dim As wieldpos slot
      
    'Получим список вещей, которые можно экипировать.
    For i = pchar.LowInv To pchar.HighInv
      iitem = pchar.HasInvItem(i)
      If iitem = TRUE Then
         'Получим предмет инвентаря.
         pchar.GetInventoryItem i, inv
         'Проверим, может ли быть экипирован.
         iret = MatchUse(inv, useWieldWear)
         'Предмет может быть экипирован.
         If iret = TRUE Then
            'Построим маску.
            mask &= Chr(i)
         End If
      EndIf
    Next
    If Len(mask) = 0 Then
      ShowMsg "Equip Items", "Nothing to equip.", tWidgets.MsgBoxType.gmbOK
    Else
      'Написуем строку для ввода.
      ib.Title = "Equip Items"
      ib.Prompt = "Select item(s) to equip (" & 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) 'Get index into inventory.
            'Получим предмет инвентаря.
            pchar.GetInventoryItem iitem, inv
            'Получим свободный слот.
            slot = pchar.GetFreeSlot(inv, msg)
            'Если есть свободный слот.
            If slot <> wNone Then
               'Поместим предмет в слот экипировки.
               pchar.AddInvItem slot, inv
               'Очистим предмет.
               ClearInv inv
               'Обновим слот инвентаря.
               pchar.AddInvItem iitem, inv
               ret = TRUE
               msg &= " was equipped."
            End If
            ShowMsg "Equip Items", msg, tWidgets.MsgBoxType.gmbOK
         Next
      EndIf
    EndIf
    
    Return ret
  End Function

Если вы помните старый код этой функции, то он был намного сложнее. Там, где сейчас у нас вызывается функция GetFreeSlot, раньше был сложный код на проверку слотов принимая во внимание необходимое количество рук и т. д. Теперь все это не нужно, функция GetFreeSlot возвращает нам слот, который мы можем использовать. Давайте взглянем на новый код для получения свободного слота.

character.bi
'Возвращает свободный слот или wNone.
  Function character.GetFreeSlot(inv As invtype, msg As String) As wieldpos
    Dim As wieldpos rslot
    Dim As String desc
    Dim As invtype inv2
    
    'Получим описание предмета.
    desc = GetInvItemDesc(inv)
    msg = desc
    'Получим слот для предмета инвентаря.
    rslot = GetInvWSlot(inv, 1)
    If rslot <> wNone Then
      'Проверим, занят ли слот.
      If HasInvItem(rslot) = TRUE Then
         rslot = wNone
      EndIf
    End If
    'Проверим второй слот.
    If rslot = wNone Then
      rslot = GetInvWSlot(inv, 2)
      If rslot <> wNone Then
         'Проверим, занят ли он.
         If HasInvItem(rslot) = TRUE Then
            rslot = wNone
         End If
      EndIf
    EndIf
    'Ищем свободный слот.
    If rslot <> wNone Then
      'Проверим, оружие ли это, и сколько рук нужно для использования.
      If inv.classid = clWeapon Then
         If inv.weapon.hands = 2 Then
            If (HasInvItem(wPrimary) = TRUE) Or (HasInvItem(wSecondary) = TRUE) Then
               msg = "Not enough free hands to equip " & desc & "."
               rslot = wNone
            EndIf
         EndIf
      ElseIf (inv.classid = clArmor) Or (inv.classid = clShield) Then
         If CanWear(inv) = FALSE Then
            msg = "Not enough strength to equip " & desc & "."
            rslot = wNone
         End If
      End If
    Else
      msg = "No empty slots to equip " & desc & "."
    EndIf
    
    Return rslot
  End Function

Эта функция возвратит свободный слот, в который можно экипировать предмет, или wNone — если свободных слотов нет. Поскольку теперь у нас при двуручном оружии во второй руку находится «недоступный» предмет, то все что нам нужно сделать, это проверить — заняты ли слоты рук функцией HasInvItem. HasInvItem возвращает False, если в слоте содержится идентификатор класса clNone. Поскольку «недоступный» предмет имеет идентификатор класса clUnavailable, то HasInvItem вернет True, как будто слот занят и, следовательно, недоступен.

Если персонаж, в настоящий момент, держит в руках двуручный меч, то при попытке экипировать, например, одноручный меч, отобразится сообщение о том, что нет свободных слотов, т. к. во вторая рука будет занята «недоступным» предметом. Мы получим то что хотели. Нам не нужно будет делать дополнительных проверок — держит ли персонаж двуручное оружие или нет, достаточно будет проверить слоты при помощи HasInvItem.

Это удобно тем, что не влияет на остальные инвентарные слоты. Функция GetInvWSlot все также вернет wPrimary или wSecondary для оружия, wNeck для ожерелья и т. д. Это работает также как и раньше, поэтому мы упростили наш код избавившись от неприятных исключений.

Введение нового «недоступного» предмета, открывает нам некоторые возможности для дальнейших усовершенствований. Например, если мы захотим ограничить объем инвентаря в соответствии с показателем силы персонажа. Все что нам нужно, это рассчитать, сколько слотов персонажу доступно в соответствии с текущей силой, и заполнить «лишние» слоты инвентаря «недоступными» предметами. По мере увеличения силы персонажа — убирать из инвентаря эти предметы, увеличивая тем самым его объем. Есть ряд других способов использования этих предметов, например для критических ударов или магических эффектов. Любое из этих усовершенствований будет намного легче реализовать, если у нас есть «недоступный» предмет инвентаря.

Этими двумя небольшими изменениями мы добились очень многого. Обновления добавляют косметические усовершенствования, упрощают наш код и открывают пути для новых усовершенствований. Мы добились того, для чего, обычно, и нужно «допиливание» - получили максимум функциональности из уже существующего кода.


1 комментарий: