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

Давайте сделаем рогалик. Глава 01. Титульный экран

Создание рогалика предполагает некоторое планирование, но есть одна вещь, которую мы можем сделать в самом начале: создать титульный экран. Это позволит нам начать проект, что иногда, бывает очень трудно. Легко застопориться на шаге «С чего же мне начать?», но кодирование экрана заставки позволит сдвинуться с мертвой точки. Делается это просто, позволит создать нам несколько файлов для будущего проекта, а также, настроит наше мышление на дальнейшее программирование.


Для экрана заставки, нам необходим, очевидно, заголовок. Так как мы создаем классический рогалик, то действия будет происходить в подземелье. Подземелье... чего? Нам нужно, чтобы название звучало опасно и зловеще. Пусть будет Подземелье Судьбы (Dungeon of DooM)! Звучит достаточно хорошо. Это и будет нашим названием.

Для простоты, у нас будет текстовый рогалик, чтобы мы могли сосредоточиться на алгоритмах, а не на графике. Но это не значит, что мы должны довольствоваться одним текстом. Вы, наверное, не помните, а, возможно, даже и не видели старые доски объявлений, или ББС-ки (Bulletin Board Station), как их еще называли, которые были еще до Интернета в дни обычных телефонных модемов. У многих ББС-ок была красивая заставка, нарисованная при помощи ANSI графики. Мы можем использовать эту технику для создания зрелищной заставки в текстовом режиме. Процесс, на самом деле, достаточно прост.

Первое, что мы должны решить - какой тип окна мы будем использовать. Мы могли бы использовать обычное консольное окно, но тогда бы пришлось ограничиться только 16-тью цветами. Никакого основания для этого нет. Для этой игра мы будем использовать графический экран в 32-х битном режиме, что позволит пользоваться полноценной RGB палитрой. Для наших целей на хватит разрешения экрана 640x480, а использование 32-х битной палитры даст нам больше гибкости для отображения данных. При разрешении 640x480 мы можем установить текстовый режим до 80 столбцов на 60 строк, которые дадут нам немало места, и используя шрифт 8x8 мы получим компактный, но хорошо читаемый дисплей.

