Menu

Categories
Books [2]
Games [16]
Global [5]
I-News [1]
Links [0]
Media [1]
Software [1]
Warez [3]

Login form

Search

Poll
Какой жанр игр вы предпочитаете больше? // Which game genres do you prefer?
Всего ответов: 83

Statistics
Online: 1
Guests: 1
Users: 0


Your IP:
3.12.34.96
Your browser:


Other counters
Урок 7

Создание Мини-игры


Часть 1

С чего бы начать создавать мини-игру? Самая простая игра, которую я знаю - Pong, так что давайте посмотрим на неё.

Что нам надо для Pong? Нам надо пару форм, немного звуков, и немного кода. К счастью, мы сделали первые два. Но как мы это всё соберём?

Графика
Она вся может быть в формате BMP, кроме той, которая перемещается по фону. Та графика должна быть сохранена как PCX, и везде, где Вы хотите иметь прозрачность должно быть окрашено в цвет №255.

Звук
Пользуйте тот звуковой редактор, который вам нравится.

Итак, у нас есть графика, и звук. Давайте используем их чтоб сделать Zong!

Сначала давайте сделаем так, чтоб шар отскакивал от краев окна. Вот пример:
//*****************************************************************************
//DEFINES
//*****************************************************************************

#define $Lesson7 "2"
#define $HitTheWalls "1"
#define $Before "2"
#define $During "3"
#define $After "4"
#define $TOPSIDE "1"
#define $LEFTSIDE "1"
#define $RIGHTSIDE "232"
#define $BOTTOMSIDE "232"

// *****************************************************************************
// КОД
// *****************************************************************************

#window $Lesson7:$HitTheWalls
startswitch $Lesson7:$Before
thinkswitch $Lesson7:$During
finishswitch $Lesson7:$After

//-------------------------------------------------------

width 256
height 256
body "Bouncing Ball Demo"
font "Boost12"
image "zong\zongball.pcx" zongX,zongY

// =======================================================

#switch $Lesson7:$Before
zongX = 200
zongY = 128
deltaX = 1
deltaY = 1
return

// =======================================================

#switch $Lesson7:$During

zongX = zongX + deltaX               // меняем координату X
zongY = zongY + deltaY          // меняем координату Y
if (zongX>$RIGHTSIDE) deltaX = -1          //, если мы справа, идем влево
if (zongX < $LEFTSIDE) deltaX = 1          //, если мы слева, идем справа
if (zongY > $BOTTOMSIDE) deltaY = -1          //, если мы снизу, поднимаемся
if (zongY < $TOPSIDE) deltaY = 1          //, если мы вверху, опускаемся
return

// =======================================================

#switch $Lesson7:$After
unset zongX
unset zongY
unset deltaX
unset deltaY
return

==>Запуск:
map test
loadape Lesson7
goape 2:1


Часть 2. Столкновение объектов.

А теперь давайте сделаем более значимое столкновение - столкновение с ... другим объектом! Но как мы сделаем это? Вместо того, чтобы проверять точную форму каждого объекта, мы идем на небольшую хитрость - просто делаем квадрат, с такими же размерами, и проверяем, пересекаются ли их грани. Это - очень быстрая проверка (только четыре сравнения), и достаточно реальная, чтобы работать, если вы не выполняете столкновеня на основе физики, подобно игре в бильярд. (Фактически, вы могли бы тоже использовать этот вид столкновения (квадраты), а затем делать другую проверку - выяснять действительно ли они столкнулись ...)

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

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

Итак...

// *****************************************************************************
// DEFINES
// *****************************************************************************

#define $lesson8 "2"
#define $hitthewalls "1"
#define $before "2"
#define $during "3"
#define $after "4"
#define $paddlecollision "5"
#define $checktoppaddle "6"
#define $checkbottompaddle "7"
#define $checkballhit "8"

//-------------------------------------------------------
// КОНСТАНТЫ
//-------------------------------------------------------

#define $topside "1"
#define $leftside "1"
#define $rightside "232"
#define $bottomside "232"
#define %paddlewidth "24"
                    //размер ракетки
#define %paddleheight "8"

// *****************************************************************************
// КОД
// *****************************************************************************

#window $lesson8:$hitthewalls
startswitch $lesson8:$before
thinkswitch $lesson8:$during
finishswitch $lesson8:$after

