суббота, 3 марта 2012 г.

Давайте сделаем рогалик. Глава 10: Главный интерфейс

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

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

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

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

Комментариев нет:

Отправить комментарий