Статьи

Создайте игру Space Invaders в Corona: завершающий геймплей

Конечный продукт
Что вы будете создавать

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

Время от времени один из захватчиков стреляет пулей. Мы будем использовать таймер для этого. Добавьте следующий код в 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

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

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

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

Следующий шаг — определить, попала ли пуля захватчика в игрока. Добавьте следующий код в функцию 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 . Если игрок не непобедим, мы его убиваем.

Когда мы убиваем игрока, мы даем ему непродолжительное время. Это дает пользователю время, чтобы вновь сосредоточиться на игре. Если переменная 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

Функция 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

Если бы вам удалось убить каждого захватчика, игра вызвала 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 .

Добавьте следующий код в 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

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

Последняя проверка столкновений, которую мы должны выполнить, — это столкновение игрока и одного из захватчиков. Добавьте следующий блок кода в метод 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 , игра заканчивается, и игра переходит в начальную сцену.

Это завершает нашу игру, но я предлагаю вам попробовать расширить игру с помощью нескольких дополнительных функций. Например, вы можете отобразить жизнь игрока в HUD.

Я также включил изображение НЛО в исходные файлы. Вы можете попробовать сделать случайным появление НЛО, и если игрок ударит его пулями, даст им дополнительную жизнь.

Если вам нужна помощь с этими концепциями, ознакомьтесь с серией игр о самолетах на Tuts +.

Если вы следили за этой серией, то теперь у вас должна быть полностью функциональная игра, похожая на оригинальные Space Invaders. Расширьте его и сделайте его своим. Я надеюсь, что вы нашли этот урок полезным и освоили некоторые новые методы. Спасибо за чтение.