Программирование игр в Freebasic урок 1 (часть 1)

Автор урока: Lachie D.(октябрь, 2007)

Оригинал находится ТУТ 

Перевод, точность далеко не 100% : Станислав Будинов

 

Введение

Цель этой серии уроков помочь новичкам изучить основы программирования в FreeBASIC для создания компьютерной игры. Некоторые элементарные базовые знания программирования помогли бы. Так если вы знакомы с основами QuickBASIC, Visual Basic, или любым другим вариантом BASIC, эти уроки должны быть легки для понимания. Начиная эту серию, я понимаю, что в подобных учебниках FreeBasic испытывает недостаток . Я встречал немало людей испытывающих одинаковые проблемы с программированием игр и постарался изложить материал как можно понятнее. Я тоже помню свое начало и частично те проблемы. Данный учебник не может стать абсолютом , поскольку задачи и реализации , которые вы заложите в свою игру могут далеко зайти за те рамки, которые здесь будут озвучены, но начальные навыки в 90% случаев будут полезны. Кроме того, многое зависит и от  графической библиотеки (инструментов) которые вы захотите использовать. Примеры программ и мини-игр, которые мы создадим, будут написаны с помощью GFXlib библиотеки, встроенной в FreeBasic. Такие игры как: Lynn's Legacy, ArKade, Mighty Line и Poxie были написаны с помощью ее, и для этих игр получены хорошие отзывы. Не волнуйтесь, при желании переход с одной графической библиотеки на другую не сложен, при условии, что вы научитесь писать игры с помощью GFXlib. Данный учебник не будет описывать программирование в 3D, поскольку умение программировать в 3D это совсем другой уровень, далеко выходящий за рамки учебника для начинающих. Для программирования вам понадобится компилятор FreeBasic (можно взять  http://www.freebasic.net),  а так же редактор для написания исходных текстов. Я рекомендую редакторы FBIDE и FBEdit.

 

Пример 1: простая программа - движение круга.

Мы начнем с простого. В первой программе мы не будем использовать загрузку изображений , поскольку это может только смутить вас на этом этапе, поверьте мне на слово и будьте терпеливы. Программа, которую мы создадим, позволит двигаться окружности по экрану. Это очень простая программа,но за счет этого вы узнаете много элементарных вещей, необходимых для создания любой игры с помощью  GFXlib библиотеки. Используя GFXlib, вам часто придется обращаться к справке FreeBasic , так как я не буду объяснять каждый параметр разных функций, используемых в примерах программ.
Открываем редактор и первое что мы там напишем, это функцию установки графического режима. Какой режим выбрать? Давайте для начала возьмем режим с 8-битной глубиной цвета и 256 цветами (8 бит на пиксель). Графический режим устанавливается с помощью функции SCREEN :

Screen 13,8,2,0
Sleep

  • 13 режим имеет разрешение 320х200
  • 8 глубина цвета, 8 битовая графика
  • 2х страничный вывод графики
  • 0 режим окна (если будет 1, то на весь экран)

Минимум 2х страничный вывод графики рекомендуется для любой графической программы. Это станет понятнее чуть позже. Следующее, что мы должны будем сделать, это установить цикл, который играет пока пользователь не нажмет клавишу "Q" на клавиатуре. Циклы являются основой любой программы. В игре должен происходить как минимум один цикл, за счет которого поддерживается "жизнь игры". Во время выполнения цикла отлавливаются соответствующие нажатия клавиш, перемещения мыши , выводится информация. Установить цикл можно разными способами:

  • с помощью WHILE:WEND
  • с помощью DO...LOOP
  • с помощью Goto и метки

Но при этом нужно обеспечить условие для выхода из цикла. Сделать это можно например добавив после Loop инструкцию Until и условие для выхода:

Screen 13,8,2,0 ' устанавливаем графический режим
Do
    'цикл работает пока не будет нажата клавиша "Q"
Loop Until InKey$ = "Q" Or InKey$ = "q"

Если скомпилировать этот код и запустить его, вы получите маленькое черное пустое окно размером 320х200 , которое можно отключить, нажав клавишу Q (возможно, потребуется сделать окно активным). Программа повторяет петли пока вы не нажмете" Q или "q". Я использовал оба (верхний и нижний) регистры на тот случай, если Caps Lock включен на вашей клавиатуре. Команда INKEY$ возвращает последнюю клавишу нажатия клавиатуры. Использование этой команды не самое лучшее, и позднее я покажу на что лучше заменить ее. Чтобы нарисовать круг, мы будем использовать функцию CIRCLE. Запустите следующий код:

Screen 13,8,2,0 ' устанавливаем графический режим
Do
    Circle (150, 90), 10, 15
Loop Until InKey$ = "Q" Or InKey$ = "q"

Код выше рисует маленький круг в координатах 150х90 , с радиусом 10 и цветом 15 ( белый) в цикле. Как заставить двигаться круг? Чтобы была такая возможность, мы должны сделать его координаты меняющимися. Для этого мы будем использовать две переменные с именами circlex и circley. Скомпилируйте и запустите следующий код:

