Crea un gioco di combattimento aereo in Corona gioco di finitura

Cosa starai creando

introduzione

Nella quarta e ultima parte di questa serie, continuiamo dove abbiamo lasciato nel tutorial precedente. Creeremo gli aerei nemici che il giocatore deve evitare o sparare, e creeremo anche un gioco sullo schermo.

1. generateEnemys

Il generateEnemys la funzione genera un numero tra tre e Sette, e chiama il generateEnemyPlane funzione ogni due secondi per molte volte numberOfEnemysToGenerate è uguale a. Immettere il seguente frammento di codice su gamelevel.lua.

function generateEnemys () numberOfEnemysToGenerate = math.random (3,7) timer.performWithDelay (2000, generateEnemyPlane, numberOfEnemysToGenerate) end

Abbiamo anche bisogno di invocare questa funzione nel enterScene metodo come mostrato di seguito.

function scene: enterScene (evento) --SNIP-- Runtime: addEventListener ("enterFrame", gameLoop) startTimers () generateEnemys () fine

Vediamo quale è l'implementazione di generateEnemyPlane sembra.

2. generateEnemyPlane

Il generateEnemyPlane la funzione genera un aereo nemico. Ci sono tre tipi di aerei nemici in questo gioco.

  • Regolare , si muove lungo lo schermo in linea retta
  • Vacillare, si muove in un modello d'onda sull'asse x
  • inseguitore, insegue l'aereo del giocatore
function generateEnemyPlane () if (gameOver ~ = true) then randomGridSpace = math.random (11) local randomEnemyNumber = math.random (3) local tempEnemy if (planeGrid [randomGridSpace] ~ = 0) then generateEnemyPlane () restituisce else if ( randomEnemyNumber == 1) then tempEnemy = display.newImage ("enemy1.png", (randomGridSpace * 65) -28, -60) tempEnemy.type = "regular" elseif (randomEnemyNumber == 2) then tempEnemy = display.newImage ( "enemy2.png", display.contentWidth / 2 -playerWidth / 2, -60) tempEnemy.type = "waver" else tempEnemy = display.newImage ("enemy3.png", (randomGridSpace * 65) -28, -60) tempEnemy.type = "chaser" end planeGrid [randomGridSpace] = 1 table.insert (enemyPlanes, tempEnemy) planeGroup: insert (tempEnemy) numberOfEnemysGenerated = numberOfEnemysGenerated + 1; end if (numberOfEnemysGenerated == numberOfEnemysToGenerate) then numberOfEnemysGenerated = 0; resetPlaneGrid () timer.performWithDelay (2000, generateEnemys, 1) end end end

Prima controlliamo per assicurarci che il gioco non sia ancora finito. Quindi generiamo a randomGridSpace, un numero tra 111, e un casuale randomEnemyNumber, un numero tra 1 e 3. Il randomGridSpace viene utilizzato per posizionare il piano in uno degli undici slot nella parte superiore dello schermo sull'asse x. Se pensi che l'area di gioco sia divisa in undici sezioni, allora vogliamo solo posizionare nuovi piani in uno slot che non è stato ancora preso da un altro piano. Il planeGrid la tabella contiene undici 0e quando posizioniamo un nuovo piano in uno degli slot, impostiamo la posizione corrispondente nella tabella 1 per indicare che lo slot è stato preso da un aereo.

Controlliamo se l'indice del randomGridSpace nella tabella non è uguale a 0. Se non lo è, sappiamo che attualmente lo slot è occupato e non dovremmo continuare, quindi chiamiamo generateEnemyPlane e ritorno dalla funzione.

Successivamente, controlliamo cosa randomEnemyNumber è uguale a e impostato tempEnemy a una delle tre immagini nemiche, le diamo anche una proprietà di entrambi regolare, vacillare, o inseguitore. Poiché Lua è un linguaggio dinamico, possiamo aggiungere nuove proprietà a un oggetto in fase di runtime. Quindi impostiamo qualsiasi indice uguale a randomGridSpace a 1 nel planeGrid tavolo.