Как вы можете заметить на картинке выше, титульный экран выглядит как точечный рисунок. На самом деле это и есть конвертированное растровое изображение. Я создал растровую картинку title.bmp (который вы найдете в директории с изображениями) в графическом редакторе, а затем преобразовал цвета в массив отображаемый на экране. Массив состоит из цвета (взятого из растрового изображения) и «графического» ASCII символа с кодом 219, который представляет из себя закрашенный квадрат. Просто и эффективно. Вот как выглядит весь процесс:

  • Создаем пустую картинку 80x60 точек — по размеру нашего экрана.
  • Создаем фон из текстуры кирпича.
  • Добавляем текст при помощи графических инструментов.
  • Добавляем монстра из набора тайлов The RLTiles (скачать этот набор можно на http://rltiles.sourceforge.net/ (примечание переводчика)).
  • Сохраняем изображение как 24-х битный битмап.

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

  • Загрузим изображение в графическом режиме.
  • Переберем все точки изображения и сохраним значение цвета в массив целых без знаковых чисел. Я просто использовал функцию Point, т.к. скорость тут не важна и это самый простой способ.
  • Сохраняем полученный массив в файл.

В результате мы должны получить файл приблизительно следующего содержимого:

'Использование: (uinteger) pixel = title(x + y * titlew)
#Define titlew 80
#Define titleh 60
Dim Shared title(4800) As UInteger = { _
&hFF6D5452,&hFF201917,&hFF211C19,&hFF443831,&hFF544438,&hFF5F534B,&hFF5A4F49,&hFF5B4E47, _
&hFF544841,&hFF554442,&hFF54433E,&hFF564945,&hFF574A44,&hFF68534A,&hFF775955,&hFF765A59, _
&hFF796257,&hFF7C6855,&hFF7E6556,&hFF7A5B5E,&hFF795D5F,&hFF75615B,&hFF6F5B56,&hFF6F5B58, _
&hFF776167,&hFF7A5D61,&hFF745750,&hFF6F534A,&hFF6A5347,&hFF5A4C35,&hFF5B4E3A,&hFF675447, _
.
.
.
&hFFC00000,&hFF200000,&hFF200000,&hFF800000,&hFF400000,&hFF600000,&hFF300000,&hFF403000, _
&hFFA07800,&hFF806000,&hFFC09000,&hFFA07800,&hFFA07800,&hFF806000,&hFF302400,&hFF604800, _
&hFF800000,&hFFA00000,&hFFFF0000,&hFF600000,&hFFFF0000,&hFFC00000,&hFF600000,&hFFE00000, _
&hFFE00000,&hFF600000,&hFFC00000,&hFF600000,&hFF300000,&hFF600000,&hFFFF0000,&hFFE00000}

Как вы видите, коды цветов записаны в шестнадцатеричном виде. Это сделано для экономии места в файле.

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

'Нарисовать игровую заставку.
Sub DisplayTitle
   Dim As String txt
   Dim As Integer tx, ty

   'Установим значения для копирайтов.
   txt = "Copyright (C) 2011, by Richard D. Clark"
   tx = (txcols / 2) - (Len(txt) / 2)
   ty = txrows - 2

   'Заблокируем экран перед выводом.
   ScreenLock
   Cls
   'Перебор значений массива, рисуем символ блока используя цвет из массива.
   For x As Integer = 0 To titlew - 1
      For y As Integer = 0 To titleh - 1
         'Получим цвет из массива, используя формулу.
         Dim clr As UInteger = title(x + y * titlew)
         'Используем draw string т.к. это быстрее и нам не нужно заботится об расположении.
         Draw String (x * charw, y * charh), acBlock, clr 
      Next
   Next
   'Напишем копирайт.
   Draw String (x * charw, y * charh), acBlock, clr
   ScreenUnLock
   Sleep
   'Очистим буфер клавиатуры.
   Do:Sleep 1:Loop While InKey <> ""
End Sub

Первое, что мы тут делаем — задаем значения для вывода информации об авторских правах. Код tx = (txcols / 2) - (Len(txt) / 2) вычисляет X координату для вывода текстовой строки, чтобы она располагалась по центру экрана. ty = txrows - 2 вычисляет Y координату, чтобы текст располагался на 2 строки выше нижнего края экрана. Мы блокируем экран при помощи ScreenLock для более быстрого вывода и исключения мерцания на слабых машинах. Для более быстрого вывода, мы могли бы получить доступ к буферу экрана на прямую, но у нас не так много строк и столбцов, особого прироста скорости это не даст, а только усложнит код.

Двумя циклами FOR-NEXT мы перебираем массив цветов и при помощи команды Draw String рисуем ASCII символ 219 заданный константой acBlock. Draw String использует пиксельные экранные координаты x и y, поэтому мы может выводить текст в любом месте экрана, а не только в определенных строках и столбцах. Но в данном случае мы хотим использовать именно строки и столбцы, поэтому мы смещаем координаты на ширину и высоту шрифта, заданного константами charw и charh.

На первый взгляд код Dim clr As UInteger = title(x + y * titlew) выглядит несколько странно, но если вы посмотрите файл title.bi, то можно увидеть, что цвета картинки у нас сохранены в одномерном массиве. Для того, чтобы преобразовать двумерные координаты к одномерным, мы используем формулу title(x + y * titlew), где x и y — массив индексов, а titlew — ширина текстового экрана. Очевидно, чтобы это работало, необходимо, чтобы данный массив был заполнен точно также. Например вы можете использовать следующий код для заполнения массива:

colorvals(x + y * imagewidth) = point(x, y)

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

Завершает подпрограмму функция разблокирования экрана ScreenUnlock, для того чтобы обновить дисплей. Затем мы ждем, пока пользователь нажмет какую либо клавишу, команда Sleep. Последним кусок кода: Do:Sleep 1:Loop While InKey <> "" мы очищаем буфер клавиатуры, т.к. В ней сейчас сохранена, как минимум, та клавиша, которую нажал пользователь для выхода из команды Sleep, которая буфер не очищает. Мы не хотим подобрать весь этот хлам клавиш когда будем обрабатывать нажатие клавиш в меню, поэтому, буфер клавиатуры необходимо очистить.

Хотя наша программа всего лишь отображает титульную заставку, в нашем проекте уже есть несколько:

  • dod.bas: Главный файл проекта.
  • defs.bi: Содержит список констант.
  • title.bi: Содержит массив со значениями цветов для заставки.
dod.bas
/'****************************************************************************
*
* Name: dod.bas
*
* Synopsis: Dungeon of Doom
*
* Description: A simple roguelike as detailed in the wikibook, Let's Build a 
*              Roguelike. This is the main program file.
*
* Copyright 2010, Richard D. Clark
*
*                          The Wide Open License (WOL)
*
* Permission to use, copy, modify, distribute and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice and this license appear in all source copies. 
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
*
*****************************************************************************'/

#Include "title.bi"
#Include "defs.bi"

'Отображает игровую заствку.
Sub DisplayTitle
   Dim As String txt
   Dim As Integer tx, ty

   'Установим значения для копирайтов.
   txt = "Copyright (C) 2010, by Richard D. Clark"
   tx = (txcols / 2) - (Len(txt) / 2)
   ty = txrows - 2

   'Заблокируем экран перед выводом.
   ScreenLock
   Cls
   'Перебор значений массива, рисуем символ блока используя цвет из массива.
   For x As Integer = 0 To titlew - 1
      For y As Integer = 0 To titleh - 1
        'Получим цвет из массива, используя формулу.
         Dim clr As UInteger = title(x + y * titlew)
         'Используем draw string т. к. это быстрее и нам не нужно заботится об расположении.
         Draw String (x * charw, y * charh), acBlock, clr 
      Next
   Next
   'Выведем информацию о копирайтах.
   Draw String (tx * charw, ty * charw), txt, fbYellow
   ScreenUnLock
   Sleep
   'Очистим буфер клавиатуры.
   Do:Sleep 1:Loop While InKey <> ""
 End Sub

'Установим разрешение экрана 640x480 32bit с текстовым режимом 80x60.
ScreenRes 640, 480, 32
Width charw, charh
WindowTitle "Dungeon of Doom"

'Нарисовать заставку.
DisplayTitle

Пока у нас не очень много действий в dod.bas. Мы подключаем файлы с заставкой и с константами при помощи директивы #Include. Когда компилятор видит директиву #Include, он останавливает компиляцию текущего файла и загружает файл, на который ссылается \ директива, а потом продолжает компиляцию. Происходит тоже самое, если бы мы вместо #Include вставили содержимое данного файла. Это позволяет разбить программу на отдельные фрагменты, а не держать весь код в одном большом файле. С точки зрения компилятора, программа все равно находится в одном файле, данное разбиение нужно только для разработчика — так легче работать с программным кодом. Затем мы описываем подпрограмму DisplayTitle для отрисовки заставки и, при помощи команды ScreenRes устанавливаем видео режим с разрешением 640x480 и глубиной цвета 32 бит. Разрешение текстового дисплея задается при помощи команды Width, а командой WindowTitle мы задаем заголовок окна нашей программы. Затем вызывается подпрограмма для отображения заставки, ждет нажатия любой клавиши и программа завершиться.

defs.bi
/'****************************************************************************
*
* Name: defs.bi
*
* Synopsis: Data definitions for DOD.
*
* Description: This file contains the various data definitions used in the game.  
*
* Copyright 2010, Richard D. Clark
*
*                          The Wide Open License (WOL)
*
* Permission to use, copy, modify, distribute and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice and this license appear in all source copies. 
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
*
*****************************************************************************'/

'Используем символы 8x8.
#Define charw 8
#Define charh 8

'Текстовый режим 80x60
#Define txcols 80
#Define txrows 60

'Цвета
Const fbYellow = RGB(255, 255, 0)

'Ascii символы
Const acBlock = Chr(219)

Пока в файле определений у нас только несколько констант, необходимых для отображения заставки. Обратите внимание, мы используем как директивы #Define, так и тип Const. #Define используется для числовых констант и макросоа, а Const для расчетных значений. Мы не хотим каждый раз, когда используем fbYellow в программе, вычислять для него RGB значение цвета. Почему мы не используем Const везде? Потому что использовании значения из Const программа должна обратиться к таблице символьных имен, чтобы найти значение этой константы. Когда же мы используем #Define, то компилятор сам заменяет все встречающиеся в программа charw на 8, как будто мы сами написали там число и программе не нужно обращаться к таблице имен. Однако, если бы мы и в самом деле в коде программы везде указывали конкретное число, а затем, для отладки например, нам понадобилось бы его сменить, то ничего хорошего бы из этого не вышло, т.к. очень трудно было бы вспомнить все места в программе, где мы указывали 8 для задания ширины символов. Поэтому, лучше, чтобы эту работу вместо нас сделал компилятор.

Все исходные коды программы (в том виде, в котором она существует на данный момент), вы можете найти в директории chap01.

Сейчас самое время назначить нашему приложению иконку. Добавим к нашему проекту еще 2 файла. Это dod.ico, который находится в директории images и dod.rc, который сообщает компилятору FB, где можно взять иконку для приложения.

dod.rc
FB_PROGRAM_ICON ICON images/dod.ico

Файл rc содержит только одну строку, указывающую компилятору место, откуда взять файл с иконкой. Для этого нужно указать данный файл в качестве параметра командной строки компилятора. Если вы хотите скомпилировать данную программу самостоятельно, то вам нужно будет изменить dod.rc — указать расположение значка программы в вашей системе. Редактировать его можно в любом текстовом редакторе.

Для компиляции, из директории с проектом, наберите в командной строке:

fbc -exx dod.bas dod.rc

Предполагается, что путь до компилятора FreeBasic указан в настройках системы, если это не так, то вам необходимо указывать полный путь до компилятора (Если Вы используете ОС Linux, то не нужно никаких *.rc файлов. Достаточно сконвертировать файл иконки в графический формат xpm, например при помощи Gimp, и просто указать этот файл в качесте параметра командной строки. Правда тут мы столкнемся с багом fbc, который известен еще с 2008-го года. нельзя, чтобы файл иконки и bas файл имели одтинаковое имя, если не брать во внимание расширение. Если имена разные, все работает нормально (примечание переводчика)).

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

Когда наша программа будет готова к распространению, мы будем компилировать ее без окна консоли и расширенной проверкой на ошибки, т.к. это замедляет выполнение.

Мы можем сделать это следующим образом:

fbc dod.bas dod.rc -s gui

Ключь -s gui указывает компилятору, что мы хотим скомпилировать программу в графическом режиме, без консоли.

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

2 комментария:

  1. Про преобразование изображения в массив можно поподробней? Желательно пошагово. А то новичку не понятно.

    ОтветитьУдалить
    Ответы
    1. Ну, я просто только переводил книгу, потому, для "поподробней", написал статью небольшую: http://uvadzucumi.blogspot.com/2013/02/1-freebasic-bitmap-image-demo.html пока просто код и немного коментов, но, думаю, и так понятно. позже более подробно по коду распишу.

      Удалить