Вступление
В предыдущем уроке этой серии мы начали реализовывать игровой процесс и уже успели заставить самолет двигаться по экрану. В этом уроке мы продолжим реализацию игрового процесса. Давайте startTimers
функцию startTimers
.
1. startTimers
Как видно из startTimers
функция startTimers
запускает таймеры. Добавьте следующий код в gamelevel.lua .
1
2
3
|
function startTimers()
end
|
Вызовите эту функцию в методе enterScene
как показано ниже.
1
2
3
4
5
6
|
function scene:enterScene( event )
local planeSound = audio.loadStream(«planesound.mp3»)
planeSoundChannel = audio.play( planeSound, {loops=-1} )
Runtime:addEventListener(«enterFrame», gameLoop)
startTimers()
end
|
2. firePlayerBullet
Функция firePlayerBullet
создает пулю для игрока.
1
2
3
4
5
|
function firePlayerBullet()
local tempBullet = display.newImage(«bullet.png»,(player.x+playerWidth/2) — bulletWidth,player.y-bulletHeight)
table.insert(playerBullets,tempBullet);
planeGroup:insert(tempBullet)
end
|
Здесь мы используем метод newImage
объекта newImage
для создания newImage
. Мы размещаем его таким образом, чтобы он находился в центре плоскости по оси x и в самом верху плоскости по оси y. Затем пуля вставляется в таблицу playerBullets
для последующего использования, а также в planeGroup
.
3. Вызов firePlayerBullet
Нам нужно периодически firePlayerBullet
функцию firePlayerBullet
чтобы убедиться, что самолет игрока автоматически запускает пули. Добавьте следующий фрагмент кода в функцию startTimers
.
1
2
3
|
function startTimers()
firePlayerBulletTimer = timer.performWithDelay(2000, firePlayerBullet ,-1)
end
|
Как видно из его имени, метод performWithDelay
вызывает указанную функцию по истечении определенного периода времени. Время firePlayerBullet
в миллисекундах, поэтому здесь мы вызываем функцию firePlayerBullet
каждые две секунды. Передав -1
в качестве третьего аргумента, таймер будет повторяться вечно.
Если вы сейчас тестируете игру, вы должны видеть, что каждые две секунды появляется пуля. Тем не менее, они еще не движутся. Мы позаботимся об этом в следующие несколько шагов.
4. movePlayerBullets
В movePlayerBullets
мы playerBullets
таблицу playerBullets
и меняем координату y
каждого playerBullets
. Сначала мы проверяем, есть ли в таблице playerBullets
. #
playerBullets
называется оператором длины, и он возвращает длину вызываемого объекта. Полезно знать, что оператор #
также работает со строками.
1
2
3
4
5
6
7
|
function movePlayerBullets()
if(#playerBullets > 0) then
for i=1,#playerBullets do
playerBullets[i].
end
end
end
|
Нам нужно вызвать movePlayerBullets
в функции gameLoop
как показано ниже.
1
2
3
4
5
6
|
function gameLoop()
—SNIP—
numberOfTicks = numberOfTicks + 1
movePlayer()
movePlayerBullets()
end
|
5. checkPlayerBulletsOutOfBounds
Когда пуля выходит за пределы экрана, она больше не относится к игре. Тем не менее, они все еще являются частью таблицы playerBullets
и продолжают двигаться как любая другая пуля в таблице. Это пустая трата ресурсов, и если игра будет продолжаться очень долго, это приведет к сотням или тысячам неиспользованных объектов.
Чтобы преодолеть это, мы отслеживаем маркеры и, как только они перемещаются за пределы экрана, мы удаляем их как из таблицы playerBullets
так и с экрана. Посмотрите на реализацию checkPlayerBulletsOutOfBounds
.
01
02
03
04
05
06
07
08
09
10
11
|
function checkPlayerBulletsOutOfBounds()
if(#playerBullets > 0) then
for i=#playerBullets,1,-1 do
if(playerBullets[i].y < -18) then
playerBullets[i]:removeSelf()
playerBullets[i] = nil
table.remove(playerBullets,i)
end
end
end
end
|
Важно отметить, что мы playerBullets
таблицу playerBullets
в обратном направлении. Если мы переместимся по таблице вперед, то при удалении одного из маркеров будет отключен индекс зацикливания, что приведет к ошибке. Зацикливая таблицу в обратном порядке, последний маркер уже обработан. Метод removeSelf
удаляет экранный объект и освобождает его память. В качестве наилучшей практики вы должны установить любые объекты removeSelf
nil
после вызова removeSelf
.
Мы вызываем эту функцию в функции gameLoop
.
1
2
3
4
5
6
|
function gameLoop()
—SNIP—
movePlayer()
movePlayerBullets()
checkPlayerBulletsOutOfBounds()
end
|
Если вы хотите проверить, работает ли эта функция должным образом, вы можете временно вставить оператор print("Removing Bullet")
сразу после установки для экранного объекта значения nil
.
6. generateIsland
Чтобы сделать игру более интересной, мы часто генерируем остров и перемещаем его вниз по экрану, чтобы создать видимость самолета, летящего над островами. Добавьте следующий фрагмент кода для функции generateIsland
.
1
2
3
4
5
|
function generateIsland()
local tempIsland = display.newImage(«island1.png», math.random(0,display.contentWidth — islandWidth),-islandHeight)
table.insert(islands,tempIsland)
islandGroup:insert( tempIsland )
end
|
Мы снова newImage
метод newImage
и позиционируем остров, устанавливая отрицательное значение для islandHeight
. Для позиции x
мы используем метод math.random
чтобы сгенерировать число между 0
и contentWidth
минус islandWidth
. Причина, по которой мы вычитаем ширину острова, состоит в том, чтобы убедиться, что остров полностью на экране. Если бы мы не вычитали ширину острова, был бы шанс, что часть острова не была бы на экране.
Нам нужно запускать таймер для генерации острова очень часто. Добавьте следующий фрагмент к функции startTimers
мы создали ранее. Как видите, мы создаем остров каждый пять секунд На следующем шаге мы заставим острова двигаться.
1
2
3
4
|
function startTimers()
firePlayerBulletTimer = timer.performWithDelay(2000, firePlayerBullet ,-1)
generateIslandTimer = timer.performWithDelay( 5000, generateIsland ,-1)
end
|
7. moveIslands
Реализация moveIslands
практически идентична функции movePlayerBullets
. Мы проверяем, содержит ли таблица islands
какие-либо острова, и, если она есть, мы перебираем ее и немного перемещаем каждый остров.
1
2
3
4
5
6
7
|
function moveIslands()
if(#islands > 0) then
for i=1, #islands do
islands[i].y = islands[i].y + 3
end
end
end
|
8. checkIslandsOutOfBounds
Точно так же, как мы проверяем, переместились ли пули игрока за пределы экрана, мы проверяем, не переместились ли какие-либо острова за пределы экрана. Поэтому реализация checkIslandsOutOfBounds
должна быть вам знакома. Мы проверяем, больше ли положение островов y
чем display.contentHeight
и если это так, мы знаем, что остров переместился за пределы экрана и поэтому может быть удален.
01
02
03
04
05
06
07
08
09
10
11
|
function checkIslandsOutOfBounds()
if(#islands > 0) then
for i=#islands,1,-1 do
if(islands[i].y > display.contentHeight) then
islands[i]:removeSelf()
islands[i] = nil
table.remove(islands,i)
end
end
end
end
|
9. generateFreeLife
Время от времени у игрока есть шанс получить свободную жизнь. Сначала мы генерируем изображение свободной жизни, и если игрок сталкивается с изображением, он получает дополнительную жизнь. Игрок может иметь максимум шесть жизней.
1
2
3
4
5
6
7
8
|
function generateFreeLife ()
if(numberOfLives >= 6) then
return
end
local freeLife = display.newImage(«newlife.png», math.random(0,display.contentWidth — 40), 0);
table.insert(freeLifes,freeLife)
planeGroup:insert(freeLife)
end
|
Если у игрока уже есть шесть жизней, мы ничего не делаем, возвращаясь рано из функции. Если нет, мы создаем новый образ жизни и добавляем его на экран. Подобно тому, как мы позиционировали острова ранее, мы устанавливаем изображение в отрицательную позицию y
и генерируем случайное значение для позиции x
изображения. Затем мы вставляем его в таблицу freeLifes
чтобы иметь возможность ссылаться на него позже.
Нам нужно вызывать эту функцию очень часто. Добавьте следующий фрагмент к функции startTimers
.
1
2
3
4
5
|
function startTimers()
firePlayerBulletTimer = timer.performWithDelay(2000, firePlayerBullet ,-1)
generateIslandTimer = timer.performWithDelay( 5000, generateIsland ,-1)
generateFreeLifeTimer = timer.performWithDelay(7000,generateFreeLife, — 1)
end
|
10. moveFreeLives
Реализация moveFreeLifes
должна выглядеть знакомо. Мы freeLifes
таблицу freeLifes
и перемещаем каждое изображение в ней.
1
2
3
4
5
6
7
|
function moveFreeLifes()
if(#freeLifes > 0) then
for i=1,#freeLifes do
freeLifes[i].y = freeLifes[i].y +5
end
end
end
|
Все, что нам нужно сделать, это вызвать moveFreeLifes
в функции gameLoop
.
1
2
3
4
5
|
function gameLoop()
—SNIP—
checkIslandsOutOfBounds()
moveFreeLifes()
end
|
11. checkFreeLifesOutOfBounds
Следующий фрагмент кода уже должен выглядеть знакомым. Мы проверяем, переместились ли какие-либо изображения в таблице freeLifes
пределы экрана, и удаляем те, которые были.
01
02
03
04
05
06
07
08
09
10
11
|
function checkFreeLifesOutOfBounds()
if(#freeLifes > 0) then
for i=#freeLifes,1,-1 do
if(freeLifes[i].y > display.contentHeight) then
freeLifes[i]:removeSelf()
freeLifes[i] = nil
table.remove(freeLifes,i)
end
end
end
end
|
Мы называем эту функцию в функции gameLoop
.
1
2
3
4
5
6
|
function gameLoop()
—SNIP—
checkIslandsOutOfBounds()
moveFreeLifes()
checkFreeLifesOutOfBounds()
end
|
12. hasCollided
Нам нужно знать, когда игровые объекты сталкиваются друг с другом, такие как плоскость игрока и изображения свободной жизни, пули и плоскости и т. Д. В то время как Corona предлагает очень надежный физический движок, который может легко обрабатывать столкновения между отображаемыми объектами. для нас это добавляет немного накладных расходов при вычислениях, которые двигатель должен выполнять каждый кадр.
Для целей этой игры мы будем использовать простую систему обнаружения столкновений в ограничивающей рамке. Эта функция делает так, чтобы прямоугольники или ограничивающие рамки вокруг двух объектов не перекрывались. Если они это делают, объекты сталкиваются. Эта логика реализована в функции hasCollided
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
function hasCollided( obj1, obj2 )
if ( obj1 == nil ) then
return false
end
if ( obj2 == nil ) then
return false
end
local left = obj1.contentBounds.xMin <= obj2.contentBounds.xMin and obj1.contentBounds.xMax >= obj2.contentBounds.xMin
local right = obj1.contentBounds.xMin >= obj2.contentBounds.xMin and obj1.contentBounds.xMin <= obj2.contentBounds.xMax
local up = obj1.contentBounds.yMin <= obj2.contentBounds.yMin and obj1.contentBounds.yMax >= obj2.contentBounds.yMin
local down = obj1.contentBounds.yMin >= obj2.contentBounds.yMin and obj1.contentBounds.yMin <= obj2.contentBounds.yMax
return (left or right) and (up or down)
end
|
Я нашел этот фрагмент кода на сайте CoronaLabs . Это работает очень хорошо, потому что игровые объекты в нашей игре прямоугольные. Если вы работаете с объектом, который не является прямоугольным, то вам лучше воспользоваться физическим движком Corona, поскольку его обнаружение столкновений очень хорошо оптимизировано для этого.
13. checkPlayerCollidesWithFreeLife
Мы хотим проверить, столкнулся ли самолет игрока со свободным объектом жизни. Если да, то мы дарим игроку свободную жизнь.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
function checkPlayerCollidesWithFreeLife()
if(#freeLifes > 0) then
for i=#freeLifes,1,-1 do
if(hasCollided(freeLifes[i], player)) then
freeLifes[i]:removeSelf()
freeLifes[i] = nil
table.remove(freeLifes, i)
numberOfLives = numberOfLives + 1
hideLives()
showLives()
end
end
end
end
|
В функции checkPlayerCollidesWithFreeLife
мы freeLives
таблицу freeLives
обратном направлении по той же причине, что я описал ранее. Мы вызываем функцию hasCollided
и передаем текущее изображение и плоскость игрока. Если два объекта сталкиваются, мы удаляем образ свободной жизни, увеличиваем переменную hideLives
и showLives
функции hideLives
и showLives
.
Мы вызываем эту функцию в функции gameLoop
.
1
2
3
4
5
6
|
function gameLoop()
—SNIP—
moveFreeLifes()
checkFreeLifesOutOfBounds()
checkPlayerCollidesWithFreeLife()
end
|
14. hideLives
Функция hideLives
livesImages
таблицу livesImages
и устанавливает для свойства isVisible
каждого изображения жизни значение false
.
1
2
3
4
5
|
function hideLives()
for i=1, 6 do
livesImages[i].isVisible = false
end
end
|
15. showLives
Функция showLives
livesImages
таблицу livesImages
и устанавливает для свойства isVisible
каждого изображения isVisible
true
.
1
2
3
4
5
|
function showLives()
for i=1, numberOfLives do
livesImages[i].isVisible = true;
end
end
|
Вывод
Это завершает третью часть этой серии. В следующей и последней части этой серии мы создадим вражеские самолеты и доработаем игровой процесс. Спасибо за чтение и увидимся там.