In deze tutorialserie leren we hoe je een space shooter-game kunt maken, net als het klassieke spel Space Defender. Lees verder!
In deze versie van Space Defender moet de speler zijn ruimte verdedigen door vijanden neer te schieten. Elke keer dat de speler met succes een vijand vernietigt, zullen ze punten verdienen en wanneer de speler 20 of 40 punten heeft bereikt, zal hun wapen een upgrade krijgen. Om dingen te mixen, verstuurt dit spel bonuspakketten die 5 punten waard zijn. Bekijk de korte video hierboven om het spel in actie te zien.
In deel 1 van deze serie hebben we geleerd hoe we onze app kunnen instellen, hoe we aangepaste lettertypen kunnen gebruiken, hoe we een storyboard kunnen gebruiken en hoe we ons hoofdmenu kunnen instellen. In deel 2 van deze serie zullen we leren hoe we de gameplay van onze app kunnen creëren. Dus laten we beginnen!
Onze eerste stap is het maken van een nieuw bestand met de naam game.lua. Zodra het is gemaakt, opent u het bestand in uw favoriete editor.
Aangezien we een nieuwe scène beginnen, moeten we een aantal bibliotheken hebben. We zullen de physics-engine gebruiken die is ingebouwd in Corona SDK voor botsingsdetectie.
lokaal storyboard = require ("storyboard") local scene = storyboard.newScene () local physics = require ("physics")
Nadat we onze bibliotheken hebben ingesteld, zullen we de physics-engine configureren. De onderstaande instellingen stellen de zwaartekracht in op 0 (net als in de ruimte) en stellen de iteraties in op 16. De setPositionIterations betekent dat de motor 16 posities per frame zal doorlopen. Alles dat hoger is dan 16 kan de spelprestaties negatief beïnvloeden.
physics.start () physics.setGravity (0, 0) physics.setPositionIterations (16)
Hoewel deze stap niet nodig is voor deze zelfstudie, is het een goede gewoonte om de generator voor willekeurige getallen te "seeden". Ik gebruik graag de huidige tijd om de generator te zaaien.
math.randomseed (os.time ())
Nu zullen we enkele variabelen definiëren voor onze game. Elke variabele heeft een opmerking ernaast waarin het doel van de variabele wordt uitgelegd.
lokaal schermW, schermH, halfW, halfY = display.contentWidth, display.contentHeight, display.contentWidth * 0.5, display.contentHeight * 0.5 local gameover_returntomenu - forward declard our game over button - Spelinstellingen instellen motionx = 0; - Variabele gebruikt om het karakter langs de y-assnelheid te verplaatsen = 10; - Regelt de schip snelheid playerScore = 0; - Stelt de speler score playerLives = 20 in; - Stelt het aantal levens in voor de speler slowEnemySpeed = 2375; - Stelt in hoe snel de witte schepen over het scherm bewegen slowEnemySpawn = 2400; - Stelt witschip spawn rate fastEnemySpeed = 1875 in; - Bepaalt hoe snel de groene schepen snel over het scherm bewegenEnemySpawn = 1800; - Stelt groen schip spawn rate in bulletSpeed = 325; - Bepaalt hoe snel de kogel door het scherm gaat bulletSpawn = 250; - Stelt spawn rate in
Nadat we de variabelen hebben gemaakt, gaan we de scène in de functie instellen scène: createScene
. Als je je herinnert uit Deel 1, wordt deze functie gebruikt om visuele elementen en spellogica te maken. In een latere functie zullen we een beroep doen op deze functies om het spel uit te voeren.
In de volgende code maken we de scène: createScene
functie en het toevoegen van de achtergrond en de boven- / onderwanden. Beide muren zijn ingesteld als statische fysica-objecten om te voorkomen dat de speler van het scherm verdwijnt.
function scene: createScene (event) local group = self.view - Stel visuele elementen en muren lokaal in bg = display.newImageRect ("images / BKG.png", 480, 320) bg.x = halfW bg.y = halfY groep: insert (bg) local topwall = display.newRect (0,0, scherm W, 20) topwall.y = -5 topwand: setFillColor (0,0,0) topwall.alpha = 0.01 physics.addBody (topwall, "static ") groep: insert (topwall) local bottomwall = display.newRect (0,0, screenW, 20) bottomwall.y = 325 bottomwall: setFillColor (0,0,0) bottomwall.alpha = 0.01 physics.addBody (bottomwall," statische ") groep: invoegen (bodemwand) einde
Binnen hetzelfde scène: createScene
functie, maar na de bottomwall
weergaveobject, we gaan nog vier weergaveobjecten toevoegen. Hier is een uitleg van het doel van elk object.
btn_up
, btn_down
: Deze weergaveobjecten fungeren als knoppen aan de linkerkant van het scherm en elk object verplaatst het schip respectievelijk omhoog of omlaag. Ze kunnen echter pas worden gebruikt als we de verplaatsingsfunctie hebben ingesteld.enemyHitBar
: Dit weergaveobject is ingesteld als een sensor en reageert alleen op fysieke botsingen. Wanneer het reageert op botsingen, zal het het vijandelijke object verwijderen en er een van het leven van de speler aftrekken.local btn_up = display.newRect (0,0,75,160) btn_up: setReferencePoint (display.TopLeftReferencePoint) btn_up.x, btn_up.y = 0,0; btn_up.alpha = 0.01 groep: invoegen (btn_up) local btn_down = display.newRect (0,0,75,160) btn_down: setReferencePoint (display.BottomLeftReferencePoint) btn_down.x, btn_down.y = 0, screenH; btn_down.alpha = 0.01 group: insert (btn_down) local enemyHitBar = display.newRect (-20,0,20,320) enemyHitBar: setFillColor (0,0,0) enemyHitBar.name = "enemyHitBar" physics.addBody (enemyHitBar, isSensor = true) groep: insert (enemyHitBar)
Direct na het weergaveobject enemyHitBar gaan we enkele GUI-elementen toevoegen om de speler score en spelerlevens weer te geven. We zullen ook tekst weergeven op het scherm met de tekst "Omhoog" en "Omlaag verplaatsen" om de speler op de hoogte te brengen van waar ze moeten worden aangeraakt om het schip omhoog of omlaag te brengen.
local gui_score = display.newText ("Score:" ... playerScore, 0,0, "Kemco Pixel", 16) gui_score: setReferencePoint (display.TopRightReferencePoint) gui_score.x = screenW group: insert (gui_score) local gui_lives = display.newText ("Lives:" ... playerLives, 0,0, "Kemco Pixel", 16) gui_lives: setReferencePoint (display.BottomRightReferencePoint) gui_lives.x = schermW gui_lives.y = screenH group: insert (gui_lives) local gui_moveup = display.newText ( "Move Up", 0,0,50,100, "Kemco Pixel", 16) groep: insert (gui_moveup) local gui_movedown = display.newText ("Move Down", 0,0,50,23, "Kemco Pixel", 16 ) gui_movedown: setReferencePoint (display.BottomLeftReferencePoint) gui_movedown.y = screenH group: insert (gui_movedown)
Vervolgens zullen we het schip van de speler aan het scherm toevoegen. Het schip zal worden toegevoegd als een dynamisch fysica-object, zodat het kan reageren op botsingen met andere fysica-objecten. We zullen later in deze tutorial dieper ingaan op botsingen.
local ship = display.newImageRect ("images / spaceShip.png", 29, 19) ship.x, ship.y = 75, 35 ship.name = "schip" physics.addBody (schip, "dynamisch", friction = 0.5, bounce = 0) groep: invoegen (verzenden)
Herinner je je de btn_up
en btn_down
weergaveobjecten die we hebben toegevoegd? We gaan nu gebeurtenislisteners aan deze objecten toevoegen om het spelersschip te laten bewegen. Wanneer btn_up
wordt aangeraakt, zullen we onze snelheid variabel negatief maken en wanneer btn_down
wordt aangeraakt, zullen we onze snelheid positief maken. Door deze variabele positief en negatief te maken, vertellen we onze volgende functie om het schip omhoog of omlaag te brengen.
-- Wanneer de knop omhoog wordt aangeraakt, stelt u onze beweging in om de functie schip omhoog te verplaatsen btn_up: touch () motionx = -snelheid; end btn_up: addEventListener ("touch", btn_up) - Als de knop omlaag wordt ingedrukt, stelt u onze beweging in om de functie schip omlaag te verplaatsen btn_down: touch () motionx = speed; einde btn_down: addEventListener ("touch", btn_down)
Nadat we evenement luisteraars hebben toegevoegd aan onze btn_up
en btn_down
om objecten weer te geven, gaan we twee runtime-gebeurtenislisteners maken met hun respectieve functies. Deze functies zullen elk frame uitvoeren en de enige met runtime-functies is dat u moet aangeven wanneer u ze wilt stoppen. We zullen dat later behandelen. Voor nu zal de stop-functie de variabele instellen MotionX
tot 0 (omdat geen enkele knop wordt aangeraakt) en de moveguy
functie voegt de variabele toe MotionX
naar ons schip Y
positie.
lokale functie stop (event) als event.phase == "ended" dan motionx = 0; end-end Runtime: addEventListener ("touch", stop) - Deze functie verplaatst het schip feitelijk op basis van de bewegingslocatiefunctie moveguy (event) ship.y = ship.y + motionx; einde Runtime: addEventListener ("enterFrame", moveguy)
Inmiddels hebben we ons schip in beweging, maar het vuurt niet! Om het schip klaar te maken om kogels af te vuren, moeten we de fireship ()
functie. Deze functie maakt nieuwe weergaveobjecten die reageren op natuurbotsingen en deze functie verplaatst het object ook van links naar rechts over het scherm.
Om de game interessanter te maken, zullen we de speler toestaan meer kogels te schieten als deze een bepaalde score bereiken. Wanneer de speler 20 bereikt, schiet het schip twee kogels af en wanneer de speler 40 bereikt, vuurt het schip een derde kogel af die diagonaal naar beneden schiet.
function fireShip () bullet = display.newImageRect ("images / bullet.png", 13, 8) bullet.x = ship.x + 9 bullet.y = ship.y + 6 bullet: toFront () bullet.name = " bullet "physics.addBody (bullet, isSensor = true) transition.to (bullet, time = bulletSpeed, x = 500, onComplete = function (self) self.parent: remove (self); self = nil; end; ) if (playerScore> = 20) then secondBullet = display.newImageRect ("images / bullet.png", 13, 8) secondBullet.x = ship.x + 9 secondBullet.y = ship.y + 12 secondBullet: toFront ( ) secondBullet.name = "bullet" physics.addBody (secondBullet, isSensor = true) transition.to (secondBullet, time = bulletSpeed, x = 500, onComplete = function (self) self.parent: remove (self); self = nil; end;) end if (playerScore> = 40) then thirdBullet = display.newImageRect ("images / bullet.png", 13, 8) thirdBullet.x = ship.x + 9 thirdBullet.y = verzenden. y + 12 thirdBullet: toFront () thirdBullet.name = "bullet" physics.addBody (thirdBullet, isSensor = true) transition.to (thirdBullet, time = bulletSpeed, x = 500, y = ship.y + 100, onComplete = function (self) self.parent: remove (self); zelf = nul; einde; )
Nadat we ons schip in brand hebben gestoken, moeten we de speler een aantal vijanden geven om op te schieten! We zullen twee verschillende functies maken - createSlowEnemy ()
en createFastEnemy ()
. Beide functies zullen een fysica-weergaveobject creëren dat van rechts naar links beweegt met de snelheid van de vijand en waarbij het beeld het enige verschil is.
function createSlowEnemy () enemy = display.newImageRect ("images / enemy.png", 32, 26) vijand.rotation = 180 enemy.x = 500 enemy.y = math.random (10, screenH-10) enemy.name = "vijand" physics.addBody (vijand, isSensor = true) transition.to (vijand, time = slowEnemySpeed, x = -20) eindfunctie createFastEnemy () enemy = display.newImageRect ("images / fastEnemy.png" , 32, 26) enemy.rotation = 180 enemy.x = 500 enemy.y = math.random (10, screenH-10) enemy.name = "vijandige" physics.addBody (vijand, isSensor = true) overgang. om (vijand, time = fastEnemySpeed, x = -20) te eindigen
Vervolgens maken we bonuspakketten voor onze speler om de functie binnen te halen createBonus ()
. De createBonus ()
functie maakt een natuurkundig weergaveobject dat van rechts naar links beweegt en elk bonuspakket dat de speler pakt, krijgt 5 punten.
functie createBonus () bonus = display.newImageRect ("images / bonus.png", 18, 18) bonus.rotation = 180 bonus.x = 500 bonus.y = math.random (10, scherm H-10) bonus.name = "bonus" physics.addBody (bonus, isSensor = true) transition.to (bonus, time = 1475, x = -20, onComplete = function () display.remove (bonus) bonus = nul einde;) einde
Onze voorlaatste functie is de updateLives () functie. Deze functie wordt elke keer dat een vijand voorbij de speler komt geroepen om de speler het doel te geven om zijn kant van de ruimte te verdedigen. Als het aantal levens langer is dan 0, trekt deze functie een leven af en werkt de tekst op het scherm bij. Anders zal het resulteren in een game over scène.
In de game over-scène annuleren we al onze timers en verwijderen we al onze evenementluisteraars. Met de Corona SDK is het erg belangrijk om te onthouden dat je expliciet aan je app moet vertellen wanneer runtime-listeners en -timers moeten worden verwijderd (alleen als de timer loopt). Nadat deze zijn verwijderd, zullen we een game-over-bericht weergeven en de speler toestaan terug te keren naar het menu.
function updateLives () if (playerLives> = 0) then playerLives = playerLives - 1 gui_lives.text = "Lives:" ... playerLives gui_lives.x = schermW else timer.cancel (tmr_fireShip) timer.cancel (tmr_sendSlowEnemies) timer.cancel (tmr_sendSlowEnemies2 ) timer.cancel (tmr_sendFastEnemies) timer.cancel (tmr_sendBonus) Runtime: removeEventListener ("collision", onCollision) Runtime: removeEventListener ("enterFrame", moveguy) Runtime: removeEventListener ("touch", stop) - Geef het spel weer op het lokale scherm gameover_message = display.newText ("Game Over!", 0,0, "Kemco Pixel", 32) gameover_message.x = halfW gameover_message.y = halfY - 15 groep: insert (gameover_message) functie returnToMenuTouch (event) if (event. phase == "started") then storyboard.gotoScene ("menu", "slideRight", "1000") end-end gameover_returntomenu = display.newText ("Return To Menu", 0,0, "Kemco Pixel", 28) gameover_returntomenu .x = halfW gameover_returntomenu.y = halfY + 35 gameover_returntomenu: addEventListener ("touch", returnToMenuTouch) group: in sert (gameover_returntomenu) end end
We zijn klaar voor onze laatste functie in onze scene: createScene () -functie! Deze functie behandelt al onze botsingsdetectie door de eigenschap te vergelijken mijn naam
van object1 tot die van object 2. Elk object wordt als een parameter doorgegeven aan deze functie onder de naam van de variabele evenement
.
Om het u gemakkelijker te maken, heb ik de vijf gevallen van botsing afgebroken.
function onCollision (event) if (event.object1.name == "bullet" en event.object2.name == "enemy") then display.remove (event.object2) playerScore = playerScore + 1 elseif (event.object1.name == "vijand" en event.object2.name == "bullet") then display.remove (event.object1) playerScore = playerScore + 1 elseif (event.object1.name == "schip" en event.object2.name = = "bonus") en vervolgens display.remove (event.object2) playerScore = playerScore + 5 elseif (event.object1.name == "enemy" en event.object2.name == "enemyHitBar") then display.remove (event. object1) updateLives () elseif (event.object1.name == "enemyHitBar" en event.object2.name == "enemy") then display.remove (event.object2) updateLives () end gui_score.text = "Score:" ... playerScore gui_score.x = screenW end
Omdat we alles hebben ingesteld voor onze game, moeten we alles laten bewegen! Binnenkant van de functie scène: enterScene ()
- onthoud dat de enterScene
functie bevindt zich buiten de createScene
functie - we zullen 5 timers en een runtime luisteraar aanmaken. De timers sturen de kogels, vijanden en bonussen uit terwijl de runtime-luisteraar de botsingsdetectie afhandelt.
functie scène: enterScene (evenement) lokale groep = self.view tmr_fireShip = timer.performWithDelay (bulletSpawn, fireShip, 0) tmr_sendSlowEnemies = timer.performWithDelay (slowEnemySpawn, createSlowEnemy, 0) tmr_sendSlowEnemies2 = timer.performWithDelay (slowEnemySpawn + (slowEnemySpawn * 0.5), createSlowEnemy, 0) tmr_sendFastEnemies = timer.performWithDelay (fastEnemySpawn, createFastEnemy, 0) tmr_sendBonus = timer.performWithDelay (2500, createBonus, 0) Runtime: addEventListener ("collision", onCollision) end
De laatste toevoeging (ik beloof het!) Is de scène: destroyScene ()
functie en luisteraars van scene-evenementen. De functie voor het vernietigen van scènes zorgt ervoor dat de fysica wordt verwijderd zodra de speler de scène verlaat. De luisteraars van de scene-evenementen zullen de createScene
, enterScene
, en destroyScene
respectievelijk.
functie scène: destroyScene (event) lokale groep = self.view pakket.loaded [physics] = nul physics = nul einde scène: addEventListener (scène "createScene", scène): addEventListener (scène "enterScene", scène): addEventListener (" destroyScene ", scène) terugkeren scène
Gefeliciteerd! Je hebt veel dingen geleerd, zoals Corona's storyboard-functie, natuurkunde, botsingen en nog veel meer! Dit zijn waardevolle vaardigheden die op bijna elk spel kunnen worden toegepast en als u deze game voor uw apparaat wilt bouwen, raad ik u ten zeerste aan de officiële Corona-documenten te gebruiken voor het bouwen van het apparaat.
Heel erg bedankt voor het lezen! Als u vragen heeft, laat deze dan achter in de opmerkingen hieronder.