//-------------------------------------------------------

width 256
height 256
flags noscroll
                    // не листать текст, подобно диалогу.
body "                              "
if (colliding == 0) body "нет"
if (colliding == 1) body "верх! "
if (colliding == 2) body "низ! "
body "\n"
body "столкновения с верхом %d ", collisioncount1
body "\n"
body "столкновения с низом %d ", collisioncount2
font "boost12"
image "zong\zongball.pcx" zongx,zongy
image "zong\tpaddle.pcx" tpaddlex,tpaddley
image "zong\bpaddle.pcx" bpaddlex,bpaddley

// =======================================================
// BEFORE
// Инициализирование!
// =======================================================

#switch $lesson8:$before
zongx = 200
                    // шар
zongy = 128
bpaddlex = 112
                    // нижняя ракетка
bpaddley = 224
tpaddlex = 112
                    // верхняя ракетка
tpaddley = 16
deltax = 2.1
deltay = .43
return

// =======================================================
// DURING
// Активно обновляем соответствующие переменные
// =======================================================

#switch $lesson8:$during
zongx = zongx + deltax
                    // смена координаты x
zongy = zongy + deltay                    // смена координаты y
if (zongx > $rightside) deltax = -1 * deltax                    // если на правой стороне, идем влево
if (zongx < $leftside) deltax = -1 * deltax                    // если на левой стороне, идем вправо
if (zongy > $bottomside) deltay = -1 * deltay                    // если внизу, поднимаемся
if (zongy < $topside) deltay = -1 * deltay                    // если вверху, спускаемся
gosub $lesson8:$paddlecollision
return

// =======================================================
// Paddlecollision
// Где мы проверяем каждую ракетку на то, столкнулся ли с ней шар.
// =======================================================

#switch $lesson8:$paddlecollision
unset colliding
gosub $lesson8:$checktoppaddle
gosub $lesson8:$checkbottompaddle
return

// =======================================================
// Checktoppaddle
// Смотрим если столкнулся с верхней ракеткой, то меняем траекторию шара.
// =======================================================

#switch $lesson8:$checktoppaddle
checkx = tpaddlex
checky = tpaddley
checkwidth = %paddlewidth
checkheight = %paddleheight
gosub $lesson8:$checkballhit
if (colliding) set collisioncount1 = collisioncount1 + 1
return

// =======================================================
// Checkbottompaddle
// Смотрим если столкнулся с нижней ракеткой, то меняем траекторию шара.
// =======================================================

#switch $lesson8:$checkbottompaddle
if (colliding) return
checkx = bpaddlex
checky = bpaddley
checkwidth = %paddlewidth
checkheight = %paddleheight
gosub $lesson8:$checkballhit
if (colliding)
{
colliding = 2
                    // столкновение с нижней ракеткой
set collisioncount2 = collisioncount2 + 1
}
return

// =======================================================
// Checkballhit
// Проверить, столкнулся ли с чем-то шар
// =======================================================

#switch $lesson8:$checkballhit
set box1left = checkx
                    // зажаем границы поля
set box1right = checkx + checkwidth
set box1top = checky
set box1bottom = checky + checkheight
set box2left = zongx
                    // шар - 8 x 7
set box2right = zongx + 7
set box2top = zongy
set box2bottom = zongy + 6
unset colliding
                    // устанавливаем толко если они друг в друге
if (box1top > box2bottom) return                    // проверка если они касаются краев окна
if (box2top > box1bottom) return
if (box1left > box2right) return
if (box2left > box1right) return
set colliding = 1
                    // если нет, то у нас столкновение!
return

// =======================================================
// AFTER
// Оставить всё таким, каким было до нашего прихода
// =======================================================

#switch $lesson8:$after
unset zongx
unset zongy
unset deltax
unset deltay
unset tpaddlex
                    // верхняя ракетка
unset tpaddley
unset bpaddlex                    // нижняя ракетка
unset bpaddley
unset collisioncount1
unset collisioncount2
return

==>Запуск:
map test
loadape Lesson8
invoke 2:1


Часть 3. Тайминг.

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

Игровой движок Anachronox запросто вам их выдаст, так что можете не переживать за фреймрейт, разве что вы не кодите в окне Anachronox мини-игру Mass Effect.

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

