Программирование игр в Freebasic урок 1 (часть 2)
Автор урока: Lachie D.(октябрь, 2007)
Оригинал находится ТУТ
Перевод, точность далеко не 100% : Станислав Будинов
Пример 2: Воин бегал по зеленому полю
В этой части мы приступаем к построению кода нашей мини-игры, которая не будет завершена в этом уроке. Мы всего лишь создадим программу, в которой воин бегает по зеленому полю (один экран).
Мы собираемся работать в 8-битном режиме глубины цвета, так что изображения, которые нужно будет использовать должны быть сохранены в режиме 256 цветов. Для спрайтов "воин" будем использовать спрайты главного персонажа из моей первой игры Dark Quest..
Как видите, это изображение разделено на 12 спрайтов , размер каждого
20 * 20 пикселей. Два спрайта для каждого направления (прогулка анимация) и один
спрайт для каждого направления, когда воин размахивает своим мечом. Размахивание
мечом не будет реализовано в первом уроке, но станет необходимым позже.
Второе изображение будет служить фоном. Оно имеет размер 320 х 200
пикселей, 8-битное изображение в формате BMP:
Оба эти изображения сохраните с этой страницы и положите в папку с исходным текстом программы. Либо вверху есть ссылка на сайт автора урока, все архивы можно скачать оттуда при желании.
В начале нашей программы, мы должны включить в код файл fbgfx.bi, как и в первом примере, и задать тот же графический режим. Код:
#INCLUDE "fbgfx.bi" Using FB Screen 13,8,2,0 ' Sets the graphic mode Setmouse 0,0,0 ' Hides the mouse cursor
Теперь мы объявим переменные-указатели, которые будут указывать на буферы
памяти, в которые загружены наши изображения (по одному для спрайтов и один для
фона).
Первый указатель назовем background1 , а объявим его с помощью
следующей строки:
Dim Shared background1 As Any Ptr
ANY PTR говорит нам, что переменная background1 это указатель на область памяти. Указатель определяемый как ANY PTR может указывать на различные типы данных. Использование переменной указателя необходимо для функции создания изображений в памяти IMAGECREATE. Эта функция создав изображение, возвратит указатель на это изображение для дальнейшего использования. Этот возвращаемый указатель мы и сохраним в переменной background1. IMAGECREATE может создавать изображения с различными размерами и глубиной цвета.
Следующие 12 указателей для наших спрайтов целесообразно держать не в переменных, а в массиве. Создаем массив таким образом:
Dim Shared WarriorSprite(12) As Any Ptr
Каждая ячейка массива(кроме 0) будет содержать указатель на один из 12 спрайтов.
Для всех программ объявления переменных и декларации желательно помещать в начале кода после строк , в которых идет подключение файлов (если они есть).В соответствии с этим, начало нашей программы должно выглядеть примерно так:
#INCLUDE "fbgfx.bi" Using FB ' указатель на область памяти буфера нашего изображения для фона Dim Shared background1 As Any Ptr '12 указателей в массиве на область памяти ' буфера наших изображений спрайтов Dim Shared WarriorSprite(12) As Any Ptr Screen 13,8,2,0 ' установка режима экрана Setmouse 0,0,0 ' прячем курсор
После установки разрешения экрана, глубины цвета и количества рабочих страниц с помощью Screen, мы будем использовать функцию SCREENSET, которая позволяет рисовать графику на отдельной странице видеопамяти, а выводить на другой. Это значительно уменьшает мерцание и другие нежелательные эффекты в требовательных программах. Ее главные возможности: устанавливать страницы графического буфера видимыми или невидимыми. Так пока происходит рисование на одной странице, отображается другая. Когда страница дорисуется, с помощью функции SCREENCOPY готовая страница копируется на разрешенную с помощью Screenset для видимости страницу. У функции ScreenSet два параметра. Первый номер страницы, второй флаг видимости (0-невидима , 1-видима).
Точечный рисунок в формате BMP загружается с помощью функции BLOAD. Если нужно сохранение рисунка, то есть другая функция BSAVE. Изображения в BMP формате могут быть загружены непосредственно на экран или в соответствующий буфер, закрепленный за переменной. При загрузке изображения с BLOAD, связанная с ней палитра, будет установлена в качестве текущей палитры. В 8-битном режиме графика должна быть в той же палитре. В 16-битном и выше режимах вам не нужно будет задумываться о палитре. Для загрузки изображений в других форматах, можно использовать сторонние библиотеки. Так например, для загрузки изображений в формате PNG , могу порекомендовать PNG library.
Сначала мы загрузим фоновое изображение с помощью BLOAD , а потом захватим его с нужными размерами с помощью функции GET и присвоим указателю background1 . Пример загрузки для фонового изображения:
'.... Screenset 1, 0 background1 = Imagecreate (320, 200) Bload "BACKGRND.bmp", 0 Get (0,0)-(319,199), background1
BACKGRND.bmp - это имя загружаемого файла. Если изображение помещено в
другой каталог, например Graphics , то необходимо прописывать правильно путь до
него: "Graphics/BACKGRND.bmp" Ни в коем случае не используйте жестко заданные
пути к изображениям и другим ресурсам программ и игр. К примеру, если путь будет
указан так : "C: / FreeBASIC / myprograms / Бобо / BACKGRND.bmp ", это
заставит пользователя использовать вашу программу только в определенном
каталоге. Второй параметр функции BLOAD поставлен 0 и это означает, что мы
хотим загрузить изображение на экран. Вместо нуля, можно указать
переменную-указатель за которой будет закреплено изображение, но поскольку мы
впоследствии это делаем с помощью функции GET, то в этом отпадает надобность.
Обратите внимание на размеры указанные в функции GET . В конечном счете
они будут соответствовать правильной ширине и высоте рисунка. Другие образы
изображений будут спрайтами , а закреплять их буферы будем за
ячейками массива. Всего 12 спрайтов 20х20 пикселей. Код, который загружает
второе изображение и разделяет его на 12 маленьких спрайтов:
WarriorSprite(1) = Imagecreate (20, 20) WarriorSprite(2) = Imagecreate (20, 20) WarriorSprite(3) = Imagecreate (20, 20) WarriorSprite(4) = Imagecreate (20, 20) WarriorSprite(5) = Imagecreate (20, 20) WarriorSprite(6) = Imagecreate (20, 20) WarriorSprite(7) = Imagecreate (20, 20) WarriorSprite(8) = Imagecreate (20, 20) WarriorSprite(9) = Imagecreate (20, 20) WarriorSprite(10) = Imagecreate (20, 20) WarriorSprite(11) = Imagecreate (20, 20) WarriorSprite(12) = Imagecreate (20, 20) Bload "SPRITES.bmp", 0 Get (0,0)-(19,19), WarriorSprite(1) Get (24,0)-(43,19), WarriorSprite(2) Get (48,0)-(67,19), WarriorSprite(3) Get (72,0)-(91,19), WarriorSprite(4) Get (96,0)-(115,19), WarriorSprite(5) Get (120,0)-(139,19), WarriorSprite(6) Get (144,0)-(163,19), WarriorSprite(7) Get (168,0)-(187,19), WarriorSprite(8) Get (192,0)-(211,19), WarriorSprite(9) Get (216,0)-(235,19), WarriorSprite(10) Get (240,0)-(259,19), WarriorSprite(11) Get (264,0)-(283,19), WarriorSprite(12)
Я думаю вы заметили сколько однотипных команд в этом коде. Такая запись нецелесообразна, в особенности если кол-во спрайтов возрастет до нескольких сотен. Все это проще проделать в цикле, тем более что значения в функции Get пропорционально изменяются. В итоге оптимизированный код выглядит так:
For imagepos As Integer = 1 To 12 WarriorSprite(imagepos) = Imagecreate (20, 20) Get (0+(imagepos-1)*24,0)-(19+(imagepos-1)*24,19),WarriorSprite(imagepos) Next imagepos
Если вы не представляете себе, как работают циклы, я объясню это. Цикл
For-Next - это петля, выполняющая операцию между FOR и NEXT столько раз, сколько
будет указано в цифре по правую сторону по отношению к оператору TO, но при этом
начальная позиция с которой стартует цикл указывается по левую сторону оператора
TO. В итоге наш цикл начнет с цифры 1 и каждый раз прибавляя значения на
единицу, выполнится 12 раз.
12 раз создается новое изображение и
захватывается с помощью GET с размерами 20х20, при этом каждый раз координаты
захвата меняются и все это так же закрепляется за указателями, которые
сохраняются в 12 ячейках массива. В итоге наши спрайты(или точнее
указатели на них) окажутся в ячейках массива в таком виде:
WarriorSprite(1) - движение вниз #1
WarriorSprite(2) - движение вниз
#2
WarriorSprite(3) - движение вверх #1
WarriorSprite(4) - движение вверх
#2
WarriorSprite(5) - движение влево #1
WarriorSprite(6) - движение влево
#2
WarriorSprite(7) - движение вправо #1
WarriorSprite(8) - движение
вправо #2
WarriorSprite(9) - движение с мечом вверх
WarriorSprite(10) -
движение с мечом вниз
WarriorSprite(11) - движение с мечом
влево
WarriorSprite(12) - движение с мечом вправо
А код должен выглядеть примерно таким образом:
#INCLUDE "fbgfx.bi" Using FB Screen 13,8,2,0 ' установка режима экрана Setmouse 0,0,0 ' прячем курсор Dim Shared background1 As Any Ptr ' указатель для фона Dim Shared WarriorSprite(12) As Any Ptr 'массив указателей для спрайтов ' пока скрываем 1 видео страницу памяти ' поскольку загружаем изображения на экран Screenset 1, 0 ' создаем новое изображение в памяти, загружаем фон на экран ' и получаем его с помощью GET в наш буфер background1 = Imagecreate (320, 200) Bload "BACKGRND.bmp", 0 Get (0,0)-(319,199), background1 Cls ' очищаем экран ' перед загрузкой изображения (не обязательно, но неплохо) ' Загружаем изображение для спрайта ' и получаем его части спомощью Get в массив Bload "SPRITES.bmp", 0 For imagepos As Integer = 1 To 12 WarriorSprite(imagepos) = Imagecreate (20, 20) Get (0+(imagepos-1)*24,0)-(19+(imagepos-1)*24,19),WarriorSprite(imagepos) Next imagepos
Наконец-то мы разобрались с загрузкой графики. Теперь мы объявим дополнительные переменные, необходимые в данном примере. Я буду использовать тот же пользовательский тип, что и в предыдущем примере, немного дополнив его:
Type ObjectType X As Single Y As Single Speed As Single Frame As Integer Direction As Integer Move As Integer Attack As Integer Alive As Integer End Type
Объект (воин), который будет использоваться, объявим так:
Dim Shared Player As ObjectType
- Frame - будет использоваться как номер спрайта, который следует выводить на экран. Зависит от ниже описываемых полей
- Direction - используется для определения одного из 4 направлений.
- Move - флаг, сигнализирующий движется герой или нет
- Attak - флаг, сигнализирующий атакует герой или нет
- Alive - жив герой или нет ( не будет использоваться в нашем примере)
Давайте напишем реализацию цикла, в котором сделаем чередование загрузки видеостраниц:
Do Screenlock ' запираем экран (ничего не будет ' отображаться на экране). Screenset workpage, workpage Xor 1 ' меняем страницу. Cls ' здесь будет ваш код, реализующий рисование . workpage Xor = 1 'опять меняем страницу. Screenunlock ' открываем экран, теперь будет отображение на экране. Sleep 10, 1 ' уменьшаем скорость и бережем ресурсы процессора. Loop Until Multikey(SC_Q) Or Multikey(SC_ESCAPE)
Конечно перед использованием переменной workpage ее нужно будет объявить так:
Dim Shared workpage As Integer
Далее нам надо вставить рисунок нашего героя на экран, делается это с помощью функции PUT, синтаксис которой выглядит так:
Put (x координата, y координата), указатель на буфер рисунка, режим
С первыми параметрами я думаю вам все понятно, это координаты, третий параметр указатель на один из 12 буферов, указатели которых находятся в нашем массиве. Четвертый параметр это режим отображения. На данный момент вам нужно знать о двух режимах:
- PSET - вставляет все пиксели изображения ( не подойдет для нашего случая, поскольку герой станет рисоваться в черном квадрате на нашем зеленом фоне
- TRANS (с прозрачным фоном)- вставляет все писксели, но пропускает пиксели, которые имеют цвет фона.
В 16-битном режиме и выше, прозрачным цветом выступает розовый RGB (255,0,255) , а в 8-битном режиме данным цветом является черный или 0 (первый в палитре). При разных нажатиях клавиатуры, будет назначаться одна из 4 цифр полю Direction , а далее в зависимости от этой цифры, будет загружаться нужный спрайт , определяющий направление:
#INCLUDE "fbgfx.bi" Using FB ' Полезные константы (делают ваш код более легким для чтения). Const FALSE = 0 Const TRUE = 1 Screen 13,8,2,0 ' установка режима экрана Setmouse 0,0,0 ' прячем курсор Dim Shared background1 As Any Ptr ' Указатель на фоновое изображение Dim Shared WarriorSprite(12) As Any Ptr 'Укатель на на массив спрайтов Dim Shared workpage As Integer ' пока не показываем страницу, при загрузках спрайтов Screenset 1, 0 'Загрузка фонового изображения в буфер background1 = Imagecreate (320, 200) Bload "BACKGRND.bmp", 0 Get (0,0)-(319,199), background1 Cls ' очистка, не обязательно, но неплохо 'Загрузка изображения, из которого будем нарезать спрайты Bload "SPRITES.bmp", 0 For imagepos As Integer = 1 To 12 WarriorSprite(imagepos) = Imagecreate (20, 20) Get (0+(imagepos-1)*24,0)-(19+(imagepos-1)*24,19), WarriorSprite(imagepos) Next imagepos Type ObjectType X As Single Y As Single Speed As Single Frame As Integer Direction As Integer Move As Integer Attack As Integer Alive As Integer End Type Dim Shared Player As ObjectType 'начальные позиции и скорость героя Player.X = 150 Player.Y = 90 Player.Speed = 1 Player.Direction = 1 Do ' Player.Direction = 1 -> движение вправо ' Player.Direction = 2 -> движение влево ' Player.Direction = 3 -> движение вниз ' Player.Direction = 4 -> движение вверх Player.Move = FALSE ' по умолчанию флаг Move false '(герой не движется) 'Если нажата хоть одна из клавиш, то флаг Move TRUE 'выставляется значение флага Direction (от 1 до 4) 'выставляются новые координаты рисования If Multikey(SC_RIGHT) Then Player.X = Player.X + Player.Speed Player.Direction = 1 Player.Move = TRUE End If If Multikey(SC_LEFT) Then Player.X = Player.X - Player.Speed Player.Direction = 2 Player.Move = TRUE End If If Multikey(SC_DOWN) Then Player.Y = Player.Y + Player.Speed Player.Direction = 3 Player.Move = TRUE End If If Multikey(SC_UP) Then Player.Y = Player.Y - Player.Speed Player.Direction = 4 Player.Move = TRUE End If Screenlock ' запираем экран (ничего не отображается Screenset workpage, workpage Xor 1 ' Меняем видеостраницу. 'Выствляем правильный номер спрайта в зависимости 'от значения флага Direction. If Player.Direction = 1 Then Player.Frame = 7 If Player.Direction = 2 Then Player.Frame = 5 If Player.Direction = 3 Then Player.Frame = 1 If Player.Direction = 4 Then Player.Frame = 3 Cls ' очищаем экран. ' Вставляем спрайт на экран с прозрачностью Put (Player.X, Player.Y), WarriorSprite(Player.Frame), Trans workpage Xor = 1 ' меняем страницу. Screenunlock ' Отпираем экран (отображается все нарисованное). Sleep 10, 1 ' уменьшаем скорость программы и бережем ресурсы процессора Loop Until Multikey(SC_Q) Or Multikey(SC_ESCAPE)
В коде я записал две константы. Они позволяют сделать наш код более
легким для чтения. Вместо того, чтобы иметь дело с 0 и 1 для определения
логических величин: ложь и истина , везде где нужно мы пишем TRUE (1)
и FALSE (0). Если вы скомпилируете и запустите этот код, то сможете
увидеть движение героя при нажатиях клавиш. Однако пока герой бегает по черному
полю и не видно анимации движения ног. Сделать все это несложно. Вы увидите, как
переменная MOVE теперь пригодится.
Вы должны добавить этот код сразу после CLS:
Frame1 = (Frame1 Mod 2) + 1 If Player.Move = FALSE Or Frame1 = 0 Then Frame1 = 1
Не забудьте вначале перед использованием продекларировать переменную
Frame1:
Dim Shared As Integer Frame1
По сути код: Frame1 = (Frame1 MOD 2) + 1 служит заменой такому коду:
Frame1 = Frame1 + 1 If Frame1 > 2 Then Frame1 = 1
То есть по сути мы создали цикл от 1 до 2. Функция MOD возвращает остаток
от операции деления. Если при делении делимое меньше чем делитель, функция MOD
возвращает делимое. Отсюда и получается:
- 0 mod 2 -> вернет 0
- 1 mod 2 -> вернет 1
- 2 mod 2 -> вернет 0
но поскольку в операции присутствует дополнительно +1 , то получается:
- (0 mod 2)+1 -> вернет 1
- (1 mod 2)+1 -> вернет 2
- (2 mod 2)+1 -> вернет 1
Зная это правило, можно сообразить любой счетчик, но нас пока в данном случае интересует возможность анимации движения ног героя. Первое что нужно сделать, это изменить участок кода, который отвечает за вывод одного из 4 направлений спрайта:
If Player.Direction = 1 Then Player.Frame = 6 + Frame1 If Player.Direction = 2 Then Player.Frame = 4 + Frame1 If Player.Direction = 3 Then Player.Frame = 0 + Frame1 If Player.Direction = 4 Then Player.Frame = 2 + Frame1
В итоге поскольку при каждом прохождении цикла у нас Frame1 равен либо 1
либо 2, то получается, что спрайты загружаются попеременно, создавая эффект
движения. К примеру:
Player.Direction = 1
тогда попеременно загружаются спрайты 7 и 8 .
Однако даже если вы вставите этот код и запустите, движения ножек получаются слишком быстрые. Нам нужно их как то замедлить. И опять нас выручит возможность создания счетчика. Только новый счетчик мы сделаем равным 16. Нужно внести новую переменную в код Frame2 . Она станет неким флагом, который при прохождении 16 итераций основного цикла , будет сигнализировать другой части кода, что ему можно выполняться. В итоге оба связанных счетчика будут выглядеть так:
Frame2 = (Frame2 Mod 16) + 1 If Frame2 = 10 Then Frame1 = (Frame1 Mod 2) + 1
Теперь скорость вроде нормальная, но герой шевелит ножками даже тогда, когда стоит. Нужно это исправить! Для этого в код нужно внести строчку, которая будет проверять (Стоит ли герой? Если да, то не чередовать спрайты) . И так вот эта строчка:
If Player.Move = FALSE Then Frame1 = 1
Кроме того давайте сделаем, чтобы наш герой бегал по зеленому фону, как было задумано. В принципе здесь все просто. Добавляем эту строчку для рисования фона перед строчкой , которая рисует самого героя:
Put (0, 0), background1, Pset
Все хорошо, но наверно неправильно то, что наш герой при движении выходит за рамки нашего экрана. Нужно выставить ограничения. Добавьте в код эти строчки:
If Player.X < 0 Then Player.Move = FALSE Player.X = 0 End If If Player.X > 300 Then Player.Move = FALSE Player.X = 300 End If If Player.Y < 0 Then Player.Move = FALSE Player.Y = 0 End If If Player.Y > 180 Then Player.Move = FALSE Player.Y = 180 End If
В итоге окончательный код должен выглядеть так:
#INCLUDE "fbgfx.bi" Using FB ' Полезные константы (делают ваш код более легким для чтения). Const FALSE = 0 Const TRUE = 1 Screen 13,8,2,0 ' установка режима экрана Setmouse 0,0,0 ' прячем курсор Dim Shared background1 As Any Ptr ' Указатель на фоновое изображение Dim Shared WarriorSprite(12) As Any Ptr 'Укатель на на массив спрайтов Dim Shared workpage As Integer Dim Shared As Integer Frame1, Frame2 ' пока не показываем страницу, при загрузках спрайтов Screenset 1, 0 'Загрузка фонового изображения в буфер background1 = Imagecreate (320, 200) Bload "BACKGRND.bmp", 0 Get (0,0)-(319,199), background1 Cls ' очистка, не обязательно, но неплохо 'Загрузка изображения, из которого будем нарезать спрайты Bload "SPRITES.bmp", 0 For imagepos As Integer = 1 To 12 WarriorSprite(imagepos) = Imagecreate (20, 20) Get (0+(imagepos-1)*24,0)-(19+(imagepos-1)*24,19),WarriorSprite(imagepos) Next imagepos Type ObjectType X As Single Y As Single Speed As Single Frame As Integer Direction As Integer Move As Integer Attack As Integer Alive As Integer End Type Dim Shared Player As ObjectType 'начальные позиции и скорость героя Player.X = 150 Player.Y = 90 Player.Speed = 1 Player.Direction = 1 Do ' Player.Direction = 1 -> движение вправо ' Player.Direction = 2 -> движение влево ' Player.Direction = 3 -> движение вниз ' Player.Direction = 4 -> движение вверх Player.Move = FALSE ' по умолчанию флаг Move false '(герой не движется) 'Если нажата хоть одна из клавиш, то флаг Move TRUE 'выставляется значение флага Direction (от 1 до 4) 'выставляются новые координаты рисования If Multikey(SC_RIGHT) Then Player.X = Player.X + Player.Speed Player.Direction = 1 Player.Move = TRUE End If If Multikey(SC_LEFT) Then Player.X = Player.X - Player.Speed Player.Direction = 2 Player.Move = TRUE End If If Multikey(SC_DOWN) Then Player.Y = Player.Y + Player.Speed Player.Direction = 3 Player.Move = TRUE End If If Multikey(SC_UP) Then Player.Y = Player.Y - Player.Speed Player.Direction = 4 Player.Move = TRUE End If 'Следующие четыре условия не позволяют 'воину ходить за пределы экрана. If Player.X < 0 Then Player.Move = FALSE Player.X = 0 End If If Player.X > 300 Then Player.Move = FALSE Player.X = 300 End If If Player.Y < 0 Then Player.Move = FALSE Player.Y = 0 End If If Player.Y > 180 Then Player.Move = FALSE Player.Y = 180 End If Screenlock ' запираем экран (ничего не отображается Screenset workpage, workpage Xor 1 ' Меняем видеостраницу. 'переменная Frame1 изменяется в цикле от 1 до 2 при том 'изменения эти разрешены 1 раз в 16 проходов цикла Frame2 = (Frame2 Mod 16) + 1 If Frame2 = 10 Then Frame1 = (Frame1 Mod 2) + 1 If Player.Move = FALSE Then Frame1 = 1 'Выставляем правильный номер спрайта в зависимости 'от значения флага Direction. If Player.Direction = 1 Then Player.Frame = 6 + Frame1 If Player.Direction = 2 Then Player.Frame = 4 + Frame1 If Player.Direction = 3 Then Player.Frame = 0 + Frame1 If Player.Direction = 4 Then Player.Frame = 2 + Frame1 ' рисуем фон на экране Put (0, 0), background1, Pset 'рисуем спрайт на экране с прозрачностью Put (Player.X, Player.Y), WarriorSprite(Player.Frame), Trans workpage Xor = 1 ' меняем страницу. Screenunlock ' Отпираем экран (отображается все нарисованное). Sleep 10, 1 ' уменьшаем скорость программы и бережем ресурсы процессора Loop Until Multikey(SC_Q) Or Multikey(SC_ESCAPE) ' освобождаем буферы памяти изображений до окончания программы ' освобождается память Imagedestroy (background1) For imagepos As Integer = 1 To 12 Imagedestroy WarriorSprite(imagepos) Next imagepos
Обратите внимание на использование функции IMAGEDESTROY которая
освобождает память, загруженную с помощью IMAGECREATE. Это нужно делать
обязательно.
Если вы захотите использовать другие графические режимы, то вам нужно будет изменить код таким образом:
Screen 14,16,2,0 ... background1 = Imagecreate (320, 240) Bload "BACKGRND24bit.bmp", 0 Get (0,0)-(319,239), background1
В коде меняется режим с 13 на 14 с 16 битным режимом цвета. Кроме того
меняются размеры фонового изображения (319х239). Данный режим уже имеет цвет
фона розовый, поэтому изображение со спрайтами нужно будет преобразовать каким
нибудь редактором в 24 битный режим цветности, или использовать готовое
ниже:
И не забываем при загрузке в коде указать имя этого 24 битного изображения. А так же поменять ограничивающие размеры выхода героя за края экрана. Вот собственно и все. Всего доброго!
1 урок часть 1 | 1 урок часть 2