Давайте сделаем рогалик (Вступление)

introscr.png

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

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

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

Весь код для введения мы поместим в intro.bi. Так как вступление нам больше нигде не понадобиться, обернем его в пространство имен intro. В нем будет описана подпрограмма DoIntro, которую мы будем вызывать сразу после создания персонажа.

dod.bas

'Обработаем выбранный пункт меню.
If mm = mmenu.mNew Then
'Сгенерируем персонажа.
Var ret = pchar.GenerateCharacter
'Не выходим из меню, если пользователь нажмет ESC.
If ret = FALSE Then
    'Установим переменную, чтобы не выйти из цикла.
    mm = mmenu.mInstruction
Else
    'Вызовем вступление.
    intro.DoIntro
Endif

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

intro.bi

Dim Shared pal(maxage) As Uinteger = { _
&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4, _
&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4, _
&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4, _
&hFFF9F7D4,&hFFF9F6B6,&hFFF8F48E,&hFFF8F364,&hFFF8F139,&hFFF9EC14,&hFFFAD51B,&hFFFCBE22, _
&hFFFDA52A,&hFFFF8C31,&hFFFA802E,&hFFF4752A,&hFFEE6A26,&hFFE95E22,&hFFE3531D,&hFFDE471A, _
&hFFD53613,&hFFCC250D,&hFFC31207,&hFFBB0100,&hFFAC0000,&hFF9D0000,&hFF8E0000,&hFF7F0000, _
&hFF700000,&hFF610000,&hFF5A0000,&hFF550000,&hFF510000,&hFF4D0000,&hFF480000,&hFF440000, _
&hFF3F0000,&hFF3B0000,&hFF370000,&hFF320000,&hFF2E0000,&hFF2A0000,&hFF250000,&hFF210000, _
&hFF1C0000,&hFF180000,&hFF130000,&hFF100000,&hFF0B0000,&hFF070000,&hFF020000,&hFF000000, _
&hFF000000,&hFF000000,&hFF000000,&hFF000000,&hFF000000,&hFF000000,&hFF000000,&hFF000000}

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

intro.bi

'Перемещение каждой частицы на экране с возможностью перемещения из стороны в сторону.
Sub MoveParticles
  Dim As Integer x, y, tage, xx
  Dim As Single r
   
  For x = 0 To txcols - 1
    For y = 1 To txrows - 1
      'Получить текущий возраст частицы.
      tage = fire(x, y) 
     'Cдвинуть частицу влево (-1) или вправо (1) или не двигать (0).
      xx = RandomRange(-1, 1) + x
      'Перенести частицу на другую сторону эерана, если вышла за границы.
      If xx < 0 Then xx = txcols - 1
      If xx > txcols - 1 Then xx = 0
      'Установить возраст частицы.
      tage += coolmap(xx, y - 1) + 1
      'Убедиться, что возраст попадает в диапазон.
      If tage < 0 Then tage = 0
      If tage > (maxage - 1) Then tage = maxage - 1
      fire(xx, y - 1) = tage
    Next
  Next

End Sub


Каждая частица в массиве огня перемещается на одну позицию вверх, и, возможно, влево или вправо, в зависимости от возвращаемого значения функцией RandomRange(-1, 1). Это пример использования нашей функции случайных значений, когда мы задаем диапазон с отрицательными и положительными числами. Когда частица перемещается вверх, ее возраст комбинируется со значением из карты охлаждения coolmap, в результате чего он увеличивается или уменьшается. Используя карту охлаждения, наподобие этой, вы можете создать несколько вариантов огня, чтобы он выглядел более реалистичным.

После того, как старые частицы переместятся вверх, новые будут добавлены в нижней части экрана.

intro.bi

'Добавить частицы огня вдоль нижнего края экрана.
Sub AddParticles
   Dim As Integer x
   
   For x= 0 To txcols - 1
       fire(x, txrows - 1) = RandomRange(0, 20)
   Next
   
End Sub


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

intro.bi

'Рисует пламя или пергамент на экране.
Sub DrawScreen(egg As Integer)
Dim As Integer x, y, cage, tx, ty, wid = 68
Dim As Uinteger clr
Dim As String st, tt

