Давайте сделаем рогалик (Главный интерфейс)

maindisp.png

Здесь вы видите главный экран игры. Идея в том, чтобы на главном экране отображалась вся основная информация. У нас не так много параметров персонажа и основных команд, т. к. наша игра достаточно простая, поэтому мы можем отобразить на основном экране как параметры, так и подсказку по клавишам команд. Однако учтите, что это просто набросок, и, в результате дальнейшей нашей разработки, данный экран может быть изменен.

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

В нашем предыдущем коде карта уровня начинала выводится с координаты 1, но для того, чтобы у нас была окантовка вокруг карты, нам необходимо добавить смещение на 1 в процедуру ее отображения DrawMap. Вот пример изменений:

DrawMap:map.bi

PutText mtile, y + 1, x + 1, tilecolor


Все процедуры вывода, включая вывод значка персонажа, смешаются по горизонтали и вертикали на единицу. Визуально, отображение локации у нас сместилось вниз и вправо на 1 символ, что даст зазор для окантовки.

Большинство действий по отображению главного экрана у нас будет происходить в подпрограмме DrawMainDisplay, описанной в файле dod.bas

dod.bas

'Отображает главный экран игры.
Sub DrawMainScreen ()
    Dim As Integer x, y, j, pct, row, col
    Dim As Double pctd
    Dim As Uinteger clr
    Dim As String txt
    Dim As terrainids terr

    Screenlock
    level.DrawMap
    'Нарисуем область сообщений.
    PrintMessage ""
    'Нарисуем область для вывода информации.
    y = 2
    For j = 0 To vh - 1
        For x = vw + 3 To txcols - 1
            PutText acBlock, y + j, x, fbBlack
        Next
    Next
    'Номер уровня.
    DrawStringShadow charw, 0, "Dungeon Level: " & level.LevelID
    'Имя персонажа.
    row = 3
    col = vw + 4
    PutText pchar.CharName, row, col, fbYellowBright
    row += 2
    'Нарисуем полоску здоровья персонажа.
    pctd = pchar.CurrHP / pchar.MaxHP
    pct = Int(pctd * 100)
    If pct > 74 Then
        clr = fbGreen
    Elseif (pct > 24) And (pct < 75) Then
        clr = fbYellow
    Else
        clr = fbRed
    Endif
    'Построение полосы здоровья.
    txt = String((txcols - 1) - (col + 4), acBlock)
    PutText "    " & txt, row, col, fbBlack
    txt = String((txcols - 1) - (col + 4), Chr(176))
    PutText "    " & txt, row, col, fbGray
    txt = String(Len(txt) * pctd, acBlock)
    PutText "    " & txt, row, col, clr
    PutText "HP: ", row, col
    'Выведем значение здоровья персонажа.
    txt = pchar.CurrHP & "/" & pchar.MaxHP
    DrawStringShadow (txcols - (Len(txt) + 2)) * charw, (row - 1) * charh, txt
    'Выведем основные характеристики.
    row += 2
    PutText "Stats", row, col, fbYellowBright
    row += 2
    If pchar.BonStr > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "Str: " & pchar.CurrStr & txt & pchar.BonStr, row, col
    row += 1
    If pchar.BonSta > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "Sta: " & pchar.CurrSta & txt & pchar.BonSta, row, col
    row += 1
    If pchar.BonDex > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "Dex: " & pchar.CurrDex & txt & pchar.BonDex, row, col
    row += 1
    If pchar.BonAgl > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "Agl: " & pchar.CurrAgl & txt & pchar.BonAgl, row, col
    row += 1
    If pchar.BonInt > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "Int: " & pchar.CurrInt & txt & pchar.BonInt, row, col
    row += 1
    PutText "Curr XP: " & pchar.CurrXP, row, col

    row += 2
    PutText "Combat Factors", row, col, fbYellowBright
    row += 2
    If pchar.BonUcf > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "UCF: " & pchar.CurrUcf & txt & pchar.BonUcf, row, col
    row += 1
    If pchar.BonAcf > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "ACF: " & pchar.CurrAcf & txt & pchar.BonAcf, row, col
    row += 1
    If pchar.BonPcf > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "PCF: " & pchar.CurrPcf & txt & pchar.BonPcf, row, col
    row += 1
    If pchar.BonMcf > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "MCF: " & pchar.CurrMcf & txt & pchar.BonMcf, row, col
    row += 1
    If pchar.BonCdf > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "CDF: " & pchar.CurrCdf & txt & pchar.BonCdf, row, col
    row += 1
    If pchar.BonMdf > -1 Then
        txt = " +"
    Else
        txt = " -"
    Endif
    PutText "MDF: " & pchar.CurrMdf & txt & pchar.BonMdf, row, col
    row += 2
    PutText "Equipment", row, col, fbYellowBright
    row += 2
    PutText "Gold: " & pchar.CurrGold, row, col
    row += 1
    PutText "RT Hand: ", row, col
    row += 1
    PutText "LT Hand: ", row, col
    row += 1
    PutText "Armor: ", row, col
    row += 2
    PutText "Keys", row, col, fbYellowBright
    row += 2
    PutText "Move:.Arrows or Numpad", row, col
    row += 1
    PutText "?.....Help", row, col
    row += 1
    PutText "v.....Inventory", row, col
    row += 1
    PutText "x.....Improve Character", row, col
    row += 1
    PutText "l.....Spell Book", row, col
    row += 1
    PutText ">.....Down Level", row, col
    row += 1
    PutText "<.....Up Level", row, col
    row += 1
    PutText "i.....Inspect Tile", row, col
    row += 1
    PutText "s.....Search Area", row, col
    row += 1
    PutText "t.....Target Enemy", row, col
    row += 1
    PutText "r.....Read Scroll", row, col
    row += 1
    PutText "c.....Cast Spell", row, col
    row += 1
    PutText "e.....Drink/Eat Item", row, col
    row += 1
    PutText "g.....Get Item", row, col
    row += 1
    PutText "d.....Drop Item", row, col
    row += 1
    PutText "p.....Pick Lock", row, col
    row += 1
    PutText "b.....Bash Door", row, col
    'Проверим, стоит ли персонаж на предмете.
    If level.HasItem(pchar.Locx, pchar.Locy) = TRUE Then
        'Получим описание предмета.
    Else
        'Стоит ли персонаж на специфической местности.
        terr = level.GetTileID(pchar.Locx, pchar.Locy)
        If (terr = tstairup) OrElse (terr = tstairdn) Then
            txt = level.GetTerrainDescription(pchar.Locx, pchar.Locy)
            PrintMessage txt
        End If
    Endif
    Screenunlock
