В предыдущей части этой серии мы заставляли корабль игрока двигаться, захватчики перемещались и определяли, когда пуля игрока попала в захватчика. В этой заключительной части серии мы получим захватчиков, атакующих игрока, обработаем уровни и добавим возможность смерти игрока.
1. Стреляющие пули
Время от времени один из захватчиков стреляет пулей. Мы будем использовать таймер для этого. Добавьте следующий код в gamelevel.lua .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
function fireInvaderBullet()
if(#invadersWhoCanFire >0) then
local randomIndex = math.random(#invadersWhoCanFire)
local randomInvader = invadersWhoCanFire[randomIndex]
local tempInvaderBullet = display.newImage(«laser.png», randomInvader.x , randomInvader.y + invaderSize/2)
tempInvaderBullet.name = «invaderBullet»
scene.view:insert(tempInvaderBullet)
physics.addBody(tempInvaderBullet, «dynamic» )
tempInvaderBullet.gravityScale = 0
tempInvaderBullet.isBullet = true
tempInvaderBullet.isSensor = true
tempInvaderBullet:setLinearVelocity( 0,400)
table.insert(invaderBullets, tempInvaderBullet)
else
levelComplete()
end
end
|
В этой функции мы сначала проверяем, содержит ли таблица invadersWhoCanFire хотя бы один элемент. Если это так, то мы выполняем код в операторе if. В противном случае это означает, что уровень закончен, и мы levelComplete
функцию levelComplete
.
Всегда будет хотя бы один захватчик, который сможет запустить пулю, пока вы не убьете последнего захватчика, после чего таблица invadersWhoCanFire будет пустой.
Внутри оператора if мы генерируем случайное число randomIndex
зависимости от количества элементов в таблице randomIndex
. Затем мы выбираем этот элемент randomInvader
из таблицы randomInvader
.
Мы создаем изображение маркера, присваиваем ему свойство name
чтобы мы могли его идентифицировать позже, вставляем в сцену и устанавливаем те же свойства, что и в маркере игрока. Наконец, мы вставляем маркер в таблицу invaderBullets
чтобы мы могли ссылаться на него позже.
Теперь нам нужно настроить таймер. Добавьте к scene:show
метод scene:show
.
1
2
3
4
5
6
7
|
function scene:show(event)
if ( phase == «did» ) then
—SNIP—
Runtime:addEventListener( «collision», onCollision )
invaderFireTimer = timer.performWithDelay(1500, fireInvaderBullet,-1)
end
end
|
Каждые 1500 миллисекунд вызывается fireInvaderBullet
. Обратите внимание, что последний параметр, который мы передаем, равен -1
, что означает, что таймер повторяется вечно. Всякий раз, когда вы создаете таймер, который повторяется вечно, вы должны в конечном итоге отменить его. Мы делаем это в scene:hide
функцию, как показано ниже.
1
2
3
4
5
6
7
|
function scene:hide(event)
if ( phase == «will» ) then
—SNIP—
Runtime:removeEventListener( «collision», onCollision )
timer.cancel(invaderFireTimer)
end
end
|
2. Удаление пуль
Как пули игрока, пули захватчиков будут перемещаться за пределы экрана и продолжать двигаться, занимая ценную память. Чтобы исправить это, мы удаляем их так же, как мы сделали с пулями игрока.
01
02
03
04
05
06
07
08
09
10
11
|
function checkInvaderBulletsOutOfBounds()
if (#invaderBullets > 0) then
for i=#invaderBullets,1,-1 do
if(invaderBullets[i].y > display.contentHeight) then
invaderBullets[i]:removeSelf()
invaderBullets[i] = nil
table.remove(invaderBullets,i)
end
end
end
end
|
Этот код очень похож на проверку, если пули игрока выходят за пределы, поэтому я не буду подробно обсуждать его реализацию.
3. Обнаружение попадания
Шаг 1: Обнаружение столкновения
Следующий шаг — определить, попала ли пуля захватчика в игрока. Добавьте следующий код в функцию onCollision
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
function onCollision(event)
if ( event.phase == «began» ) then
—SNIP—
if(event.object1.name == «player» and event.object2.name == «invaderBullet») then
table.remove(invaderBullets,table.indexOf(invaderBullets,event.object2))
event.object2:removeSelf()
event.object2 = nil
if(playerIsInvincible == false) then
killPlayer()
end
return
end
if(event.object1.name == «invaderBullet» and event.object2.name == «player») then
table.remove(invaderBullets,table.indexOf(invaderBullets,event.object1))
event.object1:removeSelf()
event.object1 = nil
if(playerIsInvincible == false) then
killPlayer()
end
return
end
end
end
|
Как и раньше, мы не знаем, какие будут объекты event.object1
и event.object2
, поэтому мы используем два оператора if для проверки обеих ситуаций. Мы удаляем маркер захватчика из таблицы invaderBullets
, удаляем его с дисплея и устанавливаем в nil
. Если игрок не непобедим, мы его убиваем.
Шаг 2: Убить игрока
Когда мы убиваем игрока, мы даем ему непродолжительное время. Это дает пользователю время, чтобы вновь сосредоточиться на игре. Если переменная numberOfLives
равна 0
, мы знаем, что игра окончена и происходит переход к начальной сцене, где пользователь может начать новую игру.
01
02
03
04
05
06
07
08
09
10
|
function killPlayer()
numberOfLives = numberOfLives- 1;
if(numberOfLives <= 0) then
gameData.invaderNum = 1
composer.gotoScene(«start»)
else
playerIsInvincible = true
spawnNewPlayer()
end
end
|
Шаг 3: порождение нового игрока
Функция spawnNewPlayer
заставляет игрока spawnNewPlayer
и исчезать на несколько секунд. Это хороший эффект, чтобы пользователь знал, что корабль временно неуязвим.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
function spawnNewPlayer()
local numberOfTimesToFadePlayer = 5
local numberOfTimesPlayerHasFaded = 0
local function fadePlayer()
player.alpha = 0;
transition.to( player, {time=400, alpha=1, })
numberOfTimesPlayerHasFaded = numberOfTimesPlayerHasFaded + 1
if(numberOfTimesPlayerHasFaded == numberOfTimesToFadePlayer) then
playerIsInvincible = false
end
end
fadePlayer()
timer.performWithDelay(400, fadePlayer,numberOfTimesToFadePlayer)
end
|
Мы используем локальную функцию fadePlayer
, которая использует библиотеку переходов для изменения alpha
значения player
. Мы отслеживаем, сколько раз player
numberOfTimesToFadePlayer
и numberOfTimesToFadePlayer
, и устанавливаем для непобедимости игрока значение false
только мы достигаем numberOfTimesToFadePlayer
. Мы используем таймер для вызова функции fadePlayer
однако число раз numberOfTimesToFadePlayer
равно.
Запустите игру, чтобы проверить это. player
должен умереть, когда пуля захватчика попадает на корабль. Если три пули попали в корабль, вы должны быть доставлены на стартовую сцену, где вы можете начать новую игру.
Чтобы упростить тестирование, закомментируйте вызов moveInvaders
в функции gameLoop
как показано ниже.
1
2
3
4
5
|
function gameLoop()
checkPlayerBulletsOutOfBounds()
—moveInvaders()
checkInvaderBulletsOutOfBounds()
end
|
4. Завершение уровня
Если бы вам удалось убить каждого захватчика, игра вызвала levelComplete
функцию levelComplete
, которой пока не существует. Давай исправим это. Добавьте следующий блок кода.
1
2
3
4
5
6
7
8
9
|
function levelComplete()
gameData.invaderNum = gameData.invaderNum + 1
if(gameData.invaderNum <= gameData.maxLevels) then
composer.gotoScene(«gameover»)
else
gameData.invaderNum = 1
composer.gotoScene(«start»)
end
end
|
Мы увеличиваем gameData.invaderNum
и, если он меньше, чем gameData.maxLevels
, мы переходим к сцене перехода на новую страницу . В противном случае, игрок прошел все уровни, и мы сбросили gameData.invaderNum
на 1 . Мы переходим к стартовой сцене, где игрок может начать новую игру.
Простой способ проверить это — moveInvaders
вызов moveInvaders
в функции gameLoop
и использовать кнопки для перемещения корабля. Если это все еще слишком сложно, вы можете также закомментировать два вызова killPlayer
в методе onCollision
.
5. Игра окончена
Добавьте следующий код в gameover.lua, чтобы реализовать игру поверх сцены.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
local composer = require(«composer»)
local scene = composer.newScene()
local starFieldGenerator = require(«starfieldgenerator»)
local pulsatingText = require(«pulsatingtext»)
local nextLevelButton
local starGenerator
function scene:create( event )
local group = self.view
starGenerator = starFieldGenerator.new(200,group,5)
local invadersText = pulsatingText.new(«LEVEL COMPLETE», display.contentCenterX, display.contentCenterY-200,»Conquest», 20,group )
invadersText:setColor( 1, 1, 1 )
invadersText:pulsate()
nextLevelButton = display.newImage(«next_level_btn.png»,display.contentCenterX, display.contentCenterY)
group:insert(nextLevelButton)
end
function scene:show( event )
local phase = event.phase
composer.removeScene(«gamelevel» )
if ( phase == «did» ) then
nextLevelButton:addEventListener(«tap»,startNewGame)
Runtime:addEventListener ( «enterFrame», starGenerator)
end
end
function scene:hide(event )
local phase = event.phase
if ( phase == «will» ) then
Runtime:removeEventListener(«enterFrame», starGenerator)
nextLevelButton:removeEventListener(«tap»,startNewGame)
end
end
function startNewGame()
composer.gotoScene(«gamelevel»)
end
scene:addEventListener( «create», scene )
scene:addEventListener( «show», scene )
scene:addEventListener( «hide», scene )
return scene
|
Этот код очень похож на стартовую сцену, поэтому вы должны быть знакомы с ним сейчас.
6. Столкновение с захватчиком
Последняя проверка столкновений, которую мы должны выполнить, — это столкновение игрока и одного из захватчиков. Добавьте следующий блок кода в метод onCollision
который мы видели ранее.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
function onCollision(event)
—SNIP—
if ( event.phase == «began» ) then
—SNIP—
if(event.object1.name == «player» and event.object2.name == «invader») then
numberOfLives = 0
killPlayer()
end
if(event.object1.name == «invader» and event.object2.name == «player») then
numberOfLives = 0
killPlayer()
end
end
|
Как обычно, нам нужно проверить обе ситуации столкновения. Мы устанавливаем numberOfLives
в 0 и вызываем killPlayer
. Установив numberOfLives
в 0 и вызвав killPlayer
, игра заканчивается, и игра переходит в начальную сцену.
7. Дополнительные функции
Это завершает нашу игру, но я предлагаю вам попробовать расширить игру с помощью нескольких дополнительных функций. Например, вы можете отобразить жизнь игрока в HUD.
Я также включил изображение НЛО в исходные файлы. Вы можете попробовать сделать случайным появление НЛО, и если игрок ударит его пулями, даст им дополнительную жизнь.
Если вам нужна помощь с этими концепциями, ознакомьтесь с серией игр о самолетах на Tuts +.
Вывод
Если вы следили за этой серией, то теперь у вас должна быть полностью функциональная игра, похожая на оригинальные Space Invaders. Расширьте его и сделайте его своим. Я надеюсь, что вы нашли этот урок полезным и освоили некоторые новые методы. Спасибо за чтение.