Dim Shared As Single circlex, circley
Screen 13,8,2,0 ' устанавливаем графический режим
circlex = 150 ' начальная позиция окружности
circley = 90
Do
    Circle (circlex, circlex), 10, 15
Loop Until Inkey$ = "Q" Or Inkey$ = "q"


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

 

Два способа объявления переменных:

Dim variable_name [AS type_of_variable]

или так:

Dim [AS type_of_variable] variable1, variable2, variable3, ...


Типы данных в FreeBasic: BYTE, SHORT, INTEGER, STRING, SINGLE, DOUBLE и некоторые другие. Каждый тип надо использовать там, где ему место. Так например при использовании строковых значений нужно использовать тип String. А при итерациях в цикле чаще используется тип Integer. Так же его можно использовать для подсчета патронов, жизней и пр. Где то в графических вычислениях вам понадобятся числа с повышенной точностью: Double или Single. Это, как правило, переменные, определюющие физику, формулы движения автомобиля или прыжок.Разница между скоростью в два пикселя и скоростью в один пиксель за один такт цикла, чаще всего слишком большая, но это не мешает эмулировать различные эффекты, например движение жидкости.
Для того, чтобы ваши переменные были доступны во всей программе, вам необходимо будет сделать их глобальными с помощью ключевого слово Shared сразу после команды DIM при объявлении. Однако если нет такой необходимости, можно найти другие возможности доступа к этим переменным, например при передаче параметра в функцию. Массивы образуют цепочку однотипных переменных, идущих друг за другом. Массивы можно сделать разного размера и если будет меняться размерность массива, лучше объявлять его с помощью команды ReDIM. Строки используются для хранения текстовых данных. Например:

Name = "Dront"

Но прежде переменную Name надо объявить

Dim Name As STRING.

Теперь я представлю новую функцию вместо команды INKEY$.
INKEY$ по сути совсем непригодна в играх из-за своей слабой чувствительности и возможным возвратом только одной клавиши в один момент времени и это может быть вы успели заметить в прошлых примерах.
Лучшим вариантом будет функция MULTIKEY GFXlib библиотеки. Она имеет один параметр: DOS скан код клавиатуры, который вы хотите задействовать в игре. Для того, чтобы узнать какой клавише какой код соответствует, откройте справку по FreeBasic и найдите функцию MULTIKEY. Далее по одной из ссылок (scan code) на странице этой функции найдете табличку с этими кодами. Так к примеру  (&h1C) сооответствует клавише  ENTER.
Для того, чтобы можно было использовать функцию MULTIKEY да и вообще библиотеку GFXlib  нужно подключить файл: fbgfx.bi в ваш код. В этом файле реализованы декларации функций и другая вспомогательная информация, которая способствует нормальной работе библиотеки в среде FreeBasic . Подключение выглядит так:

#INCLUDE "fbgfx.bi" 
Using FB


Лучше всего поставить эти две строки в начале программы. Вам не нужно устанавливать путь к fbgfx.bi, так как он установлен в глобальный для FreeBasic каталог и компилятор видит его. Using FB сообщает программе, что мы будем иметь доступ к GFXlib именам, начинающихся с FB. Если не писать эту строку, то перед каждой функцией придется писать примерно так: FB.MULTIKEY

Теперь начинается самое интересное.

Мы добавим новую переменную с именем circlespeed, для того чтобы определить на сколько пикселей сможет перемещаться круг за один проход цикла. Движение будет осуществляться  ключевыми стрелками. Каждый раз, когда пользователь нажимает определенную клавишу со стрелкой, программа меняет либо circlex либо circley (в зависимости от нажатой клавиши) на сумму circlespeed. Запустите следующий код:

#INCLUDE "fbgfx.bi"
Using FB
Dim Shared As Single circlex, circley, circlespeed
Screen 13,8,2,0 ' установка режима экрана
circlex = 150   ' начальная позиция круга
circley = 90
circlespeed = 1 ' скорость круга => 1 пиксел за проход цикла
Do
    Circle (circlex, circley), 10, 15
    ' При нажатии клавиш, мы меняем расположение круга
    If Multikey(SC_RIGHT) Then circlex = circlex + circlespeed
    If Multikey(SC_LEFT) Then circlex = circlex - circlespeed
    If Multikey(SC_DOWN) Then circley = circley + circlespeed
    If Multikey(SC_UP) Then circley = circley - circlespeed
Loop Until Multikey(SC_Q) Or Multikey(SC_ESCAPE)


Как видите, мы также изменили логику выхода из цикла. Теперь можно выходить из программы не только с помощью клавиши Q , но и с помощью клавиши ESCAPE.