Между прочим, вместо invoke вы можете использовать команду goape. Они идентичны.

// *****************************************************************************
// DEFINES
// *****************************************************************************

#define $Lesson9 "2"
#define $TimerTest "1"
#define $Before "2"
#define $During "3"
#define $After "4"
//-------------------------------------------------------

// КОНСТАНТЫ
//-------------------------------------------------------

#define %MSPERFRAME "50"
                    //50 миллисекунд *20 = 1 секунда
#define %FRAMESPERSECOND "20"

// *****************************************************************************
// КОД
// *****************************************************************************

#window $Lesson9:$TimerTest
startswitch $Lesson9:$Before
thinkswitch $Lesson9:$During
finishswitch $Lesson9:$After

//-------------------------------------------------------

width 256
height 256
title "Timer Test"
body "Seconds: %d Frames: %d", secCounter, frameCounter

// =======================================================
// Before
// Инициализирование!
// =======================================================

#switch $Lesson9:$Before
lastMS = func_GameTime
currentMS = lastMS
frameCounter = 0
                    // это ненужно, но установим значения
secCounter = 0                    // этих переменных как принято у программистов
return

// =======================================================
// During
// Активно обновляем соответствующие переменные
// =======================================================

#switch $Lesson9:$During
set currentMS = func_GameTime
                    // получаем стартовое время, запускаем счет
set delta = currentMS - lastMS
if (delta < %MSPERFRAME) return
                    // не делать ничего до начала фрейма
set lastMS = currentMS
frameCounter = frameCounter + 1
if (frameCounter == %FRAMESPERSECOND)
{
frameCounter = 0
secCounter = secCounter + 1
}
return

// =======================================================
// After
// Обнуление
// =======================================================

#switch $Lesson9:$After
unset frameCounter
unset secCounter
unset delta
unset lastMS
unset currentMS
return
==>Запуск
map joey
loadflo Lesson9
goape 2:1


Часть 4. Пользовательский Ввод.
Без пользовательского ввода, игра - кино. Так-что теперь мы научимся получать нажатия клавиш, позицию и нажатия кнопок мыши!

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

Также давайте придумаем более удобный формат для задания bank:entry!

Это окно не закрывается, так что откройте консоль и наберите:
ui_closewindow 2:1
Это - то, что вы должны делать, если ваше окно не будет закрываться.
Самый плохой случай, когда вы даже не знаете, каков порядковый номер окна:
ui_closeallwindows
Однако НИКОГДА не делайте этого с помощью консольной команды в окне. Это - плохая практика.
// *****************************************************************************
// DEFINES
// *****************************************************************************

#define @UserInput "2:1"
#define @Before "2:2"
#define @During "2:3"
#define @After "2:4"
#define @GetMouseInput "2:5"
#define @EnterWasPressed "2:6"
#define @MouseButtonWasPressed "2:7"

//-------------------------------------------------------
// КОНСТАНТЫ
//-------------------------------------------------------

#define %MSPERFRAME "50"
                    // 50 миллисекунд * 20 = 1 секунда
#define %FRAMESPERSECOND "20"
#define %LEFTSIDE "-100"
                    // может двигаться на 100 влево и 100 вправо
#define %RIGHTSIDE "100"
#define %PRESSED "1"
                    // кнопка или клавиша была нажата!

// *****************************************************************************
// КОД
// *****************************************************************************

#window @UserInput
startswitch @Before
thinkswitch @During
finishswitch @After

//-------------------------------------------------------

width 232
height 256
flags passive
                    // окно не может быть закрыто щелчком мыши
flags noscroll                    // текст остается на экране вместо прокрутки
title "User Input Wackiness! "
if (enterCounter == 0) body "Enter не нажат \n"
if (enterCounter > 0) body "Enter был нажат \n"
if (mouseButtonCounter == 0) body "Кнопка мыши не нажата \n"
if (mouseButtonCounter > 0) body "Кнопка мыши была нажата \n"
image "zong\bpaddle.pcx" bpaddleX,bpaddleY

// =======================================================
// Before
// Инициализирование
// =======================================================

#switch @Before
lastMS = func_GameTime
currentMS = lastMS
bpaddleX = 112
                    // нижняя ракетка