End Sub


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

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

character.bi

Declare Property CurrHP(hp As Integer)   'Задает текущее здоровье.
Declare Property CurrHP() As Integer     'Возвращает текущее здоровье.
Declare Property MaxHP() As Integer      'Возвращает максимальное здоровье.
Declare Property CurrStr() As Integer    'Возвращает текущую силу.
Declare Property CurrStr(amt As Integer) 'Задает текущую силу.
Declare Property BonStr() As Integer     'Возвращает бонус силы.
Declare Property BonStr(amt As Integer)  'Задает бону силы.
Declare Property CurrSta() As Integer    'Возвращает текущую выносливость.
Declare Property CurrSta(amt As Integer) 'Задает ткущую выносливость.
Declare Property BonSta() As Integer     'Возвращает бонус выносливости.
Declare Property BonSta(amt As Integer)  'Задает бонус выносливости.
Declare Property CurrDex() As Integer    'Возвращает текущую ловкость.
Declare Property CurrDex(amt As Integer) 'Задает текущую ловкость.
Declare Property BonDex() As Integer     'Возвращает бонус ловкости.
Declare Property BonDex(amt As Integer)  'Задает бонус ловкости.
Declare Property CurrAgl() As Integer    'Возвращает текущую подвижность.
Declare Property CurrAgl(amt As Integer) 'Задает текущую подвижность.
Declare Property BonAgl() As Integer     'Возвращает бонус подвижности.
Declare Property BonAgl(amt As Integer)  'Задает бонус подвижности.
Declare Property CurrInt() As Integer    'Возвращает текущий интеллект.
Declare Property CurrInt(amt As Integer) 'Задает текущий интеллект.
Declare Property BonInt() As Integer     'Возвращает бонус интеллекта.
Declare Property BonInt(amt As Integer)  'Задает бонус интеллекта.
Declare Property CurrUcf() As Integer    'Возвращает значение безоружного боя.
Declare Property BonUcf() As Integer     'Задает значение безоружного боя.
Declare Property BonUcf(amt As Integer)  'Задает бонус безоружного боя.
Declare Property CurrAcf() As Integer    'Возвращает значение боя с оружием.
Declare Property BonAcf() As Integer     'Возвращает бонус боя с оружием.
Declare Property BonAcf(amt As Integer)  'Задает бонус боя с оружием.
Declare Property CurrPcf() As Integer    'Возвращает значение использования дистанционного оружия.
Declare Property BonPcf() As Integer     'Возвращает бонус для дистанционного оружия.
Declare Property BonPcf(amt As Integer)  'Задает бонус для дистанционного оружия.
Declare Property CurrMcf() As Integer    'Возвращает силу магических атак.
Declare Property BonMcf() As Integer     'Возвращает бонус магических атак.
Declare Property BonMcf(amt As Integer)  'Задает бонус магических атак.
Declare Property CurrCdf() As Integer    'Возвращает текущую защиту персонажа.
Declare Property BonCdf() As Integer     'Возвращает бонус защиты персонажа.
Declare Property BonCdf(amt As Integer)  'Задает бонус защиты персонажа.
Declare Property CurrMdf() As Integer    'Возвращает магическую защиту персонажа.
Declare Property BonMdf() As Integer     'Возвращает бонус магической зашиты.
Declare Property BonMdf(amt As Integer)  'Задает бонус магической зашиты.
Declare Property CurrXP() As Integer     'Возвращает текущее значение опыта.
Declare Property CurrXP(amt As Integer)  'Задает текущее значение опыта.
Declare Property TotXP() As Integer      'Возвращает общее кол-во опыта.
Declare Property TotXP(amt As Integer)   'Задает общее коли-во опыта.
Declare Property CurrGold() As Integer     'Возвращает текущее золото.
Declare Property CurrGold(amt As Integer)  'Задает текущее золото.
Declare Property TotGold() As Integer     'Возвращает общее кол-во золота.
Declare Property TotGold(amt As Integer)  'Задает общее кол-во золота.


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