Скомпилировав и запустив код выше, вы получите не то, чтобы наверное хотелось. Вместо движения круга по экрану, появляется мазок в сторону ту же, что показывает клавиша, которую вы нажали. По сути движение круга есть, но во первых оно происходит слишком быстро, а во вторых один нарисованный отпечаток круга накладывается на другой. В результате мы видим что-то типа линии.  Для того, чтобы круг наконец-то у нас перемещался так как мы этого хотим, надо изменить логику программы. То есть уменьшить скорость и стирать старый отпечаток круга, если нарисован новый. Для этого нам хорошо помогут функции  CLS (очистка экрана цветом фона) и Sleep( остановка программы на заданный промежуток времени в миллисекундах). И так для очистки экрана, нам достаточно перед новой зарисовкой круга очищать экран установив в тело цикла функцию CLS.
Функция Sleep так же размещенная в цикле поможет не только уменьшить скорость передвижения круга, но и освободит ресурсы процессора. Без нее процессор потратит на вашу программу почти 100% отдачу, в результате чего остальные программы не смогут стабильно и правильно работать. По сути мы не только уменьшаем скорость круга, но и скорость всего цикла, а значит и скорость всей игры. Об этом нужно помнить при установке первого параметра функции Sleep.

Вставьте ниже код в редактор, скомпилируйте и запустите.

#INCLUDE "fbgfx.bi"
Using FB
Dim Shared As Single circlex, circley, circlespeed
Screen 13,8,2,0 ' установка графического режима
circlex = 150   ' начальная позиция круга
circley = 90
circlespeed = 1 ' скорость круга => 1 пиксель за проход
Do
    Cls
    Circle (circlex, circley), 10, 15
    ' According to pushed key we change the Circle's coordinates.
    If Multikey(SC_RIGHT) Then circlex = circlex + circlespeed
    If Multikey(SC_LEFT) Then circlex = circlex - circlespeed
    If Multikey(SC_DOWN) Then circley = circley + circlespeed
    If Multikey(SC_UP) Then circley = circley - circlespeed
    Sleep 10, 1
Loop Until Multikey(SC_Q) Or Multikey(SC_ESCAPE)


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

Способ использования переменных не самый удобный в больших проектах, где у нас огромное количество переменных обычно ассоциируется с несколькими объектами (объект может быть игрок, враг). И так нам нужен свой тип данных, в котором мы сможем размещать кучу переменных или массивов, но имеющий общее начало для легкого доступа к любой переменной. Мы назовем его пользовательский тип ObjectType:

Type ObjectType
    x As Single
    y As Single
    speed As Single
End Type

После объявления ниже в коде у нас круг становится объектом:

Dim Shared CircleM As ObjectType
' мы не можем назвать переменную или объект именем Circle
' поскольку переменные по правилам языка не могут носить названия
' такие же как у встроенных функций языка, поэтому назовем ее "CircleM"


Чем лучше данный метод?  Он позволяет управлять переменными программы более эффективным и защищенным образом. К примеру у нас будет не один круг а 100. Что для каждого круга создавать свои 100*3=300 переменных? Да и придется для каждой переменной придумывать свое имя. Данный способ позволит создавать объекты. Каждый из этих объектов как в нашем примере будет иметь три поля, но эти поля для каждого объекта изолированы от полей других объектов. Это сократит написание кода, улучшает его чтение и понимание. А все обращение к полям будет проходить через имя объекта и через точку имя поля:

CircleM.X - позиция по оси х
CircleM.Y - позиция по оси y
CircleM.speed - скорость

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

Dim Shared EnemyCircle (8) As ObjectType

И это позволит управлять 9 объектами (кругами) , имеющими те же свойства расположения по оси X , Y  и speed .  Почему не 8 а 9 ? Массив начинает свой отсчет с 0. В итоге можете посчитать сколько объектов: 0,1,2,3,4,5,6,7,8

После того, как мы введем свой пользовательский тип, пример выглядит так:

#INCLUDE "fbgfx.bi"
Using FB
' свой пользовательский тип.
Type ObjectType
    x As Single
    y As Single
    speed As Single
End Type

Dim Shared CircleM As ObjectType
' мы не можем назвать переменную или объект именем Circle
' поскольку переменные по правилам языка не могут носить названия
' такие же как у встроенных функций языка, поэтому назовем ее "CircleM"
Screen 13,8,2,0 ' установка графического режима
Setmouse 0,0,0 ' прячем курсор мышки
CircleM.x = 150   ' начальная позиция круга
CircleM.y = 90
CircleM.speed = 1 ' скорость круга => 1 пиксел за проход цикла
Do

    Cls
    Circle (CircleM.x, CircleM.y), 10, 15
    ' According to pushed key we change the Circle's coordinates.
    If Multikey(SC_RIGHT) Then CircleM.x = CircleM.x + CircleM.speed
    If Multikey(SC_LEFT) Then CircleM.x = CircleM.x - CircleM.speed
    If Multikey(SC_DOWN) Then CircleM.y = CircleM.y + CircleM.speed
    If Multikey(SC_UP) Then CircleM.y = CircleM.y - CircleM.speed
    Sleep 10, 1 ' ожидание 10 миллисекунд.
Loop Until Multikey(SC_Q) Or Multikey(SC_ESCAPE)


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

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

Всего доброго!

1 урок часть 1 | 1 урок часть 2