In het vierde en laatste deel van deze serie gaan we verder waar we gebleven waren in de vorige tutorial. We zullen vijandelijke vliegtuigen maken die de speler moet vermijden of schieten, en we zullen ook een game over het scherm maken.
generateEnemys
De generateEnemys
functie genereert een getal tussen drie en zeven, en roept de generateEnemyPlane
functioneer elke twee seconden voor vele keren numberOfEnemysToGenerate
is gelijk aan. Voer het volgende codefragment in gamelevel.lua.
functie generateEnemys () numberOfEnemysToGenerate = math.random (3,7) timer.performWithDelay (2000, generateEnemyPlane, numberOfEnemysToGenerate) end
We moeten deze functie ook aanroepen in de enterScene
methode zoals hieronder getoond.
functie scène: enterScene (event) --SNIP-- Runtime: addEventListener ("enterFrame", gameLoop) startTimers () generateEnemys () einde
Laten we eens kijken wat de implementatie is van generateEnemyPlane
lijkt op.
generateEnemyPlane
De generateEnemyPlane
functie genereert een vijandelijk vlak. Er zijn drie soorten vijandelijke vliegtuigen in dit spel.
function generateEnemyPlane () if (gameOver ~ = true) then local randomGridSpace = math.random (11) local randomEnemyNumber = math.random (3) local tempEnemy if (planeGrid [randomGridSpace] ~ = 0) en vervolgensEnemyPlane () retourneren 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
We controleren eerst of het spel nog niet is afgelopen. We genereren vervolgens een randomGridSpace
, een getal tussen 1 en 11, en een willekeurig randomEnemyNumber
, een getal tussen 1 en 3. De randomGridSpace
wordt gebruikt om het vlak in een van de elf slots aan de bovenkant van het scherm op de x-as te plaatsen. Als je bedenkt dat het speelveld verdeeld is in elf secties, dan willen we alleen nieuwe vlakken plaatsen in een slot dat nog niet is ingenomen door een ander vlak. De planeGrid
tabel bevat elf 0
's en wanneer we een nieuw vlak in een van de slots plaatsen, stellen we de overeenkomstige positie in de tabel in 1
om aan te geven dat een slot is ingenomen door een vliegtuig.
We controleren of de index van de randomGridSpace
in de tabel is niet gelijk aan 0
. Als dat niet het geval is, weten we dat er momenteel een gokkast wordt gebruikt en we moeten niet doorgaan, dus we bellen generateEnemyPlane
en terugkeren van de functie.
Vervolgens kijken we wat randomEnemyNumber
is gelijk aan en ingesteld tempEnemy
voor een van de drie vijandige afbeeldingen, geven we het ook een eigenschap van een van beide regelmatig
, wankelen
, of jager
. Omdat Lua een dynamische taal is, kunnen we tijdens runtime nieuwe eigenschappen aan een object toevoegen. Vervolgens stellen we in welke index gelijk is randomGridSpace
naar 1
in de planeGrid
tafel.
We voegen in tempEnemy
in de enemyPlanes
tabel voor latere referentie en toename numberOfEnemysGenerated
. Als numberOfEnemysGenerated
is gelijk aan numberOfEnemysToGenerate
, we resetten numberOfEnemysGenerated
naar 0
, beroep doen op resetPlaneGrid
, en stel een timer in die zal bellen generateEnemys
opnieuw na twee seconden. Dit proces wordt herhaald zolang het spel nog niet voorbij is.
moveEnemyPlanes
Zoals je misschien al geraden had, de moveEnemyPlanes
functie is verantwoordelijk voor het verplaatsen van de vijandelijke vliegtuigen. Afhankelijk van het vliegtuig type
, de juiste functie wordt aangeroepen.
functie moveEnemyPlanes () if (#enemyPlanes> 0) then for i = 1, #enemyPlanes do if (enemyPlanes [i] .type == "regular") then moveRegularPlane (enemyPlanes [i]) elseif (enemyPlanes [i] .type == "waver") en dan moveWaverPlane (enemyPlanes [i]) else moveChaserPlane (enemyPlanes [i]) end end end end
Deze functie moet worden aangeroepen in de gameLoop
functie.
function gameLoop () --SNIP-- checkFreeLifesOutOfBounds () checkPlayerCollidesWithFreeLife () moveEnemyPlanes () einde
moveRegularPlane
De moveRegularPlane
verplaatst het vliegtuig eenvoudig over het scherm langs de y-as.
functie moveRegularPlane (plane) plane.y = plane.y + 4 end
moveWaverPlane
De moveWaverPlane
functie verplaatst het vlak langs het scherm langs de y-as en, in een golfpatroon, over de x-as. Dit wordt bereikt door de cos
functie van Lua's wiskundebibliotheek.
Als dit concept iets voor jou is, schreef Michael James Williams een geweldige inleiding tot Sinusoïdale beweging. Dezelfde concepten zijn van toepassing, het enige verschil is dat we gebruiken cosinus. Je zou moeten denken sinus bij het omgaan met de y-as en cosinus wanneer het om de x-as gaat.
functie moveWaverPlane (plane) plane.y = plane.y + 4 plane.x = (display.contentWidth / 2) + 250 * math.cos (numberOfTicks * 0.5 * math.pi / 30) einde
In het bovenstaande fragment gebruiken we de numberOfTicks
variabel. We moeten dit telkens verhogen als het gameLoop
functie wordt aangeroepen. Voeg het volgende toe als de allereerste regel in de gameLoop
functie.
function gameLoop () numberOfTicks = numberOfTicks + 1 end
moveChaserPlane
De moveChaserPlane
functie heeft het vliegtuig achtervolgen de speler. Het beweegt langs de y-as met een constante snelheid en het beweegt in de richting van de positie van de speler op de x-as. Bekijk de implementatie van moveChaserPlane
Ter verduidelijking.
function moveChaserPlane (plane) if (plane.x < player.x)then plane.x =plane.x +4 end if(plane.x > player.x) en vervolgens plane.x = plane.x - 4 end plane.y = plane.y + 4 end
Als je het spel nu test, zou je de vliegtuigen over het scherm moeten zien bewegen.
fireEnemyBullets
Om de zoveel tijd willen we dat de vijandelijke vliegtuigen een kogel afvuren. We willen echter niet dat ze allemaal tegelijk schieten, dus kiezen we slechts een paar vliegtuigen om te vuren.
function fireEnemyBullets () if (#enemyPlanes> = 2) en local numberOfEnemyPlanesToFire = math.floor (# enemyPlanes / 2) local tempEnemyPlanes = table.copy (enemyPlanes) lokale functie fireBullet () local randIndex = math.random (#tempEnemyPlanes) lokaal tempBullet = display.newImage ("bullet.png", (tempEnemyPlanes [randIndex] .x + playerWidth / 2) + bulletWidth, tempEnemyPlanes [randIndex] .y + playerHeight + bulletHeight) tempBullet.rotation = 180 planeGroup: insert (tempBullet) table .Gebruik (enemyBullets, tempBullet); table.remove (tempEnemyPlanes, randIndex) einde voor i = 0, numberOfEnemyPlanesToFire do fireBullet () end end end
We controleren eerst om zeker te zijn van de enemyPlanes
tafel heeft meer dan twee vliegtuigen. Als dat zo is, krijgen we de numberOfEnemyPlanes
om te schieten door de lengte van de te nemen enemyPlanes
tafel, deel het door twee en rond het naar beneden. We maken ook een kopie van de enemyPlanes
tafel, zodat we het afzonderlijk kunnen manipuleren.
De fireBullet
functie kiest een vlak uit de tempEnemyPlanes
tafel en laat het vliegtuig een kogel schieten. We genereren een willekeurig getal op basis van de lengte van de tempEnemyPlanes
tabel maken, een opsommingstekenafbeelding maken en deze positioneren met het vlak waarop het vlak zich bevindt randIndex
in de tempEnemyPlanes
tafel. Vervolgens verwijderen we dat vlak uit de tijdelijke tabel om ervoor te zorgen dat het de volgende keer niet opnieuw wordt gekozen fireBullet
wordt genoemd.
We herhalen dit proces echter vele malen numerOfEnemyPlanesToFire
is gelijk aan en roept de fireBullet
functie.
We moeten de timer starten die deze functie af en toe oproept. Om dit te bereiken, voeg het volgende toe aan de startTimers
functie.
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) einde
moveEnemyBullets
We moeten ook de kogels van de vijand verplaatsen die op het scherm verschijnen. Dit is vrij eenvoudig met behulp van het volgende codefragment.
functie moveEnemyBullets () if (#enemyBullets> 0) then for i = 1, # enemyBullets do enemyBullets [i]. y = enemyBullets [i] .y + 7 end end end
Roep deze functie aan in de gameLoop
functie.
function gameLoop () --SNIP-- checkPlayerCollidesWithFreeLife () moveEnemyPlanes () moveEnemyBullets () einde
checkEnemyBulletsOutOfBounds
Naast het verplaatsen van de vijandelijke kogels, moeten we controleren wanneer de kogels van de vijand van het scherm zijn verdwenen en ze verwijderen wanneer ze dat doen. De implementatie van checkEnemyBulletsOutOfBounds
moet nu vertrouwd aanvoelen.
functie checkEnemyBulletsOutOfBounds () if (#enemyBullets> 0) en dan voor i = # enemyBullets, 1, -1 do if (vijandBullets [i] .y> display.contentHeight) then enemyBullets [i]: removeSelf () enemyBullets [i] = nul table.remove (enemyBullets, i) einde end end end
Roep deze functie aan in de gameLoop
functie.
function gameLoop () --SNIP-- moveEnemyBullets () checkEnemyBulletsOutOfBounds () end
checkEnemyPlanesOutOfBounds
We moeten ook controleren of de vijandelijke vliegtuigen van het scherm zijn verplaatst.
functie checkEnemyPlanesOutOfBounds () if (#enemyPlanes> 0) en dan voor i = # enemyPlanes, 1, -1 do if (enemyPlanes [i] .y> display.contentHeight) then enemyPlanes [i]: removeSelf () enemyPlanes [i] = nihil table.remove (enemyPlanes, i) end end end end
Roep deze functie aan in de gameLoop
functie
function gameLoop () --SNIP-- moveEnemyBullets () checkEnemyBulletsOutOfBounds () checkEnemyPlanesOutOfBounds () end
checkPlayerBulletsCollideWithEnemyPlanes
De checkPlayerBulletCollidesWithEnemyPlanes
functie gebruikt de hasCollided
functie om te controleren of een van de kogels van de speler in botsing is gekomen met een van de vijandelijke vliegtuigen.
function checkPlayerBulletsCollideWithEnemyPlanes () if (#playerBullets> 0 and #enemyPlanes> 0) en dan voor i = # playerBullets, 1, -1 do voor j = # enemyPlanes, 1, -1 do if (hasCollided (playerBullets [i], enemyPlanes [ j])) then playerBullets [i]: removeSelf () playerBullets [i] = nihil table.remove (playerBullets, i) generateExplosion (enemyPlanes [j] .x, enemyPlanes [j] .y) enemyPlanes [j]: removeSelf ( ) enemyPlanes [j] = nihil table.remove (enemyPlanes, j) local explosion = audio.loadStream ("explosion.mp3") local backgroundMusicChannel = audio.play (explosion, fadein = 1000) end end end end end
Deze functie maakt gebruik van twee geneste voor
lussen om te controleren of de objecten zijn gebotst. Voor elk van de playerBullets
, we lopen door alle vliegtuigen in de enemyPlanes
tafel en bel de hasCollided
functie. Als er een botsing is, verwijderen we de kogel en het vliegtuig, noem het generateExplosion
functie, en laad en speel een explosiegeluid.
Roep deze functie aan in de gameLoop
functie.
function gameLoop () --SNIP-- checkEnemyBulletsOutOfBounds () checkEnemyPlanesOutOfBounds () checkPlayerBulletsCollideWithEnemyPlanes () einde
generateExplosion
De generateExplosion
function gebruikt de SpriteObject-klasse van Corona. Sprites maken geanimeerde sequenties van frames mogelijk die zich op Image- of Sprite-bladen bevinden. Door afbeeldingen in één afbeelding te groeperen, kunt u bepaalde frames uit die afbeelding trekken en een animatiereeks maken.
functie generateExplosion (xPosition, yPosition) lokale opties = 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 () einde
De newImageSheet
methode neemt als parameters het pad naar de afbeelding en een tabel met opties voor het Sprite-blad. De opties die we instellen zijn de breedte
, de hoogte
, en de numFrames
, hoeveel individuele afbeeldingen dit blad vormen. Er zijn zes afzonderlijke explosiebeelden, zoals in de onderstaande afbeelding.
Vervolgens zetten we een tafel op, sequenceData
, wat nodig is door de SpriteObject
. We hebben de begin
eigendom aan 1
, de tellen
naar 6
, en tijd om 400
. De begin
eigenschap is het frame waarop de animatie zal starten, de tellen
is het aantal frames dat de animatie bevat, en de tijd
eigenschap is hoe lang de animatie duurt om door te spelen.
We maken vervolgens de SpriteObject
passeren in de explosionSheet
en sequenceData
, stel de x- en y-posities in en voeg een luisteraar toe aan de sprite. De luisteraar wordt gebruikt om de Sprite te verwijderen zodra de animatie is voltooid.
explosionListener
De explosionListener
functie wordt gebruikt om de sprite te verwijderen. Als het evenement
's fase
eigendom is gelijk aan beëindigde
, dan weten we dat de sprite zijn animatie heeft beëindigd en we deze kunnen verwijderen.
functie explosionListener (event) if (event.phase == "ended") then local explosion = event.target explosion: removeSelf () explosion = nil end end
checkEnemyBulletsCollideWithPlayer
De checkEnemyBulletsCollideWithPlayer
controleert of een van de kogels van de vijand in botsing is gekomen met het vliegtuig van de speler.
functie checkEnemyBulletsCollideWithPlayer () if (#enemyBullets> 0) en dan voor i = # enemyBullets, 1, -1 do if (hasCollided (enemyBullets [i], player)) then enemyBullets [i]: removeSelf () enemyBullets [i] = nil table.remove (enemyBullets, i) if (playerIsInvincible == false) en killPlayer () end end end end end
We lopen door de enemyBullets
tafel en controleer of een van hen is gebotst met de speler. Als dit waar is, verwijderen we die specifieke opsommingsteken en, zo ja playerIsInvincible
is vals
, we roepen aan killPlayer
.
Roep deze functie aan in de gameLoop
functie.
function gameLoop () --SNIP-- checkEnemyPlanesOutOfBounds () checkPlayerBulletsCollideWithEnemyPlanes () checkEnemyBulletsCollideWithPlayer () end
killPlayer
De killPlayer
De functie is verantwoordelijk voor het controleren of het spel afgelopen is en het uitzetten van een nieuwe speler als dit niet het geval is.
function killPlayer () numberOfLives = numberOfLives- 1; if (numberOfLives == 0) dan gameOver = true doGameOver () else spawnNewPlayer () hideLives () showLives () playerIsInvincible = true end end
We verlagen eerst numberOfLives
door 1
, en, als het gelijk is aan 0
, we noemen het spel is over
functie. Als de speler nog levens heeft, bellen we spawnNewPlayer
, gevolgd door hideLives
, showLives
, En instellen playerIsInvincible
naar waar
.
doGameOver
De doGameOver
functie vertelt het storyboard om naar de spel is over tafereel.
functie doGameOver () storyboard.gotoScene ("gameover") einde
spawnNewPlayer
De spawnNewPlayer
functie is verantwoordelijk voor het uitzetten van een nieuwe speler nadat deze is overleden. Het vlak van de speler knippert enkele seconden om aan te geven dat het tijdelijk onoverwinnelijk is.
function spawnNewPlayer () local numberOfTimesToFadePlayer = 5 local numberOfTimesPlayerHasFaded = 0 lokale functie fadePlayer () player.alpha = 0; transition.to (speler, time = 200, alpha = 1) numberOfTimesPlayerHasFaded = numberOfTimesPlayerHasFaded + 1 if (numberOfTimesPlayerHasFaded == numberOfTimesTo FadePlayer) then playerIsInvincible = false end end timer.performWithDelay (400, fadePlayer, numberOfTimesToFadePlayer) einde
Om het vlak van de speler te laten knipperen, vervagen we het vijf keer in en uit. In de fadePlayer
functie, we plaatsen het vliegtuig alpha
eigendom aan 0
, waardoor het transparant is. Vervolgens gebruiken we de transitiebibliotheek om het alpha
terug naar 1
over een periode van 200 milliseconden. De naar
methode van de overgang
object neemt een tabel met opties. In ons voorbeeld bevat de optietabel een tijd in milliseconden en de eigenschap die we willen animeren, alpha
, en de gewenste waarde, 1
.
We verhogen numberOfTimesThePlayerHasFaded
en controleer of het gelijk is aan het aantal keren dat we wilden dat de speler vervaagde. We zijn dan begonnen playerIsInvincible
naar vals
. We gebruiken een timer om het te bellen fadePlayer
functioneert echter vele malen numberOfTimerToFadePlayer
is gelijk aan.
Er is een manier om dit allemaal te doen zonder de timer te gebruiken en dat is door het gebruik van de overgang
's iteraties
eigendom in combinatie met zijn onComplete
handler. Lees de documentatie voor meer informatie over deze alternatieve aanpak.
checkEnemyPlaneCollidesWithPlayer
Er is nog een botsingcontrole die we zouden moeten doen en dat is om te zien of een vijandelijk vliegtuig botst met het vliegtuig van de speler.
functie checkEnemyPlaneCollideWithPlayer () if (#enemyPlanes> 0) en dan voor i = # enemyPlanes, 1, -1 do if (hasCollided (enemyPlanes [i], player)) then enemyPlanes [i]: removeSelf () enemyPlanes [i] = nil table.remove (enemyPlanes, i) if (playerIsInvincible == false) en killPlayer () end end end end end
We lopen door de vijandelijke vliegtuigen en kijken of een van hen botst met het vliegtuig van de speler. Als dat waar is, verwijderen we dat vijandelijke vliegtuig en bellen killPlayer
. Als je denkt dat het de game interessanter maakt, kun je hier ook een explosie genereren.
exitScene
Wanneer het spel voorbij is, gaan we over naar de spel is over tafereel. Onthoud van eerder in de tutorial, de exitScene
De functie is waar u eventlisteners verwijdert, timers stopt en audio die wordt afgespeeld stopt.
functiescène: exitScene (gebeurtenis) lokale groep = 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 () eindscène: addEventListener ("exitScene", scène)
We zijn in feite ongedaan maken wat we deden in de enterScene
functie. We noemen het vervreemden
methode op de audio
object om het geheugen vrij te geven dat is gekoppeld aan het audiokanaal. Roeping hou op
alleen geeft het geheugen niet vrij.
cancelTimers
Zoals de naam al aangeeft, is de cancelTimers
functie doet het tegenovergestelde van startTimers
, het annuleert alle timers.
function cancelTimers () timer.cancel (firePlayerBulletTimer) timer.cancel (generateIslandTimer) timer.cancel (fireEnemyBulletsTimer) timer.cancel (generateFreeLifeTimer) einde
Het is tijd om het te maken spel is over tafereel. Begin met het toevoegen van een nieuw Lua-bestand aan uw project met de naam gameover.lua, en voeg de volgende code eraan toe.
lokaal storyboard = vereisen ("storyboard") lokale scène = storyboard.newScene () local gameOverText local newGameButton return scene
createScene
Voeg het volgende toe aan gameover.lua bovenstaande terugkeer scène
. Vanaf hier zou alle code boven de terugkeer scène
uitspraak.
function scene: createScene (event) local group = self.view local background = display.newRect (0, 0, display.contentWidth, display.contentHeight) background: setFillColor (0, .39, .75) group: 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) group: insert (newGameButton) newGameButton.isVisible = false end
Zoals we in de vorige twee scènes hebben gedaan, geven we de spel is over scène een blauwe achtergrond. We maken vervolgens een textobject
bijvoorbeeld door te bellen newText
op tonen
. De newText
methode neemt een paar opties, de tekst voor het object, de positie ervan en het lettertype dat moet worden gebruikt. We geven het een gele kleur door aan te roepen SetFillColor
, RGB-waarden doorgeven als percentages. Ten slotte maken we een knop en verbergen deze voorlopig.
enterScene
Wanneer het storyboard volledig is overgegaan naar de spel is over scène, de enterScene
methode wordt genoemd.
In enterScene
, we verwijderen de vorige scène van het storyboard. We gebruiken de gemaksmethode scaleTo
van de Transitiebibliotheek om het gameOverText
met een factor 4
. We voegen een toe onComplete
luisteraar voor de overgang die deshowButton
functie zodra de overgang is voltooid. Ten slotte voegen we een tikken gebeurtenis luisteraar toe aan de spelknop die de nieuw spel starten
functie.
functie scène: enterScene (evenement) lokale groep = self.view storyboard.removeScene ("gamelevel") transition.scaleTo (gameOverText, xScale = 4.0, yScale = 4.0, time = 2000, onComplete = showButton) newGameButton: addEventListener (" tik op ", startNewGame) einde
showButton
De showButton
functie verbergt de gameOverText
en toont de newGameButton
.
function showButton () gameOverText.isVisible = false newGameButton.isVisible = true end
nieuw spel starten
De nieuw spel starten
functie vertelt het storyboard om over te schakelen naar de gamelevel tafereel.
function startNewGame () storyboard.gotoScene ("gamelevel") einde
exitScene
We moeten opruimen als we weggaan spel is over tafereel. We verwijderen de tikgebeurtenisluisteraar die we eerder aan de. Hebben toegevoegd newGameButton
.
function scene: exitScene (event) local group = self.view newGameButton: removeEventListener ("tap", startNewGame) einde
Het laatste stukje van de puzzel is het toevoegen van de scène-evenement luisteraars waar we eerder over gesproken hebben. Voeg hiervoor het volgende codefragment toe aan gameover.lua.
scène: addEventListener (scène "createScene", scène): addEventListener (scène "enterScene", scène): addEventListener ("exitScene", scène)
We zijn aan het einde van deze serie gekomen en hebben nu een volledig functioneel vliegtuiggevecht. Ik hoop dat je deze tutorials nuttig hebt gevonden en onderweg iets hebt geleerd. Bedankt voor het lezen.