character.bi

'Возвращает текущее значение силы персонажа.
Property character.CurrStr() As Integer
 Return _cinfo.stratt(0)
End Property

'Задает текущее значение силы персонажа.
Property character.CurrStr(amt As Integer)
_cinfo.stratt(0) = amt
End Property


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

map.bi

'Возвращает описание местности.
Function levelobj._GetTerrainDesc(x As Integer, y As Integer) As String
    Dim tile As terrainids
    Dim ret As String

    'Must be a tile.
    tile = _level.lmap(x, y).terrid
    Select Case tile
        Case twall
            ret = "Wall"
        Case tfloor
            ret = "Floor"
        Case tstairup
            ret = "Stairs up"
        Case tstairdn
            ret = "Stairs down"
        Case tdooropen
            ret = "Open door"
        Case tdoorclosed
            ret = "Closed door"
        Case Else
            ret = "Uknown"
    End Select

    Return ret
End Function

'Возвращает описание предмета, находящегося по координатам x,y.
Function levelobj.GetTerrainDescription(x As Integer, y As Integer) As String
    Return _GetTerrainDesc(x, y)
End Function

'Возвразает истина, если по координатам x,y находится какой либо предмет.
Function levelobj.HasItem(x As Integer, y As Integer) As Integer
    Return _level.lmap(x, y).hasitem
End Function


Публичная функция GetTerrainDescription вызывает приватную функцию GetTerrainDesc, которая, в свою очередь, вернет описание типа местности по координатам x и y. Функция HasItem возвращает «истина», если на карте по координатам x, y лежит какой либо предмет. Оба этих метода будут использоваться в представленном ниже коде процедуры DrawMainDisplay.

dod.bas

'Проверим, стоит ли персонаж на предмете.
If level.HasItem(pchar.Locx, pchar.Locy) = TRUE Then
    'Получим описание предмета.
Else
    'Стоит ли персонаж на специфической местности.
    terr = level.GetTileID(pchar.Locx, pchar.Locy)
    If (terr = tstairup) OrElse (terr = tstairdn) Then
        txt = level.GetTerrainDescription(pchar.Locx, pchar.Locy)
        PrintMessage txt
    End If
Endif


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

dod.bas

'Выводит на экран любые сообщения.
Sub PrintMessage(txt As String)
    Dim As Integer i, x, y

    If Len(txt) > 0 Then
        'Сдвинуть все сообщения на одну позицию вниз.
        For i = 3 To 1 Step -1
            mess(i + 1) = mess(i)
        Next
        mess(1) = txt
    End If
    'Очищаем регион для сообщений.
    ClearMessageArea
    'Выведем сообщения на экран.
    y =  1 + vh + 2
    x = 3
    For i = 1 To 4
        PutText mess(i), y, x, messcolor(i)
        y += 1
    Next
End Sub


Мы используем два массива, один для хранения сообщений, и второй, для хранения цвета каждой линии.

defs.bi

'Список сообщений.
Dim Shared mess(1 To 4) As String
Dim Shared messcolor(1 To 4) As Uinteger = {fbWhite, fbWhite1, fbWhite2, fbWhite3}


Мы хотим, чтобы последнее сообщение отображалось белым цветом, а а все предыдущие — все более и более темным серым. Это концентрирует внимание пользователя на последнем сообщении.

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

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

dod.bas

'Главный цикл игры.
If mm <> mmenu.mQuit Then
'Установим первый уровнь.
level.LevelID = 1
'Построим подземелье первого уровня
level.GenerateDungeonLevel
'Установим фон для главного экрана.
DrawBackground mainback()
'Display the main screen.
DrawMainScreen
Do
...


Рисовать фон нужно только один раз, т. к. мы обновляем только области отображения карты, сообщений и информации. Это сэкономит нам процессорное время, т. к. дисплей обновляется после каждого действия персонажа.

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

Перевод на русский: Fantik

содержание | назад | вперед