title
Emulators Machine - журнал о ретроиграх, старых компьютерах и консолях. Выпускается силами энтузиастов ретрогейминга. Бесплатен. Создается с использованием свободного программного обеспечения Scribus, LibreOffice, Inkscape, Gimp в операционной системе Linux.



Супрунов Александр

Программируем игры на DiNGS

(учебник по программированию игр)



Вступление

Учебник состоит из 24 глав, в которых рассмотрены все основные моменты создания 2-D игр. Книга не требует дополнительных знаний - все дается с нуля. Этот курс был написан в 2003 году для журнала Компьютерра и публиковался на ее сайте (пример 1 части http://www.computerra.ru/softerra/program/23791/). В предложенном варианте  на 7 частей больше. Оглядываясь назад я могу сказать, что курс при всех недостатках по прежнему в состоянии дать вам возможность создать СВОЮ неповторимую игру.

Еще один нюанс, на данный момент языка DiNGS не существует, но вы можете поискать дистрибутив этого языка в сети интернет.
Язык DiNGS был переименован в Glbasic и вы можете скачать его новейшую версию с сайта производителя  -  http://www.glbasic.com/ (несмотря на некоторые изменения, код приводимый в учебнике заработает в GlBasic с минимальными изменениями). Для школьников и студентов можно получить бесплатные версии GlBasic.
Кроме того GlBasic/DiNGS использует синтаксис языка Basic, поэтому освоив в увлекательной форме DiNGS вы без труда сможете программировать на Basic и наоборот. От себя хочу добавить, что DiNGS - прекрасный язык, способный воплотить любые ваши игровые фантазии.

DiNGS – программирование игр в домашних условиях.
 
DiNGS – язык программирования созданный специально для написания игр уровня лучших шедевров с 16 битных игровых консолей SEGA Megadrive или Super Nintendo. Даже более, где там старичку Mega Drive сравниться с играми имеющими до 16 миллионов цветов и разрешение 800х600 точек. Важную роль играет также ваша фантазия, каким бы не был совершенным инструмент, без выдумки ничего хорошего не получится. В идеале , для создания игр в домашних условиях , вы должны уметь немного рисовать, сочинять музыку , программировать и иметь чуть-чуть фантазии.

Чем DiNGS лучше других языков?
Он создан специально для игр. Сверхбыстрая и ультраплавная анимация, молниеносные эффекты скалинга (увеличения-уменьшения), ротации (поворота), использование альфа-канала (изменение степени прозрачности объектов) и тд.

Что представляет из себя DiNGS?
По синтаксису он эквивалентен всем известному языку Basic много изучаемому в нашей стране (особенно в этом преуспели владельцы домашних компьютеров ZX Spectrum), но лишен многих его недостатков. Во первых он привязан к современным технологиям (OpenGL, DirectX). Во вторых этот язык не интерпретатор (выполняющий программу по строкам) , а компилятор (создающий исполняемый файл). Ну и в третьих , на самом деле никакой это не Бейсик, а самый натуральный Си  имеющий бейсик-синтаксис. То есть для нас разработчики постарались на полную катушку. Только было бы желание удивить мир своими игровыми шедеврами.

Где взять DiNGS?
На сайте разработчиков … Он не бесплатен, но стоит весьма недорого и могу уверить , вы никогда не пожалеете потраченных денег.
Перед тем как мы начнем учиться писать игры на DiNGS рассмотрим главные понятия.

ЧТО ТАКОЕ ФОН?
В каждой игре присутствует какое-то фоновое изображение где разворачивается игровое действие. Например в космических шутерах , где маленький кораблик выносит шквальным огнем полчища злобных пришельцев все это происходит на фоне величественного космоса. Фон в простейшем случае можно сделать из нарисованной в любом графическом редакторе картинки в формате BMP имеющей палитру в 16 миллионов цветов и размер равный разрешению экрана дисплея в коем планирует функционировать ваш шутер.


Пример фоновой картинки

Фон также может состоять из множества небольших спрайтов движущихся с разной скоростью, что добавит в игру ощущение объема.
В DiNGS фоновая картинка выводится с помощью функции (подпрограммы)
loadBMP “название_картинки.bmp”

ЧТО ТАКОЕ СПРАЙТ?
На фоне космоса движутся маленькие игровые объекты - корабли, астероиды, призы – все это и есть спрайты (картинки). Мы можем запрограммировать управление ими или поручить часть объектов подпрограммам компьютера.


Пример спрайта

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

ЧТО ТАКОЕ КОЛЛИЗИЯ?
Коллизия – обработка столкновения спрайтов. Этим в Dings занимается функция CALLBOX , которая с помощью одного из своих параметров сообщает было ли столкновение нужных для игрового действия объектов (об этом будет рассказано более подробно ниже). Попадание снаряда во вражеский кораблик, отбитый шар в арканоиде, перепрыгивание на летающие островки в платформенных аркадах – все это коллизия (столкновение). Получив сведения, что объекты столкнулись мы можем задать некие действия. Например если произошло столкновение (коллизия) летящего шара и биты, тогда нужно изменить направление полета шарика на противоположное.

ДВИЖЕНИЕ ОБЪЕКТОВ
У каждого игрового объекта есть координаты на экране. Отобразим на экране шар. У него есть координаты Х и У. Воздействуя на них мы можем изменять положение шарика. Пусть вывод спрайта производится посредством процедуры SPRITE , которая имеет некоторые параметры. Одни из них – и есть координаты объекта. Но если задать их просто числами , то ничего уже изменить не удастся. Поэтому прибегнем к помощи переменных. Пусть вывод осуществляется так :

SPRITE 0, X,Y,0


Обо всех параметрах мы поговорим позднее. Изменяя значение Х или Y мы заставим объект двигаться:

x=x+1.

ЧТО ТАКОЕ ЦИКЛЫ?
Но на самом деле, чтобы все это двигалось (враги перемещались, Марио прыгал по платформочкам, ниндзя сражался с боссом ) необходимо организовать цикл – то есть последовательное повторение одних и тех же операторов.
Есть много способов для достижения оного. Самый простой вариант – использовать метки:

metka:
Вывод спрайта в координаты X и Y.
Изменение координаты Х.
GOTO metka:

Программа начинается на слове метка , выполняет вывод спрайта изменяет координату Х и опять перемещается на метку metka и снова все повторяется.
В результате спрайт шара движется.
Минус такого подхода очевиден – цикл бесконечен – из него не предусмотрен выход.
Есть более гибкие операторы – один из них

while <условие выхода>
действия предусмотренные пользователем
wend.


ЛОГИКА В ИГРЕ
Весь игровой процесс основан на логическом операторе if – что означает «если».
Полностью конструкция имеет вид:

if (если) то-то (условие)
then (тогда) делать то-то (действие)
else (иначе) то-то.

Часто применяют короткую его версию

If ... then...

Без логики трудно представить игру. Например когда бита столкнется с шаром мы хотим услышать звук .
В DiNGS это будет выглядеть примерно так:

If collision=1 then Playsound 0,0

Если произошло столкновение тогда проиграть звук. Подробнее читайте ниже.

УПРАВЛЕНИЕ В ИГРЕ
Представить игру без управления объектами невозможно. Но здесь все достаточно просто.
В DiNGS есть функция

KEY[номер клавиши]


которая может принимать значение 1 или 0.
1 – соответственно при нажатии и 0 – при всех остальных случаях. Узнать код клавиши можно с помощью идущей вместе с DiNGS утилитой keycodes. При запуске ее открывается окно, где при нажатии клавиш вы видите ее код.
Управление битой можно написать следующим образом:

If KEY(200)=1 THEN bita_Y=bita_Y-1

Если нажата клавиша «стрелка вверх» тогда уменьшить координату Y объекта бита на 1.
А что если необходимо выполнить несколько действий при нажатии?
Синтаксис будет иметь вид:

If KEY(200)=1 //если клавиша «стрелка вверх» нажата
bita_Y=bita_Y-1 //тогда уменьшить у биты координату Y
PLAYSOUND 0,0 //Проиграть звук
ENDIF //конец оператора IF

А если нужно проверить несколько условий? Есть логические операторы

AND и OR – «и» и «или».

IF (KEY(200)=1) AND bita_Y<=0
Bita_Y=0
ENDIF

В данном случае очень важно выражение <= (меньше или равно). Если бы мы указали просто равно, то это условие среагировало только когда координата bita_Y стала равна 0. А этого вполне могло не случиться , если приращивать значение bita_Y не по одному (что в играх очень часто применяется).
Надеюсь все выше перечисленное на заставило вас приуныть, т.к. мы все это рассмотрим на более понятных и доступных примерах. А волшебство языка DiNGS заключается как раз в том, что написав всего несколько строчек мы можем получить работоспособную игру. Но прежде чем мы приступим к практической части имеет смысл рассказать из чего же состоит игра.

СТРУКТУРА ИГРЫ
Любая игра начинается блоком инициализации. Присваиванием координатам объектов конкретных значений (хотя это может произойти и позднее), обнуление различных счетчиков, установка логических значений для флагов и тд.
Далее идет цикл в котором и вертится наш код создавая игровые ситуации.
В цикле мы выводим графику, обрабатываем столкновения спрайтов, отображаем очки заработанные игроком.
Здесь же реализуются возможности управления игровыми объектами – пусть той же битой или космическим кораблем.
Итак, для игры нам нужны картинки, звуки, музыка и желание этим заниматься.


Пример игры написанной на DiNGS

Наступает непосредственно самое интересное – практическое создание игры.
Для начала определимся, что за игру мы хотим написать. С одной стороны если есть желание создать игру в которой бита будет отбивать шарик – это конечно здорово, но вряд ли кто усидит за ней более 5 минут. Поэтому немаловажное значение имеет изюминка. Это может быть потрясающий научно-фантастический сюжет или расширенные возможности, множество призов, красочные уровни, нелинейность прохождения. Возможно вы сразу захотите создать платформенный шедевр в духе Super Mario или Sonic – ничего в этом невероятного нет. Сложность создания данных игр примерно одинакова. Все зависит от задумки. Единственное, что хотелось бы сказать – начинать нужно с малого, постепенно добавляя навороты. Пересмотрев не одну тысячу игр с различных игровых систем я сделал вывод, что всегда вторая часть превосходит первую в плане графики и возможностей, что и не удивительно – программист-художник имея опыт создания всегда желает улучшить свое творение. Поэтому даже если первая версия вашей игры не внушит вам восторга – это значит только что все еще впереди.
После некоторых размышлений я пришел к выводу , что начать учится программированию можно даже казалось бы с такой простой игры, как Пинг Понг. Она понятна для объяснения начинающим и содержит многие нюансы и тонкости программирования игр.
Кому этого может показаться мало, я всегда могу продолжить данную статью с описанием программирования платформенных или бет’ем’ар игр в духе “Golden Axe”, “Street of Rage” или “Donkey Kong Country”.
Итак , Пинг Понг.
Две биты с разных сторон экрана отбивают мяч летящий по некоей траектории. Бита пропустившая мяч проигрывает (теряет жизнь, очки и тд).
Казалось бы банальность, но все можно преобразить! Подключим воображение!
Игра против человека или компьютера, разнообразные уровни и графика. Ужасные босы. Призовые уровни дающие возможность заработать немного виртуальных денег. Призы. Различная сила отбива ракеткой-битой (вплоть до разрушения вражеской , если та не защищена … например защитным полем (!). Магазины с покупкой в них различных приспособлений (усилителей удара, ускорителей перемещения, защитных полей, жизней, обманных траекторий мяча и тд.) Уникальные музыкальные темы. Высокое разрешение, графические эффекты – прозрачность, вращение, скалинг!!!
Вот здесь уже высока вероятность оставить непередаваемое ощущение у играющего и желание возвращаться снова и снова к пройденным уровням. Такова и есть написанная мной Ping Pong Deluxe (Gold), которая к моменту, когда вы дочитаете статью и напишите свой первый игровой шедевр будет доступна для закачивания с sourceforge.net и мы сможем сравнить результаты.
Игра состоит у нас пока из 2-х бит и шарика, который мы будем отбивать. Следовательно наш путь лежит в любой графический редактор имеющий достаточно функций для наших целей (GIMP, Photoshop и тд).


Пример игры Ping Pong

Нарисовать биту я думаю не составит труда у большинства читающих – но как не трудно заметить – вокруг биты осталось свободное фоновое пространство. Нам необходимо сделать его прозрачным для DiNGS.
Для этого залейте его цветом

R:255
G:0
B:128


Немаловажно 2 фактора – картинка должна остаться в RGB (иметь не менее 65000 цветов), а не в индексированной палитре и иметь формат bmp. А также стороны дожны быть кратны и делится друг на друга. Например соотношение сторон картинки 32х96 правильно (делится без остатка) и будет корректно отражено (это все из-за тонкостей использования OpenGL спрятанного под командами DiNGS). А вот 80х60 будет отображен неправильно (иметь мусор на картинке), т.к. стороны не делятся без остатка.
Итак мы нарисовали 2 биты и шарик. Что же , пора заняться программированием.

Запустим редактор DiNGS- editor.
Перед нами синее окно в верхней части которого расположились пункты – открыть файл, сохранить файл, компилировать файл и запустить исходник на выполнение. Тонкость заключается в том, что прежде чем запустить исходник – его необходимо превратить в exe-файл , т.е. скомпилировать.


Вид редактора DiNGS

Начинаем программировать.
Зайдем в меню редактора Dings – file->new или щелкнем на иконке с изображением чистого листа. Перед нами лист в который мы будет набивать исходник программы.

ЗАДАЧА 1
Вывести спрайт (картинку) ракетки-биты на экран.
Теоретически можно бы было дать команду считывающую изображение с диска и отображающую ее на экран монитора. Но если наш объект двигается, то команда все время будет считывать объект с диска , чтобы отобразить его в новых координатах , что приведет просто к фатальному падению производительности.
Поэтому так уже давно никто не пишет. Самый стандартный метод – загрузить спрайт(ы) в память , а потом уже молниеносно выводить его из памяти на экран.
В DiNGS для этого предусмотрены 2 команды.

LOADSPRITE “картинка”, num

Где «картинка» - это спрайт в формате bmp (например “bita.bmp”), а num – номер по которому спрайт будет хранится в памяти. По этому номеру мы сможем получить доступ к спрайту для вывода его на экран. Нумерация осуществляется от 0 и так далее.
Пример использования команды (загрузим спрайт bita.bmp в память под номером 0, загрузим спрайт шар – ball.bmp в память под номером 1 и тд):

LOADSPRITE “bita.bmp”, 0
LOADSPRITE “ball.bmp”, 1

Итак мы загрузили спрайты в память, но чтобы увидеть их необходимо вывести спрайты на экран. Для этого используется команда DiNGS:

SPRITE num, x , y , a

Где num – номер по которому мы помещали спрайт в память (у нас 0 – это бита (см. выше, а 1 - шар)).
X – координата спрайта на экране по оси Х.
Y – координата спрайта на экране по оси Y.
A – степень прозрачности (!!!) спрайта. Может быть от -100 до +100!
Например разрешение нашей игры будет 640х480, а мы хотим вывести спрайт шара в центр экрана, тогда соответственно половина от ширины 640 будет 320 , а половина от высоты 240.
Пример использования команды SPRITE (вывод спрайта на экран):

SPRITE 0, 200,350,0
SPRITE 1, 320,240,-50

Первая команда выводит спрайт №0 , а это у нас бита на экран в координаты 200 по Х и 350 по Y с прозрачностью 0 (то есть отсутствующей).
Вторая команда выводит спрайт №1 , а это у нас шар в середину экрана в координаты 320 по Х и 240 по Y с прозрачностью - 50 (то есть шар полупрозрачен!).
Остался последний штрих. Даже если вы все написали как было описано выше – вы ничего не увидите! Для этого необходимо дать команду

SHOWSCREEN

– что значит «показать».
Вот текст первой нашей программы:

LOADSPRITE “bita.bmp”, 0
LOADSPRITE “ball.bmp”, 1
SPRITE 0, 200,350,0
SPRITE 1, 320,240,-50
SHOWSCREEN
MOUSEWAIT
END

Сохраним ее на диске зайдя в пункт меню File -> Save As и в открывшемся окошке дав нашей программе имя, например – game. Файл автоматически получит расширение SRC – game.src - что является стандартным расширением для программ на языке Dings, как programm.cpp – для Cи++ или programm.pas для Pascal.
Теперь зайдем в пункт меню Project -> Options и выберем разрешение для игры (в нашем случае 640х480) и поставим галку Full Screen – отображение игры во весь экран.
Нажмем F8 чтобы скомпилировать программу (превратить в машинные коды).
Нажмем F5 чтобы выполнить скомпилированную программу.
Если вы сделали все в точности как описано выше вы увидите на экране биту и шарик.
Щелкните кнопкой мышки чтобы завершить программу.
Еще раз приведем текст нашей будущей игры с комментариями:

LOADSPRITE “bita.bmp”, 0 // загрузить картинку биты в ячейку № 0
LOADSPRITE “ball.bmp”, 1 // загрузить спрайт шара в ячейку № 1
SPRITE 0, 200,350,0 // вывести спрайт биты в координаты 200,350
SPRITE 1, 320,240,-50 // вывести шар в координаты 320,240 полупрозрачным
SHOWSCREEN // Показать все на экране монитора
MOUSEWAIT // Ждать щелчка мыши
END // Конец программы

Если не указать команду MOUSEWAIT , то программа выведет картинки на экран и завершится, причем все произойдет с такой скоростью, что вы ничего не увидите.
Здесь же мы загружаем спрайты в память командой LOADSPRITE, выводим их в нужные координаты командой SPRITE и показываем на экране командой SHOWSCREEN после чего ждем нажатия мыши MOUSEWAIT.
Подсказка: часто чтобы вывести какой то объект в центр экрана создают 2 переменных
max_X и maxY и присваивают им максимальные значения экрана – 640 и 480.
Вывод же спрайта в середину экрана будет иметь вид

SPRITE 1, max_X/2, max_Y/2,-50

,что избавляет программиста от необходимости высчитывать сколько будет 640/2. Это конечно примитивный случай, но в игре могут использоваться очень серьезные вычисления и такой подход существенно облегчит ваш труд.
Первый результат получен, но впереди самое интересное. Давайте заставим двигаться игровые объекты!
В двух словах это можно сказать так:

1. Вывести спрайт в координаты x и у.
2. Изменить значение координаты х для движение по горизонтали, y – для движение по вертикали и оба если хотим получить движение по диагонали.



То же самое на языке DiNGS:

SPRITE 0, x,350,0 //выводим спрайт
x=x+1 //изменяем координату х для движения по горизонтали

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

Метка:
Вывести спрайт
Увеличить координату Х
Перейти на метку Метка

Для цикла используем оператор

While ... Wend.

Что аналогично меткам, но более удобно. Многие программисты почему то избегают пользоваться оператором GOTO – им кажется , что это несерьезный оператор для начинающих, который профессионалу не подойдет (пользоваться им зазорно, это для ламеров и тд). Мне кажется пользоваться нужно и можно любыми операторами если они облегчают программирование. Иногда проще использовать одну метку и оператор GOTO , чем писать кучу ненужного кода. Не могу не согласится с утверждением , что частое , неумеренное использование меток и GOTO ведет к загромождению и запутанности кода , но совет один – пользоваться всем нужно в меру – и от сахара зубы болеть могут.
В качестве условия выхода зададим клавишу ESC (код 1 - выяснить это можно запустив утилиту keycodes и нажав клавишу ESC).

Конструкция выглядит так:

While KEY(1)=0 //продолжать пока не нажата клавиша ESC
Вывод спрайтов
Изменение координат и тд.
WEND

Заставим двигаться картинку шара по экрану. Полный текст игры:

LOADSPRITE “bita.bmp”, 0 // загрузить картинку биты в ячейку № 0
X=100 //присвоим значение переменной Х
WHILE KEY(1)=0 //начало цикла игры – ее сердце
SPRITE 0, Х,350,0 // вывести спрайт биты в координаты
X=X+1 // увеличение координаты Х на 1
SHOWSCREEN // Показать все на экране монитора
WEND // переход в начало цикла к оператору WHILE
END // Конец программы


Здесь хотелось бы обратить внимание на новую операцию, которую до этого момента мы не применяли – присвоение значений переменным – инициализацию переменных. В данном случае переменной Х мы присвоили значение 100 прежде чем она была использована в программе оператором SPRITE.
Что произойдет если этого не сделать?
Переменная Х пока не инициализирована может содержать любые числа.
Что бы наша бита оказалась при отображении на экране именно в координате по Х равной 100 мы и инициализировали заблаговременно Х значением 100.
Инициализацию – присвоение значений переменным – желательно всегда производить в начале программы.
Настало время сохранить программу на диск. Скомпилировать по F8 и запустить по F5.
Бита движется! Впрочем ничего удивительного в этом нет.
А как заставить ее двигаться быстрее?
Вопрос простой, но не все знают ответ.
Увеличивать переменную Х не на 1 , а не большее значение.
Например:

X=X+5

Увеличиваем значение сразу на 5!
А как замедлить движение?
Даже когда приращение переменной осуществляется всего на единицу скорость может для какого-то объекта в игре (например облака) оказаться слишком высокой.
Что может быть медленнее единицы?
Единица деленная на 2, на 3, на 4 и тд!!!
Х=Х+1/2
А если нужно еще медленнее?
Об этом я расскажу ниже, когда мы затронет вопрос тригеров и счетчиков в игре. Но можете не волноваться – замедлить игровой объект можно до любой степени!
Запустив программу мы некоторое время наблюдаем движущуюся биту , которая неспешно исчезает за экраном. Занавес.
А вспомните лифты, движущиеся вверх-вниз платформочки в таких замечательных приставочных играх , как Super Mario, Contra, Duck Tales?
Вам интересно как это сделать?
Тогда настало время рассказать о триггерах в игре (терминология автора).

ТРИГГЕРЫ
Триггер – выключатель содержит несколько положений, в простейшем случае 2 – включено-выключено. Или 0 и 1.
Эксперименты продолжим на нашей проверенной бите. Или можете нарисовать лифт если захотите прочувствовать всю важность момента.
Как реализовать движение лифта.

1. Увеличивать кординату Y пока она не достигнет нужного нам значения.
2. Уменьшать координату Y пока она не достигнет нужного нам значения.

Смысл верен , но работать ничего не будет. Поэтому данная ошибка будет нами подробно рассмотрена.
Пусть лифт (бита) будет двигаться по Y от 10 до 200.
Пишем:

If Y<=200 then Y=Y+1
If Y>=200 then y=y-1

На самом деле бита достигнув значения 200 одновременно начнет и прибавлять и вычитать единицу. То есть перестанет двигаться.
Для реализации правильного алгоритма нам потребуется переключатель (переменная) – триггер.
Создадим переменную UP – она у нас будет получать значение 1 или 0.
Если значение UP будет равно 1 - движение вверх разрешено – если 0 – движение вниз разрешено.

ПОДПРОГРАММА ДВИЖЕНИЯ ЛИФТА

If UP=0 then Y=Y+1
If UP=1 then Y=Y-1
If Y<=10 then UP=0
If Y>=200 then UP=1

Вот так правильно. Только не забудьте инициализировать в самом начале программы значение UP. Например еще до начала цикла While вписать строчку UP=0 иначе лифт не двинется.
Рассмотрим более подробно приведенную выше программу.

If UP=0 then Y=Y+1 //если движение вниз разрешено увеличить Y
If UP=1 then Y=Y-1 //если движение вверх разрешено уменьшить Y
If Y<=10 then UP=0 // Если Y меньше или равно 10 тогда движение вверх запретить (движение вниз разрешить)
If Y>=200 then UP=1 // Если Y больше или равно 10 тогда движение
вниз запретить (движение вверх разрешить)

Применение данного алгоритма не ограничивается только движением лифта – это и имеющие переменную яркость фонари в игре и меняющие степень прозрачности объекты – пусть те же облака реагирующие на порывы ветра – которые тоже могут быть реализованы триггерами. Но для чего все это объяснять когда мы пишем какой-то там примитивный Ping Pong?
Как в простейшем случае реализовать алгоритм полета мяча?
Задать ему движение по диагонали с отскоком от краев экрана.
Такой подход применен в множестве известных игр. Те же триггеры!
Давай те же сделаем это и мы.

1. Зададим движение мяча по диагонали – одновременно увеличивая координаты X и Y.
2. Если мяч достигнет верхнего края экрана (координата Y=0) запретить движение вверх (разрешить движение вниз).
3. Если мяч достигнет левого края экрана (координата Х=0) запретить движение в лево (разрешить движение в право).

Мы описали действия для двух сторон экрана.
Аналогичные действия произведем и над оставшимся нижним и правым краями экрана:

2. Если мяч достигнет нижнего края экрана (координата Y=480) запретить движение вниз (разрешить движение вверх).
3. Если мяч достигнет правого края экрана (координата Х=640) запретить движение в право (разрешить движение влево).

На языке DiNGS это будет выглядеть как:
// описываем что делать при определенных положениях триггеров UP и LEFT

If UP=1
Y=Y-1
ENDIF
If UP=0
Y=Y+1
ENDIF
If LEFT=1
X=X-1
ENDIF
If LEFT=0
X=X+1
ENDIF
// Описываем условия переключающие триггеры.
IF X<=0 THEN LEFT=0
IF X>=640 THEN LEFT=1
IF Y>=480 THEN UP=1
IF Y<=0 THEN UP=0

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

LOADSPRITE “ball.bmp”, 0 // загрузить картинку мяча в ячейку № 0
LOADSPRITE “bita.bmp”, 1 // загрузить картинку биты в ячейку № 1
// задаем первоначальное направление полета – влево вверх
UP=1
LEFT=1
// задаем первоначальные координаты мяча
Ball_X=640/2 //середина экрана
Ball_Y=240 //середина экрана
// НАЧАЛО ИГРЫ
WHILE KEY(1)=0 //начало цикла игры – ее сердце
SPRITE 1, 600,240,0 // вывести спрайт биты
SPRITE 0, Ball_Х,Ball_Y,0 // вывести спрайт мяча (шара)
If UP=1
Ball_Y=Ball_Y-1
ENDIF
If UP=0
Ball_Y= Ball_Y+1
ENDIF
If LEFT=1
Ball_X= Ball_X-1
ENDIF
If LEFT=0
Ball_X= Ball_X+1
ENDIF
// Описываем условие переключающее триггеры.
IF Ball_X<=0 THEN LEFT=0
IF Ball_X>=640 THEN LEFT=1
IF Ball_Y>=480 THEN UP=1
IF Ball_Y<=0 THEN UP=0
SHOWSCREEN // Показать все на экране монитора
WEND // переход в начало цикла к оператору WHILE
END

Теперь давайте заставим двигаться биту под нашим управлением с клавиатуры и …
Отбивать шарик! Для этого нам следует более подробно уяснить понятие – коллизия. Но для начала добавим в игру красивый фон. Уж какая игра без фона!

ЗАГРУЗКА ФОНА В ИГРЕ

LOADBMP “fon.bmp”


Фоном может служить любая картинка. Чтобы она покрывала весь экран размер должен соответствовать 640х480. Что на ней изобразить зависит только от девелопера данной игры.
Команду LOADBMP необходимо поставить до начала цикла While ... Wend
В итоге получится уровень игры с симпатичным фоном, но… Я как раз не рекомендовал бы использовать фон таким образом. На мой взгляд его лучше сделать спрайтами, чтобы иметь возможность оживить, придать динамизм, движение.

КОЛЛИЗИЯ
Коллизия – столкновение спрайтов. В DiNGS за это отвечает оператор

BOXCOLL

В качестве аргументов BOXCOLL получает значение 1 если между двумя указанными объектами произошло столкновение и 0 – если столкновения небыло.

BOXCOLL col, ballX, ballY, 16, 16, bitaX, bitaY, 20, 50

Col = переменная при столкновении объектов получающая значение 1.
ballX – координата Х первого объекта - мяча
ballY - координата Y первого объекта - мяча
16 - размер объекта по Х
16 - размер объекта по Y
То есть мяч имеет размер 16 на 16 пикселей. Это можно узнать открыв картинку в графическом редакторе (Photoshop, GIMP) и посмотрев «размер изображения».
bitaX - координата Х второго объекта - биты
bitaY - координата Y второго объекта - биты
20 - размер биты по Х
50 – размер биты по Y
Разместив BOXCOLL в цикле мы обеспечим постоянную проверку на столкновение 2-х объектов – биты и мяча. Но как этим воспользоваться?
Прочитать значение переменной col и в соответствии с ее значением произвести определенные действия – изменить направление полета мяча и тд.
Пример:

IF col=1 // если произошло столкновение
PRINT “COLLISION YES”, 320, 240 //Вывести надпись: Collision yes
ENDIF

Давайте напишем программу , где мяч будет отлетать от стен, а мы его к тому же сможем отбивать битой. Если мы пропустим мяч, то потеряем одну из 3-х жизней.
При потере последней жизни игра завершится выведя известную всем игрокам надпись
«GAME OVER».
Это уже действительно игра, в которую можно играть и она имеет смысл.
Итак, начало:

//Инициализируем переменные
LIVES=3 //жизней 3
Minus_Lives=0 //обнуляем триггер
//Загружаем спрайты в память
LOADSPRITE “ball.bmp”, 0 // загрузить картинку мяча в ячейку № 0
LOADSPRITE “bita.bmp”, 1 // загрузить картинку биты в ячейку № 1
// задаем первоначальное направление полета – влево вверх
UP=1
LEFT=1
// задаем первоначальные координаты мяча
Ball_X=640/2 //середина экрана
Ball_Y=480/2 //середина экрана
// задаем первоначальные координаты биты
Bita_X=600 // у правого края экрана
Bita_Y=480/2 //середина экрана
//Загрузим фоновую картинку в игру размером 640х480
LOADBMP “fon.bmp”
// НАЧАЛО ИГРЫ
WHILE KEY(1)=0 //начало цикла игры – ее сердце
SPRITE 1, Bita_X,Bita_Y,0 // вывести спрайт биты
SPRITE 0, Ball_Х,Ball_Y,0 // вывести спрайт мяча (шара)
//Проверяем мяч и биту на столкновение
BOXCOLL Сol, Ball_X, Ball_Y, 16, 16, Bita_X, Bita_Y, 20, 50
// Если столкновение произойдет, то переменная Col станет равна 1
//УПРАВЛЕНИЕ БИТОЙ В ИГРЕ
IF KEY(200)=1 THEN Bita_Y=Bita_Y-1 //если нажата клавиша – уменьшить координату биты по Y
IF KEY(205)=1 THEN Bita_Y=Bita_Y+1 // увеличить координату биты по Y
//Описываем условия по которым движется мяч
// Если вверх, то уменьщать Y
If UP=1
Ball_Y=Ball_Y-1
ENDIF
// Если вниз, то увеличивать Y
If UP=0
Ball_Y= Ball_Y+1
ENDIF
// Если влево, то уменьшать Х
If LEFT=1
Ball_X= Ball_X-2
ENDIF
// Если вправо, то увеличивать Х
If LEFT=0
Ball_X= Ball_X+2
ENDIF
// Описываем условие переключающее триггеры.
IF Ball_X<=0 THEN LEFT=0
IF Ball_X>=640 //если мяч упустили
Minus_Lives=1 // задействуется спец-триггер
ENDIF
IF Ball_Y>=480 THEN UP=1
IF Ball_Y<=0 THEN UP=0
//Действия при срабатывании спец-триггера
IF Minus_Lives=1 // если сработал триггер
LIVES=LIVES-1 // уменьшить количество жизней
PRINT “Live destroy”,640/2,480/2 //Вывести надпись – «Жизнь уничтожена» в середину экрана
PRINT “Press any key for Continue”, 640/2, 520 //Вывести надпись: «Нажми клавишу мыши для продолжения игры»
MOUSEWAIT // Игра остановится до щелчка мышью
//Щелчок произведен, сбросим мяч в середину экрана
Ball_X=640/2
Ball_Y=480/2
LEFT=1 //пусть начнет двигаться удаляясь от биты
Minus_Lives=0 //обнулим триггер
ENDIF
//Опишем ситуацию когда жизни кончились
IF LIVES<=0
PRINT “GAME OVER”, 320,240 // Надпись: «Игра окончена»
PRINT “YOU LUSER”, 320,300 // Надпись: «Ты проиграл»
PRINT “PRESS MOUSE” // Надпись: «Нажми клавишу мыши»
MOUSEWAIT //ожидание нажатия мыши – требуется для того, чтобы игрок смог прочитать надписи на экране
GOTO GAME_END //Прыгнуть на метку GAME_END для выхода из игры
ENDIF
// Описываем условие переключающее триггеры при столкновении с битой
if Col=1 //Если есть столкновение
LEFT=1 // разрешить движение влево
Col=0 // переменную Col обнулить
ENDIF // конец IF
SHOWSCREEN // Показать все на экране монитора
WEND // переход в начало цикла к оператору WHILE
metka GAME_END:
END

С натяжкой вышеприведенный текст можно назвать игрой. Мне пришлось несколько искусственно решить некоторые проблемы, т.к. я еще не объяснял вам, что такое счетчики. Поэтому я применил оператор MOUSEWAIT. Метки были введены в текст в качестве иллюстрации их использования – как раз та самая золотая середина, когда мы ими не злоупотребляем. Хотя справедливости ради стоит отметить, что на самом деле проще задать выход из игры при потере последней жизни в условии оператора WHILE...WEND.
Может также вызвать недоумение, почему это мы вдруг стали увеличивать координату X сразу на 2: «Ball_X=Ball_X+2», в то время как Ball_Y лишь на единицу.
Дело в том, что меняя число приращения мы можем изменять угол полета мяча.
Для любопытных и экспериментаторов упомяну, что если задать изменение этих величин в игре динамически, то мы можем получить очень интересные траектории движений. Впрочем об этом можно будет прочесть ниже. Помимо простейших траекторий вы сможете узнать какая замечательная вещь для полетов мяча – синусы. Они используются практически во всех играх и позволяют получать очень красивые эффекты и траектории.
Наконец мы подошли к одной из интереснейших глав в этом обучении – АНИМАЦИИ!!!
Настало время заставить ожить мир нашей игры. Добавим анимированные объекты движущиеся по своим траекториям и делающим полет мяча непредсказуемым, т.к. сталкиваясь с ними мяч изменяет направление полета.

АНИМАЦИЯ
– последовательность картинок создающих при быстром просмотре иллюзию движения.
Выберем объект , который и анимируем. Например бабочку.
Нарисуем первое изображение – бабочка с распахнутыми крыльями.
Если размер первой картинки равен , например, 100х100 , то и все следующие должны иметь размер 100х100.
Рисуем вторую картинку, где крылья уже немного сместились к центру.
Рисуем третью картинку, где крылья еще сильнее сместились к центру.
Рисуем четвертую картинку, где крылья практически невидно на фоне тела.
Для примитивной анимации вполне достаточно 4-6 кадров, но мы то не собираемся плодить дерганный ужас вместо высококлассной анимации (ведь так?), поэтому мой совет: чем фаз анимации больше, тем лучше. Например для супергероя из платформенной игры желательно только на анимацию ходьбы потратить не менее 16 кадров. Это долго и несколько утомительно, но результат вас действительно порадует. Герой будет прекрасно анимирован и можно гордо написать: в игре ультраплавная анимация. Не забываем фон картинки, который в игре не должен быть виден заливать цветом R:256 G:0 B:128.
Принцип анимации прост:

1. Показать изображение 1.
2. На том же месте долю секунды спустя показать изображение 2
3. И тд.
4. Вернуться к пункту 1.

Что ж, и мы напишем:

// Загрузим графику в память
LOADSPRITE “babochka.bmp”,0
LOADSPRITE “babochka2.bmp”,1
LOADSPRITE “babochka3.bmp”,2
LOADSPRITE “babochka4.bmp”,3
// счетчик для номера картинки
i=0
// Начало игры
While Key(0)=1
SPRITE i, 320, 240, 0 //Вывести спрайт i в координаты 320х240.
i=i+1
IF i=4 THEN i=0
SHOWCREEN
WEND
END

Вывести спрайт i в координаты 320х240.
Увеличить i.
Если i=4, а у нас расположение картинок бабочки в памяти находиться только от 0 до 3, то снова присвоить i значение 0.
То есть создаем бесконечный цикл.
Вообще прежде чем начать писать программу продумайте где какие данные будут храниться.
Например фон, биты и шарика пусть хранятся в номерах от 0 до 50.
Призы – в номерах от 51 до 80.
Другие данные в номерах от 80 и выше. Нет никаких указаний, что нужно картинку с бабочкой располагать именно в номере памяти – 0. Можете расположить ее в номере 40, к примеру, но тогда строчка инициализации изменится на i=40, а строчка кода в блоке анимации на IF i=44 THEN i=40.
Действительно мы анимировали картинку, но не сложно заметить - скорость с которой демонстрируется анимация превосходит все мыслимые скорости и пределы. Действительно все слишком быстро.
Необходимо замедлить. Но как?
Настало время рассказать о такой замечательной вещи, как счетчики. Благодаря им мы можем замедлить анимацию или движение и тд.
В качестве упражнения добавьте в свою игру анимированную бабочку и назначьте ей движение через экран по алгоритму лифта.
Итак, счетчики…

СЧЕТЧИКИ
Создать игру без счетчиков можно, но… Есть причины, по которым это крайне не желательно!
Счетчики применяются, чтобы:
игра с одинаковой скоростью шла на Celeron 266 и на P4 3000Mh;
анимация разных героев в игре различалась по скорости;
перемещение объектов происходило с определенной скоростью;
чтобы, к примеру , именно на 20 секунде первого уровня пошел дождь или поднялся ветер в игре.
На самом деле применение счетчиков еще более пространно, но остановимся на этом.
При анимации бабочки мы видим - скорость анимации велика. Для замедления ее используются счетчики.
Посмотрим использование счетчиков на примере:

//Блок инициализации переменных
Speed_anim=10 //скорость анимации. Число подбирается экспериментально.
I=0 //счетчик кадров анимации
//Загрузка графики
LOADSPRITE “babochka.bmp”,0
LOADSPRITE “babochka2.bmp”,1
LOADSPRITE “babochka3.bmp”,2
LOADSPRITE “babochka4.bmp”,3
// Начало цикла
WHILE TRUE //повторять вечно. На самом деле выход по ESC
SPRITE i, 320, 240, 0 //Вывести спрайт i в координаты 320х240.
IF Speed_anim=10 //если скорость анимации =10
i=i+1 // показать следующий кадр
Speed_anim=0 //сбросить счетчик анимации в 0
ENDIF
Speed_anim=Speed_anim+1 //увеличить счетчик анимации
IF i=4 THEN i=0 //см. предыдущий пример
SHOWSCREEN
WEND
END

Действительно, мы теперь можем изменять скорость анимации.
В нашей программе крутится счетчик Speed_anim. Он циклически изменяется от 0 до 10. Этого мы достигаем конструкцией вида Speed_anim=Speed_anim+1.
Когда счетчик достигает значения 10 , срабатывает логический оператор IF.
IF Speed_anim=10
если скорость анимации = 10 , тогда увеличиваем номер кадра картинки для отображения следующего спрайта в анимации и обнуляем счетчик Speed_anim, чтобы он опять начал потихоньку приращиваться на 1.
В итоге изменение картинки на экране происходить только когда счетчик Speed_anim достигает значения 10. Если скорость по прежнему достаточно высока, подставьте в место 10 – 20 или даже 50. Скорость упадет.



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

Пример 1.
Вывести в середину экрана спрайт (разрешение экрана 800х600):
SPRITE 1,400,300,0
Такой стиль написания кода не сулит ничего хорошего и требует от вас вычислений. Этого можно избежать введя в начале программы 2 переменные содержащие максимальное значение экрана:
Max_X = 800
Max_Y = 600
Теперь запись верхнего примера будет выглядеть следующим образом:

SPRITE 1, Max_Х/2, Max_Y/2,0

Dings сам вычислит значение середины экрана поделив максимальные его значения на 2.
Если вы измените разрешение экрана - вам не придется искать по всему тексту координаты для изменения, достаточно только изменить значения Max_X и Max_Y в начале программы.
Если вы часто используете координаты середины экрана - их тоже можно выделить в переменные.

Max_X = 800
Max_Y = 600
Center_X = Max_X/2
Center_Y = Max_Y/2

SPRITE 1, Center_X, Center_Y,0

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

Max_speed_anim=30
...
IF Speed_anim=Max_speed_anim //если скорость анимации =30
i=i+1 // показать следующий кадр
Speed_anim=0 //сбросить счетчик анимации в 0
ENDIF
...
Как видно - достаточно изменять значение Max_speed_anim в блоке инициализации и мы повлияем на скорость анимации.
Применительно к замедлению движения какого либо объекта:
. . .
WHILE TRUE //повторять вечно. На самом деле выход по ESC
SPRITE i, x, y, 0 //Вывести спрайт i в координаты 320х240.
IF Speed=20 //если счетчик =20
x=x+1 // увеличить координату Х
Speed=0 //сбросить счетчик анимации в 0
ENDIF
Speed=Speed+1 //увеличить счетчик анимации
SHOWSCREEN
WEND
END
. . .
Вы уже можете создавать достаточно сложные игровые миры, но пока не хватает некоторых элементов. Во первых ни одна игра не обходится без такой замечательной вещи, как массивы, а во вторых пора рассмотреть, что же такое подпрограммы и функции и как они облегчают программирование.

МАССИВЫ
Массив – это набор однотипных данных объединенных под одним именем. Представьте стол со множеством ящиков. Нужная книга лежит во втором ящике стола. Чтобы добраться до нее, необходимо в столе открыть второй ящик:
//Объявление массива «стол» состоящего из 10 ящиков
Dimm stol[10]
//Положить какие-то данные (например число 6) во второй ящик:
stol [2]=6
// Взять данные из второго ящика (или образно - книгу):
наша переменная =stol [2]
Использование массивов в программировании игры бывает просто необходимо и избавляет от создания большого количества кода. Иногда использование массивов – единственный выход дающий возможность увеличить быстродействие! Программируя игру «PINGPONG GOLD» мне потребовалось в одном из уровней создать объемное изображение звездного неба. Помимо множества движущихся объектов и эффектов скалинга и ротации, многочисленных скролеров, получив порцию в виде трех наложенных друг на друга прозрачных карт неба находящихся в движении мой Атлон 1600 Мн захлебнулся таким объемом вычислений и стал вяло прорисовывать сцену. Виной был и слабенький видео-акселератор Radeon, но нужно исходить из того, что и Атлоны есть далеко не у всех потенциальных игроков. Чтобы сократить объем вычислений я прибег к массивам , создав массив из 50 объектов (звезд), имеющих разную яркость и скорость перемещения и как результат игра пошла необычайно резво забыв всю былую нерасторопность.
Массивы также могут помочь избежать объявления массы переменных. Например при обычном подходе, если мы захотим создать счетчики призовых очков заработанных первым и вторым игроками, нам понадобиться ввести 2 переменные – score_player_1 и score_player2.
Проще создать массив:
//Объявляем массив score из 2-х ячеек. (На самом деле в объявленном массиве 3 ячейки, т.к. можно также помимо ячеек score[1] и score[2] обратиться к ячейке score[0], но это опустим из внимания)
DIM score [2]
. . .
// Где-то в игре…
//Увеличим счет первого игрока на 10
score[1]=score[1]+10
. . .
Другой пример показывает как легко можно создать 50 различных значений координат для 50 объектов!!!

DIM zvezda_x [50]
FOR i=1 TO 50
Zvezda_x[i]= RND (800)
NEXT

Теперь у нас есть 50 различных координат.
Чтобы 50 раз не писать Zvezda_x[1]= RND (800), Zvezda_x[2]= RND (800) и тд. мы воспользовались конструкцией FOR ... TO ...NEXT.
Что означает: ОТ (стольки ) ДО (стольки) действие ПОВТОРЯТЬ.
RND – создание случайного числа из диапазона указанного за оператором в скобках.
Пример:

X= RND(30) //присвоение Х случайного числа из диапазона от 0 до 30.

Практическое использование полученных значений массива

zvezda:
SPRITE 100, zvezda_x[5],100,0

Мы рассмотрели одномерный массив, но бывают и многомерные. Возьмем аналогию с тем же столом. Вы открываете второй ящик стола и видите в нем еще один ящик.
Многомерные массивы объявляются так:
DIM stol[10][2]
Стол с 10-ю ящиками, в каждом из которых находится еще по 2 ящика!
Ниже мы рассмотрим функции, подпрограммы, применим в игре эффекты скалинга и ротации и перейдем к созданию скролеров, которые можно видеть в многочисленных демо!

ПОДПРОГРАММЫ
Если вы следовали точно выше приведенным главам, то не сложно заметить, что ваш код подвержен некоторому беспорядку и с каждой новой строчкой становится сложнее найти что либо в исходном тексте. Все происходит из-за незнания такого удобного средства, как подпрограммы. Благодаря им наш КОД может стать легок для поиска и понимания.
В чем смысл использования подпрограмм?
В тексте есть часть кода, функциональное назначение которого, к примеру, выполнить 10 секундную паузу в игре. По ходу программирования нам приходится вписывать этот кусок в новые и новые части кода.
С использованием подпрограмм все становится намного проще.
Необходимый код мы выносим в подпрограмму, даем ей какое-либо имя (например pause) и теперь при необходимости использования паузы просто пишем pause, что эквивалентно вставке соответствующей части кода.
Код подпрограммы нужно писать выше или ниже основного цикла игры.

Схема:
Инициализация переменных
Начало цикла
Тело игры
Конец цикла
Подпрограммы
Подпрограмма объявляется словом SUB , после чего идет название подпрограммы с двоеточием.
Завершается подпрограмма словом ENDSUB.

Приведем пример подпрограммы увеличивающей координаты мяча Х и Y.

SUB move_ball:
Ball_x=Ball_x+1
Ball_y=Ball_y+1
ENDSUB

Чтобы вызвать эту подпрограмму с названием move_ball необходимо дать команду
GOSUB move_ball;
В Dings можно в меню редактора выбрать пункты Project->NewSUB и редактор сам автоматически создаст в конце программы каркас подпрограммы единожды спросив у вас ее имя.
Приведем пример перемещения мяча по экрана с использованием подпрограмм.

//Загрузка графики
LOADSPRITE “ball.bmp”,0
//Начало цикла игры
WHILE TRUE
GOSUB move_ball; //вызов подпрограммы move_ball
SHOWSCREEN
WEND //конец цикла

// Подпрограмма move_ball
SUB move_ball:
Ball_x=Ball_x+1
Ball_y=Ball_y+1
SPRITE 0, Ball_x, Ball_y,0
ENDSUB

Как можно видеть код стал более нагляден и понятен.
Очень удобно использовать подпрограммы, как уровни игры.
Например:

SUB Level_1

Тело-цикл уровня.
ENDSUB
SUB Level_2
Тело-цикл уровня.
ENDSUB
. . .
Тогда код игры будет выглядеть так:

Инициализация переменных
Загрузка графики
Начало цикла
GOSUB Level_1;
GOSUB Level_2;
GOSUB Level_3;
Конец цикла
Подпрограммы Level_1, Level_2, Level_3.

Тогда изменение или удаление какого-то уровня произойдет в игре легко и безболезненно.
Не используя подпрограммы игру написать можно, но от вас потребуется помнить все изменения , которые вы вводите в код. Подпрограммы же существенно упрощают создание игр. К слову, подпрограммы используются во всех языках программирования, только имеют различные названия. В языке Паскаль они называются процедурами – PROCEDURE, а на С – функциями – FUNCTION, хотя конечно функция как мы рассмотрим ниже , больше чем просто процедура. Это код получающий какие-то данные, изменяющий их и возвращающий новое значение (хотя это и не обязательно. В том же языке С есть функции void не возвращающие никаких значений, что эквивалентно паскалевским процедурам).
Программируя PING PONG GOLD я столкнулся с катастрофическим падением быстродействия при использовании всех задуманных мной в игре спецэффектов. GeForce2 и 4 без проблем по плечу была вся графика с множеством скалинг, альфаблендинг и ротейшен эффектов, а вот мой Radeon 7200 еле шевелился. Как в старой притче – какой бы не был мощный процессор, программист всегда найдет способ загрузить его полностью.
Чтобы у вас не возникло вопросов, как обуздать самые прожорливые эффекты для слабых машин, я и подниму этот вопрос. Вспомните игры созданные для 386-486 компьютеров. Тогда в играх был такой пункт, как выбор качества графики. При выборе MIN вы лишались каких-то эффектов и даже объектов на экране, но получали существенное приращение скорости. При MAX наслаждались полным великолепием графики, если компьютер позволял.
В старые годы , когда я писал на Паскаль + Ассемблер (в такой же связке другими программистами были написаны бесподобные «Охотник на дороге» и «Turrican2») даже 386 компьютер позволял создавать очень красивые и динамичные игры на моем игровом движке написанном благодаря туторам Асфиксии (но он был в 4 раза быстрее аналога из тутора). Но сегодня в нашем распоряжении просто удивительной красоты эффекты (пусть и очень прожорливые) и нет оправдания, если их не использовать.
Итак, ваша игра жутко стала тормозить.
Нужно начать с того, что необходимо найти виновника.
Пусть у нас есть 20 строчек выполняющих загрузку графики.
Комментируем одну из строк, компилируем по F5 и смотрим исчезли ли тормоза.
Нет, продолжаем поиск. Рано или поздно вы выясните, что вот этот спрайт с гигантской планетой, попадая в фокус меняющего прозрачность лунного света и нескольких движущихся прозрачных фонов заставляем компьютер превращаться в черепаху (как и было у меня в PINGPONG GOLD).
Вводим переменную Grafix_MAX.
Если используем графику на максимуме, эта переменная равна 1, на минимуме – 0.
Пример показывающий на практике этот подход:

Grafix_MAX=1
LOADSPRITE “planet.bmp”,10
LOADSPRITE “ball.bmp”,11
LOADSPRITE “bita.bmp”,12
LOADSPRITE “moon.bmp”,13
WHILE TRUE
SPRITE 10,100,150,0
SPRITE 11,150,180,0
SPRITE 12,200,350,0
IF Grafix_MAX=1
SPRITE 13,100,150,0
ENDIF
SHOWSCREEN
WEND
END

Объяснение исходника:
Устанавливаем значение переменной Grafix_MAX в 1.
Загружаем графику.
Отображаем спрайт 10.
Отображаем спрайт 11.
Отображаем спрайт 12.
Если значение Grafix_MAX=1 , тогда показываем еще и спрайт 13.
Если же значение будет другим, то спрайт тормозящей игру планеты moon.bmp показан не будет, что повысит скорость игры.
Далее уже вы можете благодаря такому подходу скомпилировать несколько версий игры – для слабых и мощных компьютеров (или для бесплатной версии игры и коммерческой).
Можете это вынести в меню, где перед началом игры плэйер сам сможет выбирать качество графики.
Можете вынести данные в отдельный файл и тогда он будет подгружаться в игру из вне (типа config.exe в старых играх) и сохранятся также во внешний файл. Об этом я расскажу ниже.
А пока мы переходим к одной из интереснейших тем – игровым ЭФФЕКТАМ. Пора добавить в игру эффекты ротации, что по русски означает – вращение спрайтов.

Эффекты: вращение (rotation) и увеличение-уменьшение
Есть несколько способов достижения какого либо анимационного эффекта. Можно нарисовать 20 спрайтов изображающих вращение колеса или взять один спрайт и применить к нему эффект вращения. В обычных языках программирования это потребовало бы написания большого количества кода достаточной сложности, но в DiNGS его сделать ничего не стоит.
Для этого есть команда ROTOSPRITE. Она требует указать номер спрайта, координаты куда вывести на экран, угол поворота вокруг оси и значение прозрачности.
Для демонстрации нарисуйте в графическом редакторе колесо со спицами или квадрат.
Пример использования:

LOADSPRITE “kvadrat.bmp”,0
ROTOSPRITE 0,200,200,30,0
SHOWSCREEN
MOUSEWAIT

Вы увидите нарисованный вами спрайт на экране в координатах x:200, y:200, но повернутый на 30 градусов!
Это прекрасно и может даже когда-то пригодится при программировании, но такое использование эффекта rotation слишком примитивно и не производит должного впечатления. Мы заставим колесо или квадрат крутиться!
Пример вращения квадрата:

LOADSPRITE “kvadrat.bmp”,0
alpha=0
While TRUE
ROTOSPRITE 0,200,200,alfa,0
alpha=alpha+1
IF alpha>=360 THEN alpha=0
SHOWSCREEN
WEND

Колесо вращается слишком медленно?
Воздействуйте на аlpha для увеличения скорости вращения.
Например так:

alpha=alpha+5

Теперь в использовании эффекта rotation вас может ограничить только фантазия.
Колесо вращается слишком быстро? Но ведь мы уже изучали счетчики! Изменить скорость вам теперь по плечу.

ЭФФЕКТ Скалинг (увеличение-уменьшение).
Скалинг – один из самых используемых в играх эффектов. Его применение невероятно украшает игру. Вы это можете реализовать, как внезапно наезжающую на сцену камеру или увеличение каких-то объектов, как например в увлекательной игре-головоломке Wumbos Adventure написанной создателем языка DiNGS. Скачать ее бесплатную версию (10 уровней) можно с сайта Dream-D-Sign.de и много удовольствия гарантировано. В Wumbos эффект скалинга используется при применении крокодильчиком ключей на дверях. Изображение ключа взлетает вверх постепенно увеличиваясь. В моей игре PING-PONG GOLD подобным образом реализованы монеты вылетающие из биты при столкновении с призом. Монеты плавно улетают вверх уменьшаясь и растворяясь (эффект альфаблендинга!)В Wumbos Adventure также можно увидеть и другие интересные эффекты, но исходные коды к сожалению не прилагаются.
В DiNGS эффект скалинга достигается применением оператора ZOOMSPRITE.
Он требует указать номер спрайта, координаты вывода на экран, на сколько процентов увеличить по оси Y, по оси Х и степень прозрачности.
Увеличение на 100% означает, что размер спрайта изменен не будет, а отобразится таким какой есть.
Увеличение на 200% означает, что размер спрайта будет увеличен в 2 раза.
Увеличение на 50% означает, что размер спрайта будет уменьшен на половину.
Выведем спрайт колеса, но в полтора раза большего по размеру.
Пример использования:

LOADSPRITE “koleso.bmp”,10
ZOOMSPRITE 0,100,100,150,150,0
SHOWSCREEN
MOUSEWAIT

Из-за возможности изменять процент скалинга как по оси Х , так и по оси Y мы можем деформировать объекты. Как вам например отлетающее от машины колесо, которое скачет по дорожке становясь при столкновении с асфальтом овальным и вновь выпрямляясь подскакивая вверх? И все при использовании лишь одного спрайта!
Пример деформации колеса:

LOADSPRITE “koleso.bmp”,10
ZOOMSPRITE 0,100,100,150,80,0
SHOWSCREEN
MOUSEWAIT
Вышеприведенные примеры – примеры статичные, но не сложно придать им динамику.
Пример динамического скалинга:
Scaling=10
number=1
LOADSPRITE “koleso.bmp”,10
WHILE TRUE
ZOOMSPRITE 0,100,100,Scaling,80,0
Scaling=Scaling+numer
IF Scaling>=180 THEN Scaling=10
SHOWSCREEN
WEND

В данном примере колесо будет деформироваться увеличиваясь в ширину и скачком становясь вновь маленьким. Легкий путь избежать скачка – использование триггеров (см. выше). Так же введение переменной number дает нам возможность просто изменять в начале текста ее значение и получать разную скорость увеличения вместо кропотливого поиска нужной строки.
В моем PING PONG GOLD мяч летит увеличиваясь при отскоке от земли и уменьшаясь при падении. Точно также под ним ведет себя тень, но добавочно деформируясь по оси Х.
Использование скалинга не обязательно, но значительно украшает игру. Кроме того такой подход позволяет избавится от зависимости к размеру спрайтов. Создав спрайт любого размера мы всегда можем с легкостью придать ему размер необходимый нам посредством эффекта скалинга. В моем PING PONG GOLD неоднократно изменялись размеры спрайтов в поисках оптимального, но все делалось через скалинг без утомительной перерисовки. Но можно ли совместить эффекты скалинга и ротации? Ниже мы рассмотрим этот интересный вопрос, после чего углубимся в понятие Функции. Что это такое, для чего и в чем ее смысл.

Эффекты: вращение-увеличение
В DiNGS эффекты вращения (ротации) и увеличения-уменьшения (зуминга или скалинга) можно объединить, что делает код игры прозрачней для понимания. Для этого используется команда ROTOZOOMSPRITE, после которой указывается несколько параметров – координаты, угол поворота, размер и степень прозрачности.
Пример:

LOADSPRITE “priz.bmp”,0
WHILE TRUE
ROTOZOOMSPRITE 0, 100,100, 45, 200,0
SHOWSCREEN
WEND

Таким образом мы выведем спрайт в координаты x:100, y:100, повернутым на 45 градусов и размером в двое превосходящим реальный.
На практике такой пример можно применить в игре PING PONG следующим образом: при попадании мяча в биту, та может перевернуться вокруг оси на секунду увеличившись. В игре PING PONG GOLD я использовал эту команду для файрбола (огненного сгустка) вырывающегося из ракетки при подбирании соответствующего приза. Файрбол несся по экрану красиво вращаясь вокруг своей оси. Но о реализации выстрелов (взаимодействии клавиш с подпрограммами) немного ниже, в части 15.
Пример использования ROTOZOOMSPRITE в динамике.

//Загружаем графику
LOADSPRITE “priz.bmp”,0
//Присваиваем переменным значение
a=0 // угол поворота спрайта
b=50 // начальный размер спрайта
triger_size=1 // триггер
WHILE TRUE //Начало цикла
ROTOZOOMSPRITE 0, 100,100, a, b,0 // вращаем-увеличиваем спрайт
a=a+1 // увеличиваем угол поворота
IF a>=360 THEN a=0 // если угол=360 (сделали полный оборот) , тогда а=0
IF b>=200 THEN triger_size=0 //при размере спрайта =>200 обнуляем триггер
IF b<=50 THEN triger_size=1 // при размере спрайта <=50 триггер активизируется
IF triger_size=1 THEN b=b+1 // Обрабатываем положения триггера
IF triger_size=0 THEN b=b-1 // Обрабатываем положения триггера
SHOWSCREEN // Показываем все на экран
WEND

Для плавного увеличения-уменьшения мы опять прибегли к триггерам. Триггер сообщает нам что достигнуто минимально возможное значения , после чего мы принимаем действия связанные с увеличением значений размера спрайта. Достигнут максимального значения триггер опять переключается давая сигнал к уменьшению значения. Можно было бы прибегнуть для достижения аналогичного результата к таким операторам, как синус или косинус, но о них также речь пойдет ниже и тогда ваша игра заблистает удивительными траекториями мячей и призов.
А пока немного отвлечемся и уделим пару минут не менее важному вопросу.
Игрок впервые сел за вашу сногсшибательную игру и… Не может определится какими же клавишами управляется игра. Выход один – лезть в директорию с игрой и искать файлы с описаниями клавиш управления, но… Этого файла может не быть (кто-то стер, как не нужный, вы не писали никакого файла и тд). Вывод очевиден – в игре желательно иметь встроенный HELP (текст подсказки с клавишами управления). Т.е. непосредственно в игре плейер может нажать F1 (стандартная клавиша помощи) и прочитать клавиши управления. Оформление целиком зависит от вас. Это может быть просто черный экран содержащий текст или какая-то картинка. Программируя Ping Pong GOLD я для большей наглядности отсканировал клавиши используемые в игре и на экране при нажатии F1 отображаются на черном фоне фотографические изображения клавиш – такие же , какие вы видите на своей клавиатуре, но снабженные описаниями.
Пример использования клавиши F1 для экрана помощи.

//инициализация переменных
. . .
//Загрузка графики
...
WHILE TRUE
//Тело игры
//Вызов экрана помощи
IF KEY(59) THEN GOSUB help;
SHOWSCREEN
WEND
 
SUB help:
BLACKSCREEN;
WHILE KEY(57)<1
PRINT “HELP SCREEN”,100,0
PRINT “<- UP”, 0,150
PRINT “-> DOWN”,0,200
PRINT “Z - FIRE”,0,250
PRINT “PRESS KEY SPACE FOR EXIT”,0,450
SHOWSCREEN
WEND
ENDSUB

Поясним, как все устроено.

IF KEY(59) THEN GOSUB help;

Если нажата клавиша F1 (код 59), тогда выполнить подпрограмму help.
Подпрограмма help представляет из себя обыкновенный цикл внутри которого выводятся надписи помощи. Цикл повторяется до нажатия клавиши SPACE (код 57), о чем мы и уведомляем игрока соответствующей строкой.
Внимания заслуживает только команда BLACKSCREEN – она затемняет экран, после чего на черный фон выводятся надписи.
Цикл не обязателен. Можно поступить проще:

SUB help:
BLACKSCREEN;
PRINT “HELP SCREEN”,100,0
PRINT “<- UP”, 0,150
PRINT “-> DOWN”,0,200
PRINT “Z - FIRE”,0,250
PRINT “PRESS KEY SPACE FOR EXIT”,0,450
SHOWSCREEN
KEYWAIT
ENDSUB

Результат аналогичен. Теперь можете добавить HELP в ваш код и будущие пользователи видеоигры будут признательны. Игрок погружаясь в игру не должен ломать голову, какие клавиши управления используются. Есть прекрасный способ избежать проблем дав возможность игроку самостоятельно назначать клавиши управления, но об этом читайте ниже в соответствующей главе.

Искусственный интеллект в играх
До этого момента мы предполагали, что в нашей игре есть 2 участника. Но как быть если никого рядом нет, а сыграть в игру хочется? Поэтому с самых первых игр программисты пытались воссоздать разум человека, награждая им компьютерного оппонента. Это получило название – Искусственный Интеллект (АI). AI используется практически во всех играх. Кому то удается приблизится к модели поведения очень близкой настоящему человеку, кому то это не удается. Сегодня мы попытаемся вдохнуть в наш компьютер человеческий разум. Заставим компьютер действовать и мыслить, как человек. Создадим компьютерного соперника. В играх его обычно именуют CPU.
Хотя создать умного соперника задача не простая, но все это не так сложно , как может показаться и очень увлекательно.
Исходя из того, что мы пишем игру-клон PingPong, где бита должна отбивать мяч, напишем искусственный интеллект способный самостоятельно отбивать биту и что самое важное… Иногда промахиваться! Действительно, чисто человеческая черта.
Посадите друга за игру и понаблюдайте за ним, что за действия он выполняет. Можно конечно и самому одновременно играя выступить в роли наблюдателя.
Действие:
мяч летит в нашу сторону.
Реакция:
сдвигаем биту в направлении мяча.
Это самый примитивный вариант искусственного интеллекта.
Пример кода:

BitaY_Player2=ballY;
//Координата биты по Y всегда соответствует координате мяча по Y.

Все.
При таком AI компьютер не может проиграть, он обязательно отобьет мяч и такой вариант не всегда плох. В игре может быть задумано, что при финальной битве с Боссом CPU пропустить мяч не может, но существуют способы другими средствами разрушить ракетку противника.
Но во всех остальных случаях играть против такого соперника будет не интересно.
Заставим компьютерного оппонента поумнеть.
Пример кода:

IF ballY > BitaY_Player2 THEN BitaY_Player2= BitaY_Player2+1
IF ballY < BitaY_Player2 THEN BitaY_Player2= BitaY_Player2-1

Если координата мяча по Y больше чем координата биты по Y, тогда увеличить координату Y для биты. Тоже самое проводим при варианте если Y мяча меньше Y биты.
Теперь уже компьютер может изредка пропустить мяч, т.к. ему например может не хватить скорости перемещения биты, чтобы перехватить мяч. Но этот алгоритм тоже не блещет оригинальностью.
Все кроется в уже приводившейся выше схеме. Она не точна. Человек действует немного иначе.
Действие:
мяч летит в нашу сторону.
Реакция:
отсутствует, пока мяч не достигнет некоей критической точки (например середины экрана), когда уже необходима сосредоточенность и точность перемещения биты.
Действие:
мяч пересек середину экрана.
Реакция:
Сдвигаем биту для отбива мяча.
Такая схема более вероятно моделирует поведение человека.
Пример кода (бита управляемая компьютером находится слева и разрешение экрана 640x480):

IF ballY<=320;
IF ballY > BitaY_Player2;
BitaY_Player2= BitaY_Player2+1
ENDIF
ENDIF
IF ballY<=320;
IF ballY < BitaY_Player2;
BitaY_Player2= BitaY_Player2-1
ENDIF
ENDIF

Т.е. компьютер не обращает внимания на летающий по экрану мяч, но стоит тому пересечь середину экрана и компьютерный мозг оживает, посылая электронные команды бите.
Здесь необходимо обратить внимание на новый метод записи – сдвоенный оператор IF.
Таким образом мы можем задавать несколько условий для выполнения каких то действий.
Пример (если a<10 и b>2 тогда с= a+b):

IF (a<10)
IF (b>2)
c=a+b;
ENDIF
ENDIF

Пока «a» не станет меньше 10 второе условие даже не будет проверяться. Таким способом мы можем создавать конструкции любой сложности.
Итак, мы действительно создали Искусственный Интеллект способный управлять битой и поспорить с вами на звание чемпиона в игру Ping Pong!
Но…
Не кажется ли вам, что компьютер действует слишком прямолинейно?
Все так и есть. По данному алгоритму компьютер играет очень сильно, что не интересно. Но чтобы улучшить АI , необходимо научить компьютер ошибаться!
Придать железу черты человека.
Что для этого необходимо?
Как уже было не раз – доработать выше приведенную схему.
(На данном примере хорошо видно, что для реализации сложных задач необходимо начинать с элементарных вещей. Заставить компьютер сделать один ход, проанализировать, понять, что не удовлетворяет в таком его поведении и изменить. Это путь от простого к сложному. Добавляя новые возможности к однострочной конструкции мы можем получить очень мощные алгоритмы имитирующие поведение человека).
Ниже приведен улучшенный алгоритм поведения компьютерного опонента .
Действие:
мяч летит в нашу сторону.
Реакция:
отсутствует, пока мяч не достигнет некоей критической точки (например середины экрана), когда уже необходима сосредоточенность и точность перемещения биты.
Но ведь живой человек не в состоянии определить с абсолютной точностью когда мяч пересекает середину экрана. Он может начать двигать битой либо с опозданием, что приведет к пропуску мяча (не обязательно), либо раньше.
Действие:
мяч пересек середину экрана.
Реакция:
Сдвигаем биты для отбива мяча.
Необходимо добавить некую случайность реагирования CPU на приближение мяча. Если отсутствие реакции на мяч летящей во второй половине экрана вполне естественно, то при пересечении середины экрана необходимо ввести случайный фактор реакции компьютерного игрока. Чтобы в разные моменты он мог среагировать на мяч и когда он уже пересек 400 точек экрана, и когда он только пересек границу, а то и находится в 100 точках от биты. В последнем случае скорее всего CPU пропустит мяч – что соответствует выражению «прозевал мяч»!
Пример кода улучшенного AI CPU:

Zone_Action=RND(300)
IF Zone_Action <70 THEN Zone_Action =70
. . .
IF ballY<= Zone_Action;
IF ballY > BitaY_Player2;
BitaY_Player2= BitaY_Player2+1
ENDIF
ENDIF
. . .
По аналогии надеюсь вы самостоятельно сможете дописать до конца весь алгоритм.
Что мы видим? Компьютер действует, как живой!
Самое время изменить код нашей игры дав компьютерному противнику Искусственный Интеллект!

Устранение ошибок в игре. Отладка
Даже гениальные программисты совершают ошибки. Благодаря DiNGS программист избавлен от необходимости ломать голову, почему некий фрагмент кода не освободил после себя память, а компьютер намертво виснет. Создавая на DiNGS мы программируем именно игру, а не ненужные технические детали. Всем известна Майкрософтовская библиотека для разработки игр – DirectX. Она искусно скрывает детали программирования, но все же она достаточно (и часто неоправданно) сложна. Программируя на DirectX вы можете допустить множество ошибок – на DiNGS только связанные с логикой игры! Можно рассматривать DiNGS, как более удобную надстройку над DirectX (что является правдой). Ведь и неказистому DOS вы предпочитаете более понятный интуитивно Windows. Т.к. мы используя DiNGS к счастью избавлены от множества ошибок технического характера (не освобождение памяти, вывод не на те сурфейсы и тд), то поговорим о логических ошибках и их нахождении.
Программируя игру Ping Pong GOLD я часто сталкивался с ситуациями, когда после введения нового алгоритма игра должна была действовать сообразно с моим замыслом, но в реальности все выходило иначе. Например при столкновении летающего черепа с мячом отбитым игроком должен был вылетать приз создаваемый случайным образом и лететь в сторону игрока. При подборе игроком приз производил некие действия и срикошетив исчезал плавно растворяясь.
В реальности приз вылетал, доходил до биты игрока и благополучно продолжал путь сквозь нее. Было ясно, что в код вкралась ошибка, но какая и как это выяснить?
В игре для данной ситуации использовались несколько переменных:
priz – могла быть 1 или 0, сообщала есть ли на экране в данный момент приз.
collision_priz - могла быть 1 или 0, сообщала есть ли столкновение биты и приза.
ident – показывала, что за приз отобразить на экране, была числом от 1 до 12.
Например: 1- приз Iceball, 2 – приз Armor, 3 – приз Energy UP и тд.
Все мои действия свелись к добавлению в код лишь нескольких строчек для отладки:

PRINT “сollision priz = “+collision_priz, 100,100
PRINT “ident for priz = “+ident, 100,130
PRINT “priz = “+ priz, 100,160

Запустив игру я смог наблюдать за значениями переменных.
На экране горели надписи:

collision priz = 0
ident for priz = 0
priz = 0

Череп столкнулся с мячом и тут же я увидел:

collision priz = 0
ident for priz = 3
priz = 1

На экране из черепа выскочил приз Energy UP, как и было задумано.
Переменная collision_priz оставалась равной 0, т.к. пока никакого столкновения биты и приза не произошло.
Приз столкнулся с битой, но collision_priz остался равным 0.
Очевидно, что ошибка в участке кода обрабатывающего столкновение и присваивающего значения переменной collision_priz.



Переместившись на данный участок кода (нажатие Ctrl+F вызывает меню поиска, куда вводим название интересующей переменной collision_priz и нажимаем Энтер) я увидел, что перепутал в обработчике столкновений название переменной и вместо присвоения 1 переменной collision_priz , он присваивал это значение мифической переменной Сollision_priz.
Да, DiNGS хоть и имеет синтаксис BASIC - на самом деле чистый С, где названия набранные с разным регистром различны (т.е. priz, Priz и pRiZ – три совершенно разные переменные).
К подобному приему отладки приходится прибегать достаточно часто. И даже не из за того что была ошибка. Иногда просто требуется удостоверится, что все действительно так как надо. Например генератор случайных чисел присваивает переменной generator случайное число, после некоторой обработки это значение присваивается переменной ident. Но действительно ли все так, как кажется, уж слишком часто выпадает приз Energy UP.
Выводим на экран 2 переменные:

PRINT generator, 200,100
PRINT ident, 200,130

И… Видим как все время выпадает одно и тоже значение у ident, хотя в генераторе действительно разные числа.
По Ctrl+F вводим поиск переменной ident и видим неожиданно, что у нас есть 2 такие переменные, одна из которых все время присваивает себе двойку. Как вы поступите дальше, переименуете переменную или примените другие действия целиком зависит от вас – главное проблема обнаружена.
Программируя плавное движение биты я хотел просто убедится в работе данного алгоритма, т.к. алгоритм применявшийся для этих целей ранее был тоже достаточно хорош и визуально не отличим. Отличия сказались когда я ввел призы SpeedUP для увеличения скорости биты и SpeedDown для уменьшения.
Я вывел на экран интересующую меня переменную velocity и увидел, что значения действительно меняются, т.е. все работает замечательно. Подробнее об этом алгоритме смотрите в части 18.
Надеюсь теперь, если и возникали у вас проблемы в игре, вы их успешно сможете исправить.

Key and Fireball
Многие играли в космические стрелялки, где маленький отважный истребитель выносит с экрана полчища врагов ураганным огнем лазерных пушек, но мало кто задумывался как это реализовано. Ах, какая ерунда!
Как бы не так. Все это достаточно сложно и есть много методов решения этой проблемы.
Но как это соотносится с программированием Понга?
Во многих играх, даже не стрелялках используются выстрелы. Вспомните тот же «Арканоид», где часто встречается приз «Fire» дающий возможность бите стрелять. В написанном мной PING PONG GOLD есть 2 приза аналогичного свойства – fireball (огненный шар поражающий биту противника и нанося ей повреждения) и Iceball – заморозка (попав во вражескую биту на время примораживает ее на месте не давая сдвинуться).
Попробуем повторить.

IF KEY(57) THEN SPRITE 12, fire_x, fire_y,0

Это первое что приходит в голову, но… Двигаться огонь (пуля, бомба, лазер) будет только пока вы держите нажатой клавишу SPACE (код 57).
Изменим тактику. Какая то команда должна дать сигнал о произведенном выстреле, после чего уже независимо от этого мы выведем изображение огня и станем изменять его координаты. На помощь придут триггеры. Пусть при нажатии на клавишу SPACE триггер (переменная) fire будет получать значение 1.
Пример кода:

Fire=0 //Триггер выстрела в 0, т.е. нет выстрела
LOADSPRITE “bita.bmp”,0 //загрузить спрайт биты в 0
LOADSPRITE “fire.bmp”,1 //загрузить спрайт огня в 1
bita_x=320 //координаты биты
bita_y=240
fire_x=bita_x //координаты огня те же, что у биты
fire_y=bita_y
WHILE TRUE //Начало цикла
SPRITE 0, bita_x, bita_y,0 //Вывести спрайт биты
IF KEY(57) THEN Fire=1 //Если нажат пробел присвоить триггеру 1
IF Fire=1 THEN SPRITE 1, fire_x, fire_y,0
//Если триггер сработал вывести спрайт огня
fire_x = fire_x-1 //Изменять координаты спрайта для его движения.
SHOWSCREEN
WEND

Данная программа будет работать. Бита произведет выстрел. Огонь полетит через экран и исчезнет за его краем. Второй раз мы выстрел сделать не сможем.
Очевидно, что триггер так и остался включенным даже когда спрайта огня уже не видно на экране. Необходимо предусмотреть ситуацию обнуления триггера. Это может быть вариант, когда координаты спрайта выходят за границу экрана.
Пример:

IF fire_x <=-50 THEN Fire=0

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

PRINT “FIRE = ”+ Fire, 100,100
PRINT “fire_x = ”+ fire_x, 100,140

Запустим программу и видим, что Fire действительно обнуляется, но координаты fire_x продолжают увеличиваться. Т.е. мы не предусмотрели после исчезновения спрайта огня присвоению ему координат биты, для нового выстрела. Огонь начинает движение от биты.

IF fire_x <=-50
Fire=0
fire_x=bita_x //координаты огня те же, что у биты
fire_y=bita_y
ENDIF

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

IF KEY(57)
fire_x=bita_x //координаты огня те же, что у биты
fire_y=bita_y
ENDIF

Огонь при нажатии клавиши пробел будет появляться возле биты даже не долетев до конца и начинать свой путь сначала.
Как видите произвести удачный выстрел задача не тривиальная. Но где вы видели стрелялки или арканоиды с одной единственной пулей?
Во всех играх стреляют очередями или регулируя частоту выстрелов бесконечным нажатием клавиши пробела. Чем быстрее тем лучше.
Мы рассмотрим упрощенный вариант, который без труда можно усложнить.
Позволим игроку произвести 2 выстрела. Т.е. на экране может быть до двух изображений огня. Удобно для этого использовать массивы, но я для простоты от них откажусь.

Fire=0 //Триггер выстрела в 0, т.е. нет выстрела
Fire2=0 //Триггер выстрела в 0, т.е. нет выстрела
LOADSPRITE “bita.bmp”,0 //загрузить спрайт биты в 0
LOADSPRITE “fire.bmp”,1 //загрузить спрайт огня в 1
bita_x=320 //координаты биты
bita_y=240
fire_x=bita_x //координаты огня те же, что у биты
fire_y=bita_y
fire2_x=bita_x //координаты огня те же, что у биты
fire2_y=bita_y
WHILE TRUE //Начало цикла
SPRITE 0, bita_x, bita_y,0 //Вывести спрайт биты
IF KEY(57)
IF Fire=0
fire_x=bita_x //координаты огня те же, что у биты
fire_y=bita_y
Fire=1
ENDIF
IF KEY(57)
IF Fire2=0
fire2_x=bita_x //координаты огня те же, что у биты
fire2_y=bita_y
Fire2=1
ENDIF
 
IF Fire=1 THEN SPRITE 1, fire_x, fire_y,0
//Если триггер сработал вывести спрайт огня
IF Fire2=1 THEN SPRITE 1, fire_x, fire_y,0
//Если триггер сработал вывести еще один спрайт огня
fire_x = fire_x-1 //Изменять координаты спрайта для его движения.
Fire2_x = fire2_x-1 //Изменять координаты спрайта для его движения.
IF fire_x <=-50
Fire=0
ENDIF
IF fire2_x <=-50
Fire2=0
ENDIF
SHOWSCREEN
WEND

Этот вариант очень хорош. На его основе можно создать любые
комбинации выстрелов – очереди, автофайр и тд. В вышеприведенный пример желательно добавить массивы:

DIM Fire[2]
DIM fire_x[2]

А также выделить в подпрограммы логические конструкции. Но об этом уже неоднократно рассказывалось в предыдущих главах.

Функции
Функции часто используются в программировании. Прежде чем начать писать функции самостоятельно, необходимо понять что же это такое.
Представьте кухонный комбайн. Вы закладываете в него мясо, лук, соль и на выходе получаете фарш. Это краткое описании «функции». Как все происходит от нас скрыто (свойство сокрытия подробностей называется инкапсуляцией).
Функция получает определенные данные и возвращает результат после действий произведенных над ними. Пример, известная вам функция RND в качестве параметра получающая число и возвращающая случайное значение не превышающее указанное функции. Этот результат мы может присвоить переменной:
А=RND(400)
Существуют функции которые вообще не возвращают значений (или возвращают значение void – что значит «ничего»). На языке С это функции void, а на Pascale подобные функции называются процедурами. Все вместе - есть обычные подпрограммы, а остальное лишь терминология способная при незнании запутать.
Функции позволяют упростить программирование. Один раз сокрыть (инкапсулировать) часть кода под осмысленным названием и в дальнейшем пользоваться этим псевдонимом.
Но прежде чем мы углубимся в создание первой функции необходимо объяснить, что есть локальные и глобальные переменные.
Локальные переменные действуют только внутри функции. Если есть локальная переменная «A» внутри функции и она равна 5, то это никак не повлияет на одноименную переменную находящуюся за пределами кода функции.
Если есть необходимость, чтобы определенная переменная везде была одной и той же необходимо ее обозначить, как глобальную:

GLOBAL A

Локальные переменные обозначаются

LOCAL A

Особенность состоит только в том, что если вы хотите использовать значение именно глобальной переменной, то стоит так и написать:

С=GLOBAL a +b

Приведем пример функции seredina получающей определенное значение и возвращающей это значение деленным на 2:

// Начало программы
LOADSPRITE “bita.bmp”,0
SPRITE 0, seredina(640),seredina(480),0
SHOWSCREEN
FUNCTION seredina: a //если значений больше, то писать через запятую a,b,c
c=a/2
ENDFUNCTION c

Вышеприведенный пример демонстрирует использование функции.
Функция начинается словом FUNCTION после которого следует название функции, двоеточие и входные переменные и завершается словом ENDFUNCTION с указанием названия возвращаемой переменной: ENDFUNCTION с.
Если функции не возвращает значения указывается: ENDFUNCTION void
Функция середина получает значение обозначенное переменной «а». Далее мы вводим переменную «с», которой присваиваем результат деления «а» на 2. И возвращаем из функции значение «с».
Часто функции используют для определения большего числа из 2.

FUNCTION max: a,b
IF a>b THEN c=a
IF b>a THEN c=b
ENDFUNCTION c

Как вы будете использовать функции зависит только от вашей фантазии.

Переназначение клавиш. Управление мышью
Большой успех игры не мало зависит от удачного управления. Вы привыкли, чтобы клавишей «огонь» был пробел, а ваш друг предпочитает Ctrl. Есть способ, который удовлетворит обоих – назначение клавиш пользователем.
Перед началом игры вы укажите клавиши управления удобные вам и сможете целиком погрузится в игру не отвлекаясь на мелочи.
В DiNGS этим ведает команда INKEY.
INKEY возвращает код нажатой клавиши. В общем-то вы много использовали утилиту для определения нажатой клавиши keycode – ниже код этой программы.

WHILE TRUE
   i$=INKEY$()
  PRINT i$, 0, 100
SHOWSCREEN
WEND

Переменной i присваивается нажатая клавиша. Знак $ указывает на размещение данных в памяти, что исключает их случайное изменение.
Следующая строка выводит на экран код нажатой клавиши.
Рассмотрим пример переназначения клавиш.

GOSUB control
WHILE TRUE
IF key(left$)=1 THEN PRINT “LEFT”,100,100
IF key(right$)=1 THEN PRINT “RIGHT”,100,100
SHOWSCREEN
WEND
END
SUB control:
PRINT “PRESS LEFT KEY”
   left$=INKEY$()
PRINT “PRESS RIGHT KEY”
   right$=INKEY$()
ENDSUB

В начале программы вызывается подпрограмма переназначения клавиш control, которая просит вас нажать клавишу управления которую вы желаете использовать для перемещения объекта в лево. Нажатая клавиша сохраняется в переменной left.
Далее программа просит нажать клавишу для управления движением в право и присваивает код нажатой клавиши переменной right.
Далее демонстрируется использование вновь назначенных клавиш:

IF key(left$)=1 THEN PRINT “LEFT”,100,100

Следует обратить внимание – функция INKEY появилась только в последних версиях языка DiNGS.
Теперь рассмотрим использование мыши в игре.
Команда MOUSESTATE помещенная в код программы будет постоянно передавать вам данные о координатах курсора мыши и нажатых клавишах.
Пример кода:

MOUSESTATE mouse_x, mouse_y, button1, button2

Если требуется вывести координаты курсора в определенное положение используется команда SETMOUSE.
Пример кода:

SETMOUSE 300,200

Ниже приведем пример использования обоих команд.

LOADSPRITE “cursor.bmp”,0

SETMOUSE 640/2, 480/2
WHILE TRUE
MOUSESTATE mouse_x, mouse_y, button1, button2
SPRITE 0, mouse_x, mouse_y, 0
IF button1=1 THEN PRINT “MOUSE PRESS BUTTON 1”,100,100
IF button2=1 THEN PRINT “MOUSE PRESS BUTTON 2”,100,150
SHOWSCREEN
WEND
END

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

Улучшаем управление
В игре необходимо захватить игрока с первых мгновений прекрасным управлением и плавностью. Пусть игра не блещет графикой, но приятное, удобное и чуткое управление сделает свое дело. Игрок даже не поймет в чем дело, ему будет просто нравиться играть.
Как у вас происходит управление битой в Ping Pong?

IF KEY(200) THEN bat_x= bat_x+1

Это конечно просто, но и результат от такого управления не высок. Давно существуют методы существенно добавляющие комфорта в управление. О них вы нигде не прочтете (ну разве, что здесь). Овладеть этими приемами не сложно и на помощь опять прейдут счетчики. Я этот прием называю «велосити» (velocity).
Вы нажали клавишу «стрелка вверх» - бита начинает движение. Вы отпустили клавишу. Бита намертво застывает. От этого создается ощущение дерганности управления.
Пример: едет машина со скоростью 60 км/ч , водитель внезапно нажимает тормоз. Что произойдет? Машина остановится? Но ведь прежде чем она застыла, ей пришлось пройти тормозной путь, пока визжали тормоза. Т.е. машина плавно, но быстро меняла скорость, пока не остановилась.
В идеале и при отпускании клавиши бита не может сразу застыть. У нее должен быть свой «тормозной путь». Бита по инерции продолжит путь постепенно замедляясь до полной остановки.
Приведем пример демонстрирующий это:

velocity=0
LOADSPRITE “bita.bmp”,15
bita_x=200
bita_y=200
WHILE TRUE
SPRITE 15,bita_x, bita_y, 0
GOSUB control
WEND
SUB control:
IF KEY(200)
bita_y=bita_y-1
velocity=10
ENDIF
IF velocity>0 THEN velocity=velocity-1
IFvelocity<=0 THEN velocity=0
bita_y=bita_y-velocity
ENDSUB

При нажатии клавиши переменная velocity получает значение 10 и пока нажата клавиша она все время получает значение 10. При отпускании клавиши становится возможным уменьшение значения velocity.
velocity=velocity-1
А так как она привязана к перемещению ракетки: bita_y=bita_y-velocity , то мы увидим эффект «тормозного пути».
Но, что случилось с управлением битой? Оно стало великолепным, отзывчивым, практически интуитивным!
Для практического применения необходимо ввести переменную velocity2 для описания движения вниз и изменить строку влияющую на перемещение биты подобной:
bita_y=bita_y+velocity.
Скорость перемещения или «путь торможения» биты можно увеличить или уменьшить присваивая переменной velocity большие или меньшие значения.
Пример:

IF KEY(200)
bita_y=bita_y-1
velocity=20
ENDIF

Это прекрасный пример программирования показывающий одну из возможностей улучшить управление в игре. А что если подобный пример применить на мяч? И почему бита сразу срывается с места, а потом постепенно замедляется. Что если ей добавить и «путь разгона»? Впрочем, в моей игре PING PONG GOLD движения биты так и реализованы. А у вас?

Траектории
При использовании в игре движения мяча может показаться, что оно уж очень просто. Действительно, вполне возможно внести в его движение некоторые коррективы. Например закручивание мяча, тогда полет будет иметь другую траекторию. В программируемой мной Ping Pong Gold я использовал такую возможность следующим образом. Игрок в процессе игры набирает монетки и через каждые 3 уровня попадает в магазин, где может прикупить жизни, энергию, оружие (файрболы и айсболы) и обманные траектории движения мяча. В процессе игры можно вызвать экран INVENTORY и в нем выбрать для применения купленные предметы. При выборе traectory мяч меняет свой полет делая умопомрачительные повороты обманывая противника.
Следует разъяснить, как все это происходит. Траектория строится по принципу окружности. Мяч летит не ровно, а описывая окружность. Для создания подобных траекторий понадобятся синус или косинус. В DiNGS это возможно.

Пример №1 программы перемещающей объект по кругу.

LOADSPRITE “ball.bmp”,0
WHILE TRUE
SPRITE 0, 100*SIN(a)/1000 +150, 100*COS(a)/1000+150,0
a=a+1
IF a>360 THEN a=0
SHOWSCREEN
WEND

Эта несложная формула позволят создать движение по окружности. Число 100 – это диаметр окружности, а 150 – координаты.
Если вы захотите, чтобы вылетающие призы летели не по прямой, а качаясь словно на волнах можете использовать следующий пример №2:

SPRITE 0, priz_x, 100*COS(a)/1000+150,0
a=a+5
IF a>180 THEN a=0
priz_x=priz_x+1
Создать плавающую надпись тоже не составит труда.
Пример №3.
SPRITE 0, 100, 100*COS(a)/1000+150,0
a=a+5
IF a>360 THEN a=0

Если примеру №1 задать движение по оси х, то можно получить движение по спирали, что очень эффектно например для выстрелов.

fire_x=100

LOADSPRITE “fire.bmp”,0
WHILE TRUE
SPRITE 0, fire_x+100*SIN(a)/1000 +150, 100*COS(a)/1000+150,0
a=a+1
IF a>360 THEN a=0
fire_x=fire_x+8
SHOWSCREEN
WEND

Использование синусов и косинусов сделают игру увлекательней и веселей.

Шрифты
Редко можно встретить игру без надписей. Важно использовать красивый шрифт. Эта деталь придаст профессиональный вид вашему творению. К счастью Гермон Фриш (создатель языка DiNGS) постарался на славу и у нас есть инструмент использования шрифтов. Да каких!!!
Изображения букв хранятся в обычных графических *.bmp-файлах. По умолчанию для шрифта используется smalfont.bmp лежащий в директории с DiNGS или вашей игрой.
Самый простой способ открыть этот файл в графическом редакторе и перерисовать буквы вручную.
Другой способ заключается в использовании утилитки идущей в комплекте с DiNGS. Она способна преобразовать любой шрифт установленный в ОС Windows в *.bmp файл. Причем размер букв вы можете задать удобным ползунком. Я в Ping Pong Gold поступил аналогичным образом. Получив bmp-файл я открыл его в графическом редакторе, после чего применил к нему ряд специальных эффектов. Вид букв стал более красочен и объемен. Если вы пожелаете создать игровые шрифты от начала и до конца самостоятельно, то это тоже возможно. Следует помнить только, что каждая буква должна умещаться в квадратике 8х8 или 16х16. Если не соблюдать этого правила, то на экран будет отображена абракадабра. Фон между букв должен быть залит либо черным цветом R:0,G:0,B:0, либо малиновым R:255,G:0,B:128.
Никто не ограничивает вас использованием одного шрифта. По умолчанию всегда загружается smalfont.bmp.
Для загрузки другого шрифта используется команда

LOADFONT “name.bmp”

Ниже приведем пример использования 2-х шрифтов – smalfont (по умолчанию) и custom.bmp (созданный нами).

PRINT “WRITE smalfont.bmp”,100,100
LOADFONT “custom.bmp”
PRINT “WRITE custom.bmp”,100,150
SHOWSCREEN
MOUSEWAIT

Такая возможность дает создавать очень красивые надписи в игре не прибегая к использованию одиночных спрайтов.
В Ping Pong Gold свой шрифт используется для вывода счета (заработанных очков) и информационных надписей.

Взрывы в игре
В играх часто используются красочные взрывы. Не обязательно только в стрелялках типа Tyrian (на IBM PC), Banshee AGA (AMIGA) и тд. Взрывы могут использоваться и во вполне мирных, логических, играх. Например исчезновение блока изобразить взрывом.
Мы уже рассказывали о анимации. Это периодически повторяющаяся последовательность спрайтов.
При реализации взрывов существуют некоторые тонкости, на которых и хотелось бы остановится.
Предположим в игре Ping Pong мяч столкнулся с препятствием и если сила удара была достаточно существенной уничтожил его. На экране необходимо изобразить взрыв от столкновения, но с анимацией прокрутившей последовательность лишь один раз.
Как это сделать? Нужно создать переменную разрешающую или запрещающую взрыв – назовем ее active.
Ниже рассмотрим это подробнее на небольшом примере.

//Загружаем графику
//Мяч
LOADSPRITE “ball.bmp”,0
//Взрыв
LOADSPRITE “boom1.bmp”,1
LOADSPRITE “boom2.bmp”,2
LOADSPRITE “boom3.bmp”,3
LOADSPRITE “boom4.bmp”,4
//Препятствие
LOADSPRITE “stena.bmp”,5
//Номер спрайта взрыва из памяти для начала анимации
boom_i=1
//Точка начала движения мяча по X
ball_x=500
//Взрыва нет
active=0
//Начало игры
WHILE TRUE
//Выводим спрайт мяча
SPRITE 0,ball_x,100,0
//Выводим препятствие
SPRITE 5,50,75,0
BOXCOLL col, ball_X, 100, 16, 16, 50, 75, 20, 50
IF col=1
active=1
ENDIF
IF active=1
IF boom_i<=4
SPRITE boom_i,100,100,0
boom_i=boom_i+1
ELSE
active=0
boom_i=1
ENDIF;ENDIF;
//Движение мяча
ball_x=ball_x+1
SHOWSCREEN
WEND

Безусловно, заслуживает внимания только часть кода описывающая условия для active:

IF active=1
IF boom_i<=4
SPRITE boom_i,100,100,0
boom_i=boom_i+1
ELSE
active=0
boom_i=1
ENDIF;ENDIF;

Если active=1 (то есть произошло столкновение мяча и препятствия) и кадр анимации взрыва еще не достиг номера последнего кадра анимации (4), тогда выводим спрайт взрыва на экран, увеличиваем счетчик кадров, чтобы следующий раз показать другой кадр и выводим на экран. В условие присутствует оператор ELSE (иначе).
Если не одно из условий не совпадает (уже показан последний кадр или переменная active=0 (нет столкновения)) – обнуляем триггер active и присваиваем анимации первый кадр для следующих взрывов в игре.
Более кратко: при столкновении срабатывает триггер active и дает команду прокрутить анимацию взрыва. При демонстрировании последнего кадра анимации триггер сбрасывается. Анимация более не показывается до следующего срабатывания триггера.
Если необходимо отобразить на экране множество взрывов, то прибегните к использованию массивов (подробнее можно прочитать в соответствующей главе).

Музыка и звуковые эффекты в игре
Сложно представить игру без звукового оформления. Звук придает жизненность и достоверность игровому миру. Журчание ручейка или удар биты по мячу существенно улучшают восприятие игровой программы. Если же вы снабдите игру музыкальным сопровождением, то игроки будут только благодарны. Только не следует забывать о нескольких простых правилах. Необходимо предусмотреть возможность отключения музыки (вкусы у всех различны и если вы обожаете Antrax или Metallica – это не значит, что всем по душе придется такая музыкальная тема). Музыка по возможности не должна быть надоедливой и содержать приятную мелодию.
DiNGS понимает музыкальные форматы mid, wav и mp3.
MID –миди-музыка. Занимает очень мало места. При хорошей звуковой плате (Live, Audigy) звучит просто фантастически. Ее можно написать в программах Кейкволк или Кубейс. Если же у вас есть отдельные клавишные инструменты типа Korg или Yamaha - я сразу снимаю шляпу.
WAV – музыка без сжатия. Занимает жутко много места. Использование данного формата оправдано только для коротких звуковых эффектов – взрывов, выстрелов и аналогичных звуков.
MP3 – музыка со сжатием. Если у вас уже есть музыка написанная вами, то можно ее преобразовать в mp3-формат. Недостатки- очень большой размер (но в разы меньше, чем wav).
В идеале музыка должна быть в формате MID, а эффекты в wav.
Можно создать музыку и в любимых народом трэкерах Scream Treacker, IT и др. Но музыку придется потом конвертировать в mp3.
Трэкерные форматы (s3m, mod, it и др.) к сожалению в DiNGS не поддерживаются.
Их поддержка есть в языке Dark Basic.
Для воспроизведения эффектов используются команды:
LOADSOUND – загружает в память звуковой файл wav
PLAYSOUND – проигрывает файл из памяти
HUSH – прерывает воспроизведение любого звука.
Возможно наложение одних звуков на другие.
Для воспроизведения музыки (mid,mp3,wav) используются команды:
PLAYMUSIC – начинает проигрывание музыкального файла
STOPMUSIC – останавливает проигрывание музыкального файла
Ниже приведем пример использования данных команд на практическом примере.

LOADSOUND “boom.wav”,0,1
PLAYSOUND 0,0
MOUSEWAIT

Программа один раз проиграет звук boom.wav.
Рассмотрим подробнее LOADSOUND “boom.wav”,0,1.
“boom.wav” – файл содержащий звук
0 –номер в памяти для размещения.
1 – буфер. Определяет длительность воспроизведения звука. Может иметь значение 1-4.
Пример загрузки нескольких звуковых файлов:

LOADSOUND “boom.wav”,0,1
LOADSOUND “tada.wav”,1,1
LOADSOUND “bah.wav”,2,1
LOADSOUND “go.wav”,3,1

С проигрыванием музыки дело обстоит еще проще.

PLAYMUSIC "music1.mid"
MOUSEWAIT
STOPMUSIC

Приведем пример совмещающий воспроизведение музыки и эффектов.
Фоном играется музыка. При нажатии клавиши «Пробел» слышен выстрел.

LOADSOUND “boom.wav”,0,1
PLAYMUSIC "music1.mid"
WHILE TRUE
IF KEY(57)=1 THEN PLAYSOUND 0,0
WEND
STOPMUSIC
END

Прочитав данную главу вы теперь можете украсить свою игру великолепным звуковым оформлением, но я надеюсь вы подойдете к этому с выдумкой. Существует много способов добавить колорит в игру при помощи звука от банальной фразы “Уровень 1” в начале стадии до ужасного вопля при проигрыше.
Сделайте звук в игре динамическим!!! Если ваш герой идет по лесу добавьте пение птиц, подходит к водопаду – журчание воды и тд. Такие приемы используются во множестве отличных игр – Warcraft (PC), Cannon Fodder (AMIGA), Chaos Kid (AMIGA). Играть становится значительно интересней, а значит ваша игра найдет больше поклонников.

Сюжет и интро в игре
DiNGS – великолепный язык программирования и содержит много полезных функций.
Курс обучения программированию игр подходит к концу. Вы уже в состоянии создать игровой проект любой сложности. Конечно я не в состоянии был осветить все тонкости программирования игровых программ, но основы объяснены. Далее только практическое программирование способно помочь геймдевелоперу. Если вы шаг за шагом следовали за мной, то должны получить вполне законченную игру. Не хватает лишь нескольких деталей.
Какая игра без сюжета!
DiNGS позволяет демонстрировать видео-ролики формата avi.
Как это использовать?
Допустим вы сделали клон известной игры от Ocean – Terminator-2! Более лучшая графика и звук, высокое разрешение и тд. Какие-то события вы желаете обострить демонстрацией фрагментов из фильма в определенных ситуациях.
Нет проблем!
… код игры

BLENDSCREEN //плавное затемнение экрана
PLAYMOVIE "terminator2.avi"
SHOWSCREEN
...
Историю игры можно рассказать и в картинках. Анимированные, они могут плавно выезжать из-за края экрана и комментироваться текстом под ними. Такой подход часто использовался на замечательной 8-битной игровой приставке Nes (Dendy), вспомним хотя бы игры Ninja Gaiden 1-3, Darkwing Duck и др. Да и 16-битные монстры не брезговали таким приемом – Street of Rage, Contra Hardcorps и тд.
Но допустим, вы желаете ограничится только текстом плывущем снизу вверх по экрану.
Помимо уже известных вам способов замедлить движение существует еще один (только в последних версиях DiNGS) – LIMITFPS.

y=480

LIMITFPS 10
WHILE a_y>0
PRINT “Coder Infinity”, 0,y
PRINT “Art Infinity”, 0,y+20
PRINT “Music Infinity”, 0,y+20*2
PRINT “Music Infinity”, 0,y+20*3
a_y=a_y-1
SHOWSCREEN
WEND
BLENDSCREEN
LIMITFPS 60

... код игры
Вышеприведенный фрагмент интересен тем, что демонстрирует еще один ранее не использовавшийся прием добавления кратных чисел y+20*2, y+20*3 и показывает возможность регулирования скорости игрового процесса.
Зачем это нужно?
Хотите эффекты аля-Матрица или Max Pain с замедлением времени – нет проблем! Такой подход я использовал в своей игре Chaos World. При использовании суперудара время на секунду замедлялось и все происходило красиво и необычно.
Не оставляйте игру без сюжета. Даже плохой сюжет лучше, чем ничто. Пусть злобные пришельцы в 1001 раз захватывают галактику, а в конце игры вы уничтожаете главаря этой банды – все это значительно лучше, чем пройдя 20 уровней не получить никаких поздравлений, лишь унылую надпись «Игра окончена».
Сделайте в начале игры завязку сюжета.
«В 3102 году в солнечную систему вторглись корабли злобной инопланетной расы. Земляне захваченные врасплох в считанные часы потеряли весь космический флот. Уцелел только один удивительный космический истребитель, найденный на одном из спутников Юпитера в 2860 году. Предположительно его построила неизвестная цивилизация более 5 миллионов лет назад и оставила на дне кратера для неизвестных целей. За упорные годы изучения стал понятен принцип управления кораблем. Вы были одним из тех, кто занимался его изучением и теперь единственная надежда только на вас. Ужасные пришельцы не должны достигнуть орбиты Земли».



Далее идут уровни игры. При победе:
«Когда корабль пробился сквозь обломки вражеских летательных аппаратов вы увидели погруженную в лед атомной зимы некогда прекрасную голубую планету. Неужели все напрасно и вы опоздали?
Но нет! Это были лишь белые облака показавшиеся мертвой пустыней. Открылись чаши бездонных океанов, словно внимательные глаза наблюдающие за происходящем. Проплыли материки утопающие в буйстве зелени.
Все было прекрасно. Аппарат пошел на посадку. Земля встречала победителя!»
Game Over.
Такое завершение игры более предпочтительно. Причем стоит обратить внимание на псевдотрагизм. Казалось все напрасно, но нет… Такие приемы очень украшают игры заставляя переживать за происходящее на мерцающем экране монитора.


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

СОВЕТЫ
Прежде всего спланируйте игру. Продумайте уровни. Изюминку в каждом из них. Просто возьмите листок бумаги и нарисуйте уровень таким, каким вы его представляете.
Программируя PING PONG GOLD я иногда застревал на месте. Я писал и писал код, но казалось ничего не меняется и я думал, что топчусь на месте. Такие мысли могут отбить интерес к дальнейшему программированию. У меня это случилось, когда я запрограммировал главные возможности игры и в нее стало возможно играть. Еще не велся подсчет очков, небыло призов и тд. Самое важное еще небыло интересности игры.
Я садился за компьютер думал, что же написать сегодня. Подсчет очков или спец-траектории для мяча. В итоге я мог не написать ничего. Поэтому я составил план и сразу стало ясно, что именно необходимо для игры.
Пример плана.
1. Создаю оформление - бары и тд. Расставляю на экране.
2. Создаю в графическом редакторе игровые объекты. Биты. Шары.
3. Добавляю движение объектов.
4. Добавляю призы.
5. Делаю анимацию.
6. Добавляю АИ .
и тд
Если вам покажется, что вы обойдетесь без этого, то это вполне возможно, но я не обошелся.
Предусмотрите помощь в игре. В PING PONG GOLD я сделал экран помощи вызываемый по клавише F1. Но не каждый человек читает помощь перед игрой. Из этого следует совет – не делайте первый уровень слишком сложным. Относитесь к нему, как в введению в игру. Игрок только приноравливается к управлению, он еще не знает всех тонкостей игрового процесса. Не дайте игроку в первые же мгновения потерять интерес к своему творению из-за излишней сложности. Если в игре будет использоваться какая-то хитроумная комбинация с объектами (телепортерами, ключами и тд.) – сделайте уровень, который сперва познакомит игрока с этими объектами, а уже в следующих уровнях применяйте свою комбинацию.
Я сделал первый уровень PING PONG GOLD ознакомительным. Над призами висели таблички объясняющие их назначение. Высвечивались полупрозрачные подсказки с советами по управлению и тд. Во втором уровне этого уже небыло, но появились новые игровые объекты. Я постепенно увеличивал уровень сложности для последующих уровней. Нельзя делать уровни безумно трудными. Вы можете гордится, что никто кроме вас не догадается, как решить вашу головоломку, но это только будет раздражать игроков и отталкивать от вашего творения. Игра должна проходится легко, но с некоторой степенью сложности, что бы игрок мог чувствовать свое превосходство. Тогда игра оставит очень приятное впечатление. Я помню игру Overkill: All Six Planets на 386 IBM PC. Она проходилась всего за 10 минут, но то чувство, что вы способны вынести армады вражеских звездолетов с экрана заставляло проходить ее по 5 раз подряд!!! В конце концов игрок садится за игру не для того, чтобы проигрывать.
Составьте план расположения игровых объектов в ячейках памяти. Например:
0-10 – оформление игры. Бордюры, бары (полоски энергии) и тд.
20-50 - игровые объекты. Мячи, биты.
60-100 – спрайты призов.
Заметьте отсутствие в плане чисел 11-19, 51-59 и тд. Эти адреса оставлены для резерва. Если вы не уместите оформление игры в адреса с 0 по 10, то не будет необходимости не уместившемуся объекту присвоить число 120. Вы используете резерв, адреса 11,12 и тд.
При программировании, чтобы не вспоминать под какими номерами находятся у вас те или иные спрайты составьте списки.
Пример.
// Оформление игры.
0 karkas.bmp
01 bar.bmp
02
03

// Игровые объекты
20 bita.bmp
21 ball.bmp

// Спрайты призов
60 fireball.bmp
61 icebal.bmp

Таким способом вы всегда будете знать какие ячейки памяти у вас еще свободны.
Не забывайте, что старые версии DiNGS могут использовать только 255 ячеек (т.е. где то в компиляторе это описывается 8-битным числом). DiNGS последних версий позволяет использовать столько ячеек, сколько заблагорассудится.
Приведи переменные к однообразию.
Если вы не задумывались об этом, то зря. Иногда сложно вспомнить, как называется переменная ball_x или x_ball, поэтому во многих программистских коллективах существуют правила написания кода и объявления переменных.
Я сперва пишу название объекта, после чего следую уточнения.
Пример – переменная описывающая координату Х мяча.
ball_x
И если мне нужно будет создать триггер отвечающий за направление полета мяча, то я не задумываясь напишу ball_triger.
Пример однообразия переменных:

ball_x , bat_x, priz_x

Это конечно далеко не все, что можно сказать по поводу программирования игр, но остальное вы узнаете сами. Надеюсь ваши игры наполнятся фантастическими городами, неведомыми существами, монстрами и героями. Вы сможете создать свои неповторимые живые миры. В духе Street of Rage или Mario.
А как написать хорошую игру? Никто не знает.
Напишите интересную игру. Других советов быть не может.
 
 


(C) Infinity 2001
Hosted by uCoz