Inseriamo tempEnemy nel enemyPlanes tabella per riferimento e incremento successivi numberOfEnemysGenerated. Se numberOfEnemysGenerated è uguale a  numberOfEnemysToGenerate, abbiamo ripristinato numberOfEnemysGenerated a 0, invocare resetPlaneGrid, e impostare un timer che chiamerà generateEnemys di nuovo dopo due secondi. Questo processo si ripete fintanto che il gioco non è finito.

3. moveEnemyPlanes

Come avrai intuito, il moveEnemyPlanes la funzione è responsabile per lo spostamento degli aerei nemici. A seconda del piano genere, viene chiamata la funzione appropriata.

function moveEnemyPlanes () if (#enemyPlanes> 0) quindi per i = 1, #enemyPlanes fa if (enemyPlanes [i] .type == "regular") quindi moveRegularPlane (enemyPlanes [i]) elseif (enemyPlanes [i] .type == "waver") quindi moveWaverPlane (enemyPlanes [i]) else moveChaserPlane (enemyPlanes [i]) end end end end

Questa funzione deve essere invocata in gameLoop funzione.

function gameLoop () --SNIP-- checkFreeLifesOutOfBounds () checkPlayerCollidesWithFreeLife () moveEnemyPlanes () fine

4. moveRegularPlane

Il moveRegularPlane sposta semplicemente l'aereo lungo lo schermo lungo l'asse y.

function moveRegularPlane (plane) plane.y = plane.y + 4 end

5. moveWaverPlane

Il moveWaverPlane la funzione sposta l'aereo lungo lo schermo lungo l'asse y e, in un modello d'onda, attraverso l'asse x. Questo si ottiene usando il cos funzione della biblioteca matematica di Lua.

Se questo concetto ti è estraneo, Michael James Williams ha scritto una grande introduzione a Sinusoidal Motion. Si applicano gli stessi concetti, l'unica differenza è che stiamo usando coseno. Dovresti pensare seno quando si ha a che fare con l'asse y e coseno quando si ha a che fare con l'asse x.

function moveWaverPlane (plane) plane.y = plane.y + 4 plane.x = (display.contentWidth / 2) + 250 * math.cos (numberOfTicks * 0.5 * math.pi / 30) end

Nel frammento sopra, usiamo il numberOfTicks variabile. Abbiamo bisogno di incrementare questo ogni volta il gameLoop la funzione è chiamata. Aggiungi quanto segue come prima linea nel file gameLoop funzione.

function gameLoop () numberOfTicks = numberOfTicks + 1 end

6. moveChaserPlane

Il moveChaserPlane la funzione ha il piano caccia il giocatore. Si sposta lungo l'asse y a una velocità costante e si sposta verso la posizione del giocatore sull'asse x. Dai un'occhiata all'implementazione di moveChaserPlane per chiarire.

function moveChaserPlane (plane) if (plane.x < player.x)then plane.x =plane.x +4 end if(plane.x > player.x) quindi plane.x = plane.x - 4 end plane.y = plane.y + 4 end

Se stai testando il gioco ora, dovresti vedere gli aerei muoversi verso il basso sullo schermo.

7. fireEnemyBullets

Ogni tanto, vogliamo che gli aerei nemici sparino un proiettile. Non vogliamo che sparino tutti allo stesso tempo, quindi, scegliamo solo un paio di aerei da sparare.

function fireEnemyBullets () if (#enemyPlanes> = 2) then numberOfEnemyPlanesToFire = math.floor (# enemyPlanes / 2) local tempEnemyPlanes = table.copy (enemyPlanes) function locale fireBullet () local randIndex = math.random (#tempEnemyPlanes) local tempBullet = display.newImage ("bullet.png", (tempEnemyPlanes [randIndex] .x + playerWidth / 2) + bulletWidth, tempEnemyPlanes [randIndex] .y + playerHeight + bulletHeight) tempBullet.rotation = 180 planeGroup: tabella insert (tempBullet) .Insert (enemyBullets, tempBullet); table.remove (tempEnemyPlanes, randIndex) end per i = 0, numberOfEnemyPlanesToFire do fireBullet () end end end

Per prima cosa controlliamo per accertarci che enemyPlanes la tabella ha più di due piani in essa. Se lo fa, otteniamo il numberOfEnemyPlanes sparare prendendo la lunghezza del enemyPlanes tabella, dividerlo per due e arrotondarlo verso il basso. Facciamo anche una copia del enemyPlanes tabella, in modo che possiamo manipolarlo separatamente.

Il fireBullet la funzione sceglie un piano dal tempEnemyPlanes tavolo e fa sparare un proiettile all'aereo. Generiamo un numero casuale basato sulla lunghezza del file tempEnemyPlanes tabella, creare un'immagine bullet e posizionarla utilizzando qualsiasi piano randIndex nel tempEnemyPlanes tavolo. Quindi rimuoviamo quell'aereo dalla tabella temporanea per assicurarci che non venga scelto di nuovo la prossima volta fireBullet è chiamato.

Ripetiamo questo processo comunque molte volte numerOfEnemyPlanesToFire è uguale a e chiama il fireBullet funzione.

Abbiamo bisogno di avviare il timer che chiama questa funzione ogni tanto. Per fare ciò, aggiungi quanto segue al startTimers funzione.

function startTimers () firePlayerBulletTimer = timer.performWithDelay (2000, firePlayerBullet, -1) generateIslandTimer = timer.performWithDelay (5000, generateIsland, -1) generateFreeLifeTimer = timer.performWithDelay (7000, generateFreeLife, - 1) fireEnemyBulletsTimer = timer.performWithDelay (2000 , fireEnemyBullets, -1) end

8. moveEnemyBullets

Abbiamo anche bisogno di spostare i proiettili nemici che sono sullo schermo. Questo è abbastanza semplice utilizzando il seguente frammento di codice.

function moveEnemyBullets () if (#enemyBullets> 0) quindi per i = 1, # enemyBullets do enemyBullets [i]. y = enemyBullets [i] .y + 7 end end end

Invoca questa funzione nel gameLoop funzione.

function gameLoop () --SNIP-- checkPlayerCollidesWithFreeLife () moveEnemyPlanes () moveEnemyBullets () end

9. checkEnemyBulletsOutOfBounds

Oltre a spostare i proiettili nemici, dobbiamo controllare quando i proiettili nemici sono andati fuori campo e rimuoverli quando lo fanno. L'implementazione di checkEnemyBulletsOutOfBounds dovrebbe sentirsi familiare ormai.

function checkEnemyBulletsOutOfBounds () if (#enemyBullets> 0) quindi per i = # enemyBullets, 1, -1 do if (enemyBullets [i] .y> display.contentHeight quindi enemyBullets [i]: removeSelf () enemyBullets [i] = nil table.remove (enemyBullets, i) end end end end

Invoca questa funzione nel gameLoop funzione.

function gameLoop () --SNIP-- moveEnemyBullets () checkEnemyBulletsOutOfBounds () end

10. checkEnemyPlanesOutOfBounds

Dovremmo anche controllare se gli aerei nemici sono stati spostati fuori dallo schermo.

function checkEnemyPlanesOutOfBounds () if (#enemyPlanes> 0) quindi per i = # enemyPlanes, 1, -1 do if (enemyPlanes [i] .y> display.contentHeight quindi enemyPlanes [i]: removeSelf () enemyPlanes [i] = nil table.remove (enemyPlanes, i) end end end end

Invoca questa funzione nel gameLoop funzione

function gameLoop () --SNIP-- moveEnemyBullets () checkEnemyBulletsOutOfBounds () checkEnemyPlanesOutOfBounds () end

11. checkPlayerBulletsCollideWithEnemyPlanes

Il checkPlayerBulletCollidesWithEnemyPlanes la funzione usa il hasCollided funzione per verificare se qualcuno dei proiettili del giocatore si è scontrato con uno qualsiasi degli aerei nemici.

function checkPlayerBulletsCollideWithEnemyPlanes () if (#playerBullets> 0 e #enemyPlanes> 0) quindi per i = # playerBullets, 1, -1 do per j = # enemyPlanes, 1, -1 do if (hasCollided (playerBullets [i], enemyPlanes [ j])) then playerBullets [i]: removeSelf () playerBullets [i] = nil table.remove (playerBullets, i) generateExplosion (enemyPlanes [j] .x, enemyPlanes [j] .y) enemyPlanes [j]: removeSelf ( ) enemyPlanes [j] = nil table.remove (enemyPlanes, j) local explosion = audio.loadStream ("explosion.mp3") local backgroundMusicChannel = audio.play (esplosione, fadein = 1000) fine fine fine fine fine

Questa funzione utilizza due nidificati per loop per verificare se gli oggetti sono entrati in collisione. Per ciascuno dei playerBullets, corriamo attraverso tutti gli aerei nel enemyPlanes tavolo e chiama il hasCollided funzione. Se c'è una collisione, rimuoviamo il proiettile e l'aereo, chiamiamo il generateExplosion funzione e carica e riproduce un suono esplosivo.

Invoca questa funzione nel gameLoop funzione.

function gameLoop () --SNIP-- checkEnemyBulletsOutOfBounds () checkEnemyPlanesOutOfBounds () checkPlayerBulletsCollideWithEnemyPlanes () fine

12. generateExplosion

Il generateExplosion la funzione utilizza la classe SpriteObject di Corona. Gli sprite consentono sequenze animate di fotogrammi che si trovano su fogli di immagini o di sprite. Raggruppando le immagini in una singola immagine, puoi estrarre determinati fotogrammi da quella immagine e creare una sequenza di animazione.

function generateExplosion (xPosition, yPosition) opzioni locali = width = 60, height = 49, numFrames = 6 local explosionSheet = graphics.newImageSheet ("explosion.png", options) local sequenceData = name = "explosion", start = 1, count = 6, time = 400, loopCount = 1 local explosionSprite = display.newSprite (explosionSheet, sequenceData) explosionSprite.x = xPosition explosionSprite.y = yPosition explosionSprite: addEventListener ("sprite", explosionListener) explosionSprite: play () fine

Il newImageSheet il metodo prende come parametri il percorso dell'immagine e una tabella di opzioni per il Foglio Sprite. Le opzioni che impostiamo sono le larghezza, il altezza, e il numFrames, quante immagini singole compongono questo foglio. Ci sono sei immagini di esplosione separate come mostrato nell'immagine qui sotto.

Successivamente, impostiamo un tavolo, sequenceData, che è necessario dal SpriteObject. Abbiamo impostato il inizio proprietà a 1, il contare a 6, e tempo per 400.  Il inizio proprietà è il frame su cui inizierà l 'animazione, il contare è quanti fotogrammi include l'animazione e il tempo proprietà è quanto tempo impiega l'animazione per giocare.

Quindi creiamo il SpriteObject passando nel explosionSheet e sequenceData, imposta le posizioni xey e aggiungi un listener allo sprite. Il listener verrà utilizzato per rimuovere lo sprite una volta completata l'animazione.

13. explosionListener

Il explosionListener la funzione è usata per rimuovere lo sprite. Se la evento'S fase la proprietà è uguale a conclusa, allora sappiamo che lo sprite ha terminato la sua animazione e possiamo rimuoverlo.

function explosionListener (event) if (event.phase == "ended") then explosion locale = event.target explosion: removeSelf () explosion = nil end end

14. checkEnemyBulletsCollideWithPlayer

Il checkEnemyBulletsCollideWithPlayer controlla se qualcuno dei proiettili nemici si è scontrato con l'aereo del giocatore.

function checkEnemyBulletsCollideWithPlayer () if (#enemyBullets> 0) quindi per i = # enemyBullets, 1, -1 do if (hasCollided (enemyBullets [i], player)) then enemyBullets [i]: removeSelf () enemyBullets [i] = nil table.remove (enemyBullets, i) if (playerIsInvincible == false) then killPlayer () fine fine fine fine fine

Passiamo attraverso il enemyBullets tabella e controllare se qualcuno di essi è entrato in collisione con il giocatore. Se è vero, rimuoviamo quel particolare proiettile e, se playerIsInvincible è falso, noi invochiamo killPlayer.

Invoca questa funzione nel gameLoop funzione.

function gameLoop () --SNIP-- checkEnemyPlanesOutOfBounds () checkPlayerBulletsCollideWithEnemyPlanes () checkEnemyBulletsCollideWithPlayer () end

15. killPlayer

Il killPlayer la funzione è responsabile per controllare se il gioco è finito e generare un nuovo giocatore se non lo è.

function killPlayer () numberOfLives = numberOfLives- 1; if (numberOfLives == 0) then gameOver = true doGameOver () else spawnNewPlayer () hideLives () showLives () playerIsInvincible = true end end

Iniziamo a decrementare numberOfLives di 1, e, se è uguale a 0, noi chiamiamo il gioco finito funzione. Il giocatore ha una vita sinistra, noi chiamiamo spawnNewPlayer, seguito da hideLives, showLives, e impostare playerIsInvincible a vero.

16. doGameOver

Il doGameOver la funzione dice allo storyboard di andare al gioco finito scena.

function doGameOver () storyboard.gotoScene ("gameover") fine

17. spawnNewPlayer

Il spawnNewPlayer la funzione è responsabile della generazione di un nuovo giocatore dopo la sua morte. L'aereo del giocatore lampeggia per alcuni secondi per mostrare che è temporaneamente invincibile.

function spawnNewPlayer () local numberOfTimesToFadePlayer = 5 local numberOfTimesPlayerHasFaded = 0 local function fadePlayer () player.alpha = 0; transition.to (player, time = 200, alpha = 1) numberOfTimesPlayerHasFaded = numberOfTimesPlayerHasFaded + 1 if (numberOfTimesPlayerHasFaded == numberOfTimesToFadePlayer) then playerIsInvincible = false end end timer.performWithDelay (400, fadePlayer, numberOfTimesToFadePlayer) end

Per far lampeggiare l'aereo del giocatore, lo sbiadiamo in entrata e in uscita cinque volte. Nel fadePlayer funzione, impostiamo l'aereo alfa proprietà a 0, che lo rende trasparente. Quindi usiamo la libreria di transizione per dissolvere il alfa di nuovo a 1 per un periodo di 200 millisecondi. Il a metodo del transizione oggetto prende una tabella di opzioni. Nel nostro esempio, la tabella delle opzioni include un tempo in millisecondi e la proprietà che desideriamo animare, alfa, e il valore desiderato, 1.

Aumentiamo numberOfTimesThePlayerHasFaded e controlla se è uguale al numero di volte che volevamo che il giocatore svanisse. Abbiamo quindi impostato playerIsInvincible a falso. Usiamo un timer per chiamare il fadePlayer funzione tuttavia molte volte numberOfTimerToFadePlayer è uguale a.

C'è un modo per fare tutto questo senza usare il timer e cioè usando il transizione'S iterazioni proprietà in combinazione con il suo onComplete handler. Leggi la documentazione per saperne di più su questo approccio alternativo.

18. checkEnemyPlaneCollidesWithPlayer

C'è un altro controllo di collisione che dovremmo fare e cioè vedere se un aereo nemico si scontra con l'aereo del giocatore.

function checkEnemyPlaneCollideWithPlayer () if (#enemyPlanes> 0) quindi per i = # enemyPlanes, 1, -1 do if (hasCollided (enemyPlanes [i], player)) then enemyPlanes [i]: removeSelf () enemyPlanes [i] = nil table.remove (enemyPlanes, i) if (playerIsInvincible == false) then killPlayer () fine fine fine fine fine

Attraversiamo gli aerei nemici e vediamo se qualcuno di loro si scontra con l'aereo del giocatore. Se è vero, rimuoviamo quell'aereo nemico e chiamiamo killPlayer. Se pensi che renda il gioco più interessante, potresti anche generare un'esplosione qui.

19. exitScene

Quando il gioco è finito, passiamo al gioco finito scena. Ricorda da prima nel tutorial, il exitScene la funzione è dove si rimuovono gli ascoltatori di eventi, si fermano i timer e si interrompe l'audio riprodotto.

function scene: exitScene (evento) local group = self.view rectUp: removeEventListener ("touch", movePlane) rectDown: removeEventListener ("touch", movePlane) rectLeft: removeEventListener ("touch", movePlane) rectRight: removeEventListener ("touch" , movePlane) audio.stop (planeSoundChannel) audio.dispose (planeSoundChannel) Runtime: removeEventListener ("enterFrame", gameLoop) cancelTimers () fine scena: addEventListener ("exitScene", scena) 

In pratica stiamo annullando ciò che abbiamo fatto nel enterScene funzione. Chiamiamo il disporre metodo sul Audio oggetto di rilasciare la memoria associata al canale audio. chiamata Stop da solo non rilascia la memoria.

20. cancelTimers

Come indica il nome, il cancelTimers la funzione fa il contrario di  startTimers, cancella tutti i timer.

function cancelTimers () timer.cancel (firePlayerBulletTimer) timer.cancel (generateIslandTimer) timer.cancel (fireEnemyBulletsTimer) fine.cancel (generateFreeLifeTimer) fine

21. Game Over Scene

È tempo di creare il gioco finito scena. Inizia aggiungendo un nuovo file Lua al tuo progetto chiamato gameover.lua, e aggiungere il seguente codice ad esso.

storyboard locale = require ("storyboard") local scene = storyboard.newScene () local gameOverText local newgameButton return scene 

22. createScene

Aggiungi il seguente a gameover.lua sopra scena di ritorno. Da qui in poi, tutto il codice dovrebbe essere posizionato sopra il scena di ritorno dichiarazione.

scena della funzione: createScene (evento) local group = self.view local background = display.newRect (0, 0, display.contentWidth, display.contentHeight) background: setFillColor (0, .39, .75) gruppo: insert (background) gameOverText = display.newText ("Game Over", display.contentWidth / 2.400, native.systemFont, 16) gameOverText: setFillColor (1, 1, 0) gameOverText.anchorX = .5 gameOverText.anchorY = .5 group: insert (gameOverText ) newGameButton = display.newImage ("newgamebutton.png", 264,670) gruppo: insert (newGameButton) newGameButton.isVisible = false end

Come abbiamo fatto nelle due scene precedenti, diamo il gioco finito scena uno sfondo blu. Quindi creiamo a TextObject istanza chiamando newText sopra display. Il newText metodo prende alcune opzioni, il testo per l'oggetto, la sua posizione e il tipo di carattere da usare. Diamo un colore giallo invocando SetFillColor, passando in valori RGB come percentuali. Infine, creiamo un pulsante e lo nascondiamo per il momento.

23. enterScene

Quando lo storyboard è completamente passato al gioco finito scena, il enterScene il metodo è chiamato.

Nel enterScene, rimuoviamo la scena precedente dallo storyboard. Usiamo il metodo di convenienza scaleTo dalla Transition Library per scalare il gameOverText di un fattore di 4. Aggiungiamo un onComplete ascoltatore della transizione che chiama ilshowButton funzione una volta completata la transizione. Infine, aggiungiamo un listener di eventi tap al pulsante di gioco che richiama il startNewGame funzione.

function scene: enterScene (evento) local group = self.view storyboard.removeScene ("gamelevel") transition.scaleTo (gameOverText, xScale = 4.0, yScale = 4.0, time = 2000, onComplete = showButton) newGameButton: addEventListener (" tocca ", startNewGame) fine

24. showButton

Il showButton la funzione nasconde il gameOverText e mostra il newGameButton.

 function showButton () gameOverText.isVisible = false newGameButton.isVisible = true end

25. startNewGame

Il startNewGame la funzione dice allo storyboard di passare a gamelevel scena.

function startNewGame () storyboard.gotoScene ("gamelevel") fine

26. exitScene

Dobbiamo fare un po 'di pulizia quando lasceremo il gioco finito scena. Rimuoviamo il listener dell'evento tap che abbiamo aggiunto in precedenza a newGameButton.

function scene: exitScene (event) local group = self.view newGameButton: removeEventListener ("tap", startNewGame) end

27. Aggiungi ascoltatori di scene

Il pezzo finale del puzzle sta aggiungendo gli ascoltatori di eventi di scena di cui abbiamo parlato in precedenza. Per fare ciò, aggiungere il seguente frammento di codice a gameover.lua.

scena: addEventListener (scena "createScene", scena): addEventListener (scena "enterScene", scena): addEventListener ("exitScene", scena)

Conclusione

Siamo giunti alla fine di questa serie e ora abbiamo un gioco di combattimento aereo completamente funzionale. Spero che tu abbia trovato questi tutorial utili e hai imparato qualcosa lungo la strada. Grazie per aver letto.