Screenlock
MoveParticles
AddParticles
For x = 0 To txcols - 1
    For y = 0 To txrows - 1
        If fire(x, y) < maxage Then
            cage = Smooth(fire(), x, y)
            cage += 10
            If cage > maxage Then cage = maxage
            'Если цвет фона равен 0, то использовать цвет огня.
            clr = introback(x + y * txcols)
            'Проверим, если рисуется пергамент.
            If clr = &hFF000000 Then clr = pal(cage)
            'Тут пасхалка.
            If egg = TRUE Then
                clr = pal(cage)
            Endif
            Draw String (x* 8, y* 8), Chr(219), clr
        End If
    Next
Next
...


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

intro.bi

'Вывод текста письма.
tx = 6 * charw
ty = 3 * charh
st = "Dear " & pchar.CharName & ","
DrawStringShadow tx, ty, st
ty += (charh * 2)
st = "My friend and ally... Dark days are upon us and the fires of evil are threatening the beloved land. "
st &= "The evil wizard Deboza has escaped from her prison, the magical crystal shard in the center of the "
st &= "Amulet of Crystal Fire. How she escaped from this prison, I do not know, but it must have required "
st &= "great magic to accomplish. She is even now gathering an army to do battle with the good folk of the "
st &= "land and it is only a matter of time before she overruns the weak defenses of the of the King. Long "
st &= "peace, though welcome, has its own dangers."
Do
    tt = WordWrap(st, wid)
    DrawStringShadow tx, ty, tt
    ty += charh + 2
Loop Until Len(tt) = 0


Мы используем уже знакомую нам процедуру DrawStringShadow и новую функцию WordWrap, которую также необходимо добавить в utils.bi. Функция WordWrap принимает текст абзаца (переменная st) и ширину, в которую нужно уместить выводимый текст (переменная wid). Функция возвращает строку такой длинны, чтобы поместилась в указанную ширину. Возвращаемая строка начинается с начала переданного ей текста и заканчивается обязательно целым словом. Также, функция удаляет возвращаемую строку из первоначального текста, поэтому мы вызываем ее в цикле, чтобы напечатать возвращаемые функцией строки на новой строке экрана. Когда текст в переменной st закончится (все строки напечатаны), программа выйдет из цикла.

intro.bi

'Запустить вступление.
Sub DoIntro()
  Dim As Single t
  Dim As String eg
  Dim As Integer doegg = FALSE
  
  CreateCoolMap
  Do
    eg = Inkey
    If eg = "f" Then
      doegg = Not doegg
      eg = ""
    Endif
    t = Timer
    'Перерисовываем экран.
    DrawScreen doegg
    Do While (Timer - t) < FD 
      Sleep 1
    Loop
  Loop Until eg <> "" 
  ClearKeys
End Sub


Первое, что мы сделаем, это создаем карту охлаждения

intro.bi

'Создает карту охлаждения, которая объединяясь с огнем даст наилучший эффект 
Sub CreateCoolMap 
  Dim As Integer i, j, x, y 
  
  For x = 0 To txcols - 1 
    For y = 0 To txrows - 1 
      coolmap(x, y) = RandomRange(-10, 10) 
    Next 
  Next 
   
  For j = 1 To 10 
    For x = 1 To txcols - 2 
      For y = 1 To txrows - 2 
        coolmap(x, y) = Smooth(coolmap(), x, y) 
      Next 
    Next 
  Next 
End Sub


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

intro.bi

Do While (Timer - t) < FD
    Sleep 1
Loop


Эта часть кода приостанавливает отображения следующего кадра на время заданное константой FD, равной 1/60, для того, чтобы анимация огня перерисовывалась с одинаковой скорость на компьютерах с различной частотой процессора (В самой первой строке процедуры DoIntro переменная t объявлена как Single, что неверно. Функция Timer возвращает Double переменную, а из-за неточности типа Single в результат выражения Timer - t может быть даже отрицательным, что вызовет неприятный эффект «подвисания» программы в цикле Do While (Timer — t) < FD, при этом, разумеется, вы не сможете даже закрыть обычным способом окно приложения. Поэтому, чтобы этого не происходило, строку «Dim As Single t» нужно заменить на «Dim As Double t» (примечание переводчика)).

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

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

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

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