bpaddleY = 224
// назначим клавиши и кнопку мыши согласно нашим нуждам!
console "pushbind"

console "bind enter \"invoke @EnterWasPressed\"; bind mouse1 \"gamevar mouseButtonState 1\""

// нажать escape, чтобы выйти в программу
console "bind escape \"ui_closewindow 2:1\""

return                    

// =======================================================
// During
// Активно обновляем соответствующие переменные
// =======================================================

#switch @During
set currentMS = func_GameTime
                    // получаем стартовое время, запускаем счет
set delta = currentMS - lastMS
if (delta < %MSPERFRAME) return
                    // не делать ничего до начала фрейма
set lastMS = currentMS
// если был нажат Enter, показывать сообщение в течение 20 секунд
if (enterCounter> 0) enterCounter = enterCounter - 1
if (enterState == %PRESSED) enterCounter = 20
                    // показывать сообщение в течение 1 секунды
enterState = 0                    // (20 фреймов)
// если была нажата кнопка мыши 1, показывать сообщение в течение 20 секунд
if (mouseButtonCounter> 0) mouseButtonCounter = mouseButtonCounter - 1
if (mouseButtonState == %PRESSED) mouseButtonCounter = 20
mouseButtonState = 0
gosub @GetMouseInput
return

// =======================================================
// GetMouseInput
// Перемещение мыши!
// =======================================================

#switch @GetMouseInput
bpaddleX = (func_mousex * 209) / 640
return

// =======================================================
// After
// Обнуление
// =======================================================

#switch @After
console "popbind"
unset delta
unset lastMS
unset currentMS
unset bpaddleX
                    // нижняя ракетка
unset bpaddleY
return

// =======================================================
// EnterWasPressed
// Обработка нажатия Enter.
// =======================================================

#switch @EnterWasPressed
enterState = %PRESSED
return

// =======================================================
// MouseButtonWasPressed
// Обработка нажатия мыши.
// =======================================================

#switch @MouseButtonWasPressed
mouseButtonState = %PRESSED
return
==> Запуск
map test
loadape lesson10
invoke 2:1


Часть 5. Состояния.
Если коротко, смысл любой игры - вы получаете вводимые данные, обрабатываете их, меняете состояние чего - нибудь, основываясь на этих данных, и потом всё повторяется снова.

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

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

#define @StateTest "2:1"
#define @Before "2:2"
#define @During "2:3"
#define @After "2:4"
#define @LeftWasPressed "2:8"
#define @RightWasPressed "2:9"
#define @ProcessEvent "2:10"
#define @UpdateCounters "2:11"
#define @DoLeftThings "2:110"
#define @DoRightThings "2:120"
#define @MoveGuy "2:130"
#define @GuyWalkLeft "2:140"
#define @GuyWalkRight "2:150"

//-------------------------------------------------------
// КОНСТАНТЫ
//-------------------------------------------------------

#define %MSPERFRAME "50"
                    // 50 миллисекунд * 20 = 1 секунда
#define %FRAMESPERSECOND "20"
#define %LEFTSIDE "-100"
                    // может двигаться на 100 влево и 100 вправо
#define %RIGHTSIDE "100"
#define %PRESSED "1"
                    // кнопка или клавиша была нажата!

//-------------------------------------------------------

#define STATE_STANDINGLEFT "1"
#define STATE_STANDINGRIGHT "2"
#define STATE_WALKINGRIGHT "3"
#define STATE_WALKINGLEFT "4"

//-------------------------------------------------------

#define KEY_LEFT "1"
#define KEY_RIGHT "2"

// *****************************************************************************
// КОД
// *****************************************************************************

#window @StateTest
startswitch @Before
thinkswitch @During
finishswitch @After

//-------------------------------------------------------

width 232
height 256
title "Using States"
if (guyState == STATE_WALKINGRIGHT) image apeshape\sideactr\GUYWR$guyFrame$.PCX guyX,guyY

if(guyState == STATE_WALKINGLEFT) image apeshape\sideactr\GUYWL$guyFrame$.PCX guyX,guyY

if (guyState == STATE_STANDINGLEFT) image apeshape\sideactr\GUYSL$guyFrame$.PCX guyX,guyY

if (guyState == STATE_STANDINGRIGHT) image apeshape\sideactr\GUYSR$guyFrame$.PCX guyX,guyY

// =======================================================
// Before
// Инициализировать материал!
// =======================================================

#switch @Before
lastMS = func_GameTime
currentMS = lastMS
set guyX = 7 * 16
set guyY = 2 * 16
set guyFrame = 0
set guyState = STATE_STANDINGRIGHT
                    // начальная стойка
console "pushbind"
console "bind LEFTARROW invoke @LeftWasPressed"
console "bind RIGHTARROW invoke @RightWasPressed"
return
                    // в программу

// =====================================================
// During
// Активно обновляем соответствующие переменные
// =======================================================

#switch @During
set currentMS = func_GameTime
                    // получаем стартовое время, запускаем счет
set delta = currentMS - lastMS
if (delta < %MSPERFRAME) return
                    // не делать ничего до начала фрейма
set lastMS = currentMS
if (keyMeaning > 0) gosub @ProcessEvent
gosub @UpdateCounters
gosub @MoveGuy
return

// =======================================================
// ProcessEvent
// Если кто - то нажал клавишу, давайте мутить фишку!
// =======================================================

#switch @ProcessEvent
if (keyMeaning == KEY_RIGHT) gosub @DoRightThings
else if (keyMeaning == KEY_LEFT) gosub @DoLeftThings
set keyMeaning = 0
return

// =========================================================

#switch @DoRightThings
if (guyX > 200) guyState = STATE_STANDINGRIGHT
else guyState = STATE_WALKINGRIGHT
return

// ==========================================================

#switch @DoLeftThings
if (guyX < 8) guyState = STATE_STANDINGLEFT
else guyState = STATE_WALKINGLEFT
return

// =================================================
// Счетчики Обновления
// Анимировать чувачка, если он идет.
// =======================================================

#switch @UpdateCounters
if (guyState == STATE_WALKINGRIGHT || guyState == STATE_WALKINGLEFT)
{
guyFrame = guyFrame + 1
if (guyFrame > 3) guyFrame = 0
}
return
                    // готово!

// ========================================================
#switch @MoveGuy
if (guyState == STATE_WALKINGRIGHT) gosub @GuyWalkRight
else if (guyState == STATE_WALKINGLEFT) gosub @GuyWalkLeft
return

// ========================================================

#switch @GuyWalkRight
if (guyX < 200) guyX = guyX + 2
else
{
guyState = STATE_STANDINGRIGHT
guyFrame = 0
}
return

// =======================================================

#switch @GuyWalkLeft
if (guyX > 8) guyX = guyX - 2
else
{
guyState = STATE_STANDINGLEFT
guyFrame = 0
}
return

// =======================================================
// After
// Оставить место подобно, мы нашли это
// =======================================================

#switch @After
unset delta
unset lastMS
unset currentMS
unset guyX
unset guyY
console "popbind"
return

// =======================================================
// LeftWasPressed
// Нажата клавиша влево на доп клавиатуре
// =======================================================

#switch @LeftWasPressed
keyMeaning = KEY_LEFT
return

// =======================================================
// RightWasPressed
// Нажата клавиша вправо на доп клавиатуре
// =======================================================

#switch @RightWasPressed
keyMeaning = KEY_RIGHT
return
==> Проверка
map test
loadape Lesson11
goape 2:1


Часть 6. Циклы.

Чтобы делать реальные игры, вы нуждаетесь в циклах! Циклы - части кода которые выполяются снова и снова, обычно увеличивая счетчик или для проверки, чтобы видеть, если что-то изменяется. Как только условия завершения выполнены, цикла прерывается. В APE, нашу инструкцию выполнения цикла называют While.

While
while (условие) СписокИнструкций
<следующий оператор>
While проверяет истинно ли условие. Если да, то выполняется СписокИнструкций. В противном случае выполняется <следующая инструкция>.
СписокИнструкций может быть одной инструкцией или набором инструкций, заключенных внутри круглых скобок (точно так же как мы делали с if).
Вот пример:
counter = 0
while (counter <10)
{
A [$counter$] = 1
counter = counter + 1
}
Этот цикл установит значения от A [0] до A [9] равные 1. Как только счётчик counter становится = 10, то условие while больше не будет истинным, и цикл завершается.


Назад
Copyright Z'Ha'Dum LTD © 2024