Creëer Space Invaders met Swift en Sprite Kit Finishing Gameplay

Wat je gaat creëren

In het vorige deel van deze serie hebben we de indringers laten bewegen, de speler en invallers hebben kogels afgeschoten en botsingsdetectie geïmplementeerd. In het vierde en laatste deel van deze serie voegen we de mogelijkheid toe om de speler te verplaatsen met behulp van de versnellingsmeter, de niveaus te beheren en ervoor te zorgen dat de speler sterft wanneer deze door een kogel wordt geraakt. Laten we beginnen.

1. Het afmaken van de Speler Klasse

Stap 1: Eigenschappen toevoegen

Voeg de volgende eigenschappen toe aan de Speler klasse onder de canFire eigendom.

private var onoverwinnelijk = false private var lives: Int = 3 didSet if (lives < 0) kill() else respawn()    

De onoverwinnelijk eigendom zal worden gebruikt om de speler tijdelijk onoverwinnelijk te maken als het een leven verliest. De levens eigendom is het aantal levens dat de speler heeft voordat hij wordt vermoord.

We gebruiken een eigenschapwaarnemer op de levens eigenschap, die elke keer dat de waarde is ingesteld, wordt aangeroepen. De didSet waarnemer wordt opgeroepen onmiddellijk nadat de nieuwe waarde van de eigenschap is ingesteld. Door dit te doen, elke keer dat we de levens eigenschap controleert het automatisch of levens is minder dan nul, het aanroepen van de doden methode als het is. Als de speler nog levens heeft, de respawn methode wordt aangeroepen. Vastgoedwaarnemers zijn erg handig en kunnen veel extra code besparen.

Stap 2: respawn

De respawn methode maakt de speler voor een korte tijd onoverwinnelijk en vervaagt de speler in en uit om aan te geven dat het tijdelijk onoverwinnelijk is. De implementatie van de respawn methode ziet er als volgt uit:

func respawn () onoverwinnelijk = waar laat fadeOutAction = SKAction.fadeOutWithDuration (0.4) laat fadeInAction = SKAction.fadeInWithDuration (0.4) laat fadeOutIn = SKAction.sequence ([fadeOutAction, fadeInAction]) laten fadeOutInAction = SKAction.repeatAction (fadeOutIn, count: 5) laat setInvicibleFalse = SKAction.runBlock () self.invincible = false runAction (SKAction.sequence ([fadeOutInAction, setInwicibleFalse])) 

We gaan zitten onoverwinnelijk naar waar en maak een aantal SKAction voorwerpen. Inmiddels zou u bekend moeten zijn met hoe het SKAction klasse werkt.

Stap 3: dood gaan

De dood gaan methode is vrij eenvoudig. Het controleert of onoverwinnelijk is vals en, als dat het geval is, verlaagt het levens veranderlijk.

 func die () if (onoverwinnelijk == false) lives - = 1

Stap 4: doden

De doden methode reset invaderNum naar 1 en brengt de gebruiker terug naar de StartGameScene zodat ze een nieuw spel kunnen beginnen.

func kill () invaderNum = 1 laat gameOverScene = StartGameScene (grootte: self.scene! .size) gameOverScene.scaleMode = self.scene! .scaleMode let transitionType = SKTransition.flipHorizontalWithDuration (0.5) self.scene! .view! .presentScene (gameOverScene, transition: transitionType) 

Deze code moet u bekend voorkomen, omdat deze bijna identiek is aan de code die we gebruikten om naar de. Te gaan GameScene van de StartGameScene. Merk op dat we het uitpakken van de tafereel  om toegang te krijgen tot de scènes grootte en scaleMode eigenschappen.

Hiermee is het Speler klasse. We moeten nu de dood gaan  en doden methoden in de didBeginContact (_ :) methode.

func didBeginContact (contact: SKPhysicsContact) ... if ((firstBody.categoryBitMask & CollisionCategories.Player! = 0) && (secondBody.categoryBitMask & CollisionCategories.InvaderBullet! = 0)) player.die () if ((firstBody.categoryBitMask & CollisionCategories.Invader! = 0) && (secondBody.categoryBitMask & CollisionCategories.Player! = 0)) player.kill ()

We kunnen nu alles testen. Een snelle manier om het te testen dood gaan methode is door het commentaar te geven moveInvaders bel in de bijwerken(_:) methode. Nadat de speler sterft en drie keer herstelt, moet je worden teruggebracht naar de StartGameScene.

Om de te testen doden methode, zorg ervoor dat de moveInvaders oproep is niet becommentarieerd. Stel de invaderSpeed onroerend goed met een hoge waarde, bijvoorbeeld, 200. De indringers moeten de speler heel snel bereiken, wat resulteert in een onmiddellijke kill. Verandering invaderSpeed terug naar als je klaar bent met testen.

2. Afwerking Invaders Invaders

Zoals het spel nu staat, kan alleen de onderste rij indringers kogels afvuren. We hebben al de botsingsdetectie voor wanneer een speler op een indringer botst. In deze stap verwijderen we een indringer die is geraakt door een opsommingsteken en voegt de indringer één rij toe aan de reeks indringers die kunnen worden geactiveerd. Voeg het volgende toe aan de didBeginContact (_ :) methode.

func didBeginContact (contact: SKPhysicsContact) ... if ((firstBody.categoryBitMask & CollisionCategories.Invader! = 0) && (secondBody.categoryBitMask & CollisionCategories.PlayerBullet! = 0)) if (contact.bodyA.node? .parent == nul || contact.bodyB.node? .parent == nihil) return laat invadersPerRow = invaderNum * 2 + 1 laat theInvader = firstBody.node as! Invader laat newInvaderRow = theInvader.invaderRow - 1 laat newInvaderColumn = theInvader.invaderColumn if (newInvaderRow> = 1) self.enumerateChildNodesWithName ("invader") node, stop in let invader = node as! Invader if invader.invaderRow == newInvaderRow && invader.invaderColumn == newInvaderColumn self.invadersWhoCanFire.append (invader) stop.memory = true let invaderIndex = findIndex (invadersWhoCanFire, valueToFind: firstBody.node? As Invader) if ( invaderIndex! = nil) invadersWhoCanFire.removeAtIndex (invaderIndex!) theInvader.removeFromParent () secondBody.node? .removeFromParent ()

We hebben de NSLog verklaring en controleer eerst of contact.bodyA.node? .parent en contact.bodyB.node? .parent zijn niet nul. Zij zullen zijn nul als we dit contact al hebben verwerkt. In dat geval keren we terug van de functie.

We berekenen de invadersPerRow zoals we eerder hebben gedaan en ingesteld theInvader naar firstBody.node, gieten naar een binnendringer. Vervolgens krijgen we de newInvaderRow door af te trekken 1 en de newInvaderColumn, welke hetzelfde blijft.

We willen alleen dat indringers kunnen vuren als het newInvaderRow is groter dan of gelijk aan 1, anders zouden we proberen een indringer in rij te plaatsen om te kunnen vuren. Er is geen rij 0 dus dit zou een fout veroorzaken.

Vervolgens inventariseren we de indringers, op zoek naar de indringer met de juiste rij en kolom. Zodra het is gevonden, voegen we het toe aan de invadersWhoCanFire array en call stop.memory naar waar dus de opsomming stopt vroeg.

We moeten de indringer vinden die is geraakt met een kogel in de invadersWhoCanFire array, zodat we deze kunnen verwijderen. Normaal gesproken hebben arrays een soort functionaliteit zoals een index van methode of iets dergelijks om dit te bereiken. Op het moment van schrijven bestaat er geen dergelijke methode voor arrays in de Swift-taal. De Swift-standaardbibliotheek definieert een vind functie die we zouden kunnen gebruiken, maar ik vond een methode in de secties over generieke geneesmiddelen in de Swift-programmeertaalgids die zal bereiken wat we nodig hebben. De functie heeft de toepasselijke naam findIndex. Voeg het volgende toe onderaan GameScene.swift.

func findIndex(array: [T], valueToFind: T) -> Int? for (index, value) in enumerate (array) if value == valueToFind return index return nil 

Als je nieuwsgierig bent naar hoe deze functie werkt, raad ik je aan meer te lezen over generieke geneesmiddelen in de Swift-programmeertaalgids.

Nu we een methode hebben die we kunnen gebruiken om de indringer te vinden, roepen we die aan, doorgeven in de invadersWhoCanFire array en theInvader. We controleren of invaderIndex is niet gelijk aan nul en verwijder de indringer uit de invadersWhoCanFire array met behulp van de removeAtIndex (index: Int) methode.

U kunt nu testen of het werkt zoals het hoort. Een gemakkelijke manier zou zijn om uit te leggen waar de oproep is player.die in de didBeginContact (_ :) methode. Zorg ervoor dat u de opmerking verwijdert wanneer u klaar bent met testen. Merk op dat het programma crasht als je alle indringers doodt. We zullen dit in de volgende stap oplossen.

De applicatie crasht, omdat we een hebben SKAction repeatActionForever (_ :) oproepen voor indringers om kogels af te vuren. Op dit moment zijn er geen indringers meer om kogels af te vuren, zodat de spellen crashen. We kunnen dit oplossen door de is leeg eigendom op de invadersWhoCanFire matrix. Als de array leeg is, is het niveau voorbij. Voer het volgende in de fireInvaderBullet methode.

func fireInvaderBullet () if (invadersWhoCanFire.isEmpty) invaderNum + = 1 levelComplete () else let randomInvader = invadersWhoCanFire.randomElement () randomInvader.fireBullet (self)

Het niveau is voltooid, wat betekent dat we verhogen invaderNum, die wordt gebruikt voor de niveaus. We roepen ook aan level voltooid, die we nog moeten maken in de komende stappen.

3. Een niveau voltooien

We moeten een bepaald aantal niveaus hebben. Als we dat niet doen, zullen we na verschillende rondes zoveel indringers hebben dat ze niet op het scherm passen. Voeg een eigenschap toe maxLevels naar de GameScene klasse.

class GameScene: SKScene, SKPhysicsContactDelegate ... let speler: Speler = Speler () laat maxLevels = 3

Voeg nu de level voltooid methode onderaan GameScene.swift.

func levelComplete () if (invaderNum <= maxLevels) let levelCompleteScene = LevelCompleteScene(size: size) levelCompleteScene.scaleMode = scaleMode let transitionType = SKTransition.flipHorizontalWithDuration(0.5) view?.presentScene(levelCompleteScene,transition: transitionType) else invaderNum = 1 newGame()  

We controleren eerst om te zien of invaderNum is kleiner dan of gelijk aan de maxLevels we hebben gezet. Als dat zo is, gaan we over naar de LevelCompletScene, anders worden we gereset invaderNum naar 1 en bel nieuw spel. LevelCompleteScene bestaat nog niet, noch het nieuw spel methode, dus laten we deze in een keer in de volgende twee stappen aanpakken.

4. Implementatie van de LevelCompleteScene Klasse

Maak een nieuw Cocoa Touch Class genaamd LevelCompleteScene dat is een subklasse van SKScene. De implementatie van de klasse ziet er als volgt uit:

importeren Fundering importeren SpriteKit-klasse LevelCompleteScene: SKScene override func didMoveToView (weergave: SKView) self.backgroundColor = SKColor.blackColor () laat startGameButton = SKSpriteNode (imageNamed: "nextlevelbtn") startGameButton.position = CGPointMake (size.width / 2, size.height / 2 - 100) startGameButton.name = "nextlevel" addChild (startGameButton) override func raaktBegan (raakt aan: Set, withEvent event: UIEvent) let touch = touches.first as! UITouch let touchLocation = touch.locationInNode (self) let touchedNode = self.nodeAtPoint (touchLocation) if (touchedNode.name == "nextlevel") let gameOverScene = GameScene (size: size) gameOverScene.scaleMode = scaleMode let transitionType = SKTransition. flipHorizontalWithDuration (0,5) view? .presentScene (gameOverScene, transition: transitionType)

De implementatie is identiek aan de StartGameScreen klasse, behalve dat we de naam eigendom van startGameButton naar "volgende niveau". Deze code moet bekend zijn. Als dat niet het geval is, gaat u terug naar het eerste deel van deze zelfstudie voor een opfriscursus.

5. nieuw spel

De nieuw spel methode gaat eenvoudigweg terug naar de StartGameScene. Voeg het volgende toe onderaan GameScene.swift.

func newGame () let gameOverScene = StartGameScene (grootte: grootte) gameOverScene.scaleMode = scaleMode let transitionType = SKTransition.flipHorizontalWithDuration (0.5) view? .presentScene (gameOverScene, transition: transitionType) 

Als je de applicatie test, kun je een paar levels spelen of een paar games verliezen, maar de speler kan niet bewegen en dit zorgt voor een saai spel. Laten we dat in de volgende stap oplossen.

6. De speler verplaatsen met behulp van de versnellingsmeter

We gebruiken de versnellingsmeter om de speler te verplaatsen. We moeten eerst het importeren CoreMotion kader. Voeg een importinstructie toe voor het framework bovenaan GameScene.swift.

importeer SpriteKit importeer CoreMotion

We hebben ook een aantal nieuwe eigenschappen nodig.

let maxLevels = 3 let motionManager: CMMotionManager = CMMotionManager () var acceleratieX: CGFloat = 0.0

Voeg vervolgens een methode toe setupAccelerometer onderaan GameScene.swift.

func setupAccelerometer () motionManager.accelerometerUpdateInterval = 0.2 motionManager.startAccelerometerUpdatesToQueue (NSOperationQueue.currentQueue (), withHandler: (accelerometerData: CMAccelerometerData !, error: NSError!) in let acceleration = accelerometerData.acceleration self.accelerationX = CGFloat (acceleration.x ))

Hier hebben we de accelerometerUpdateInterval, Dit is het interval in seconden voor het leveren van updates aan de handler. ik vond 0.2 werkt goed, je kunt verschillende waarden proberen als je dat wilt. In de handler; een afsluiting, we krijgen de accelerometerData.acceleration, wat een structuur van het type is CMAcceleration.

struct CMAcceleration var x: Double var y: Double var z: Double init () init (x x: Double, y y: Double, z z: Double)

We zijn alleen geïnteresseerd in de X eigenschap en we gebruiken een numerieke conversie om het naar een te casten CGFloat voor onze accelerationX eigendom.

Nu dat we de accelerationX eigenschap ingesteld, kunnen we de speler verplaatsen. We doen dit in de didSimulatePhysics methode. Voeg het volgende toe onderaan GameScene.swift.

override func didSimulatePhysics () player.physicsBody? .velocity = CGVector (dx: accelerationX * 600, dy: 0)

Beroep doen op setupAccelerometer in didMoveToView (_ :) en je zou de speler met de versnellingsmeter moeten kunnen verplaatsen. Er is maar één probleem. De speler kan van op het scherm naar elke kant gaan en het duurt een paar seconden om hem terug te krijgen. We kunnen dit oplossen door de physics engine en botsingen te gebruiken. Dit doen we in de volgende stap.

override func didMoveToView (weergave: SKView) ... setupInvaders () setupPlayer () invokeInvaderFire () setupAccelerometer ()

7. Beperking van de bewegingen van de speler

Zoals vermeld in de vorige stap, kan de speler van het scherm bewegen. Dit is een eenvoudige oplossing met de physics-engine van de Sprite Kit. Voeg eerst een nieuw toe CollisionCategory genaamd EdgeBody.

struct CollisionCategories static laat Invader: UInt32 = 0x1 << 0 static let Player: UInt32 = 0x1 << 1 static let InvaderBullet: UInt32 = 0x1 << 2 static let PlayerBullet: UInt32 = 0x1 << 3 static let EdgeBody: UInt32 = 0x1 << 4 

Stel dit in als de speler collisionBitMask in zijn in het methode.

override init () ... self.physicsBody? .categoryBitMask = CollisionCategories.Player self.physicsBody? .contactTestBitMask = CollisionCategories.InvaderBullet | CollisionCategories.Invader self.physicsBody? .CollisionBitMask = CollisionCategories.EdgeBody animeren ()

Ten slotte creëren we een physicsBody op het toneel zelf. Voeg het volgende toe aan de didMoveToView (weergave: SKView) methode in GameScene.swift.

 override func didMoveToView (weergave: SKView) self.physicsWorld.gravity = CGVectorMake (0, 0) self.physicsWorld.contactDelegate = self self.physicsBody = SKPhysicsBody (edgeLoopFromRect: frame) self.physicsBody? .categoryBitMask = CollisionCategories.EdgeBody 

We initialiseren een fysisch lichaam door aan te roepen init (edgeLoopFromRect :), passeren in de scene's montuur. De initializer maakt een randlus van het frame van de scène. Het is belangrijk op te merken dat een rand geen volume of massa heeft en altijd wordt behandeld alsof de dynamische eigenschap gelijk is aan vals. Randen kunnen ook alleen botsen met op volume gebaseerde physics bodies, die onze speler is.

We hebben ook de categoryBitMask naar CollisionCategories.EdgeBody. Als u de toepassing test, merkt u mogelijk dat uw schip niet meer van het scherm kan worden verplaatst, maar soms roteert het. Wanneer een fysisch lichaam botst met een ander fysisch lichaam, is het mogelijk dat dit resulteert in een rotatie. Dit is het standaardgedrag. Om dit te verhelpen, hebben we vastgesteld allowsRotation naar vals in Player.swift.

override init () ... self.physicsBody? .collisionBitMask = CollisionCategories.EdgeBody self.physicsBody? .allowsRotation = false animé ()

8. Sterrenveld

Stap 1: Het sterrenveld maken

De game heeft een bewegend sterrenveld op de achtergrond. We kunnen het startveld maken met de deeltjesmotor van Sprite Kit.

Maak een nieuw bestand en selecteer hulpbron van de iOS sectie. Kiezen SpriteKit Particle File als de sjabloon en klik volgende. Voor de Deeltjessjabloon Kiezen regen en bewaar het als starfield. Klik creëren om het bestand in de editor te openen. Om de opties te zien, opent u de SKNode Inspector aan de rechterkant, rechts.


In plaats van elke instelling hier door te nemen, wat lang zou duren, is het beter om de documentatie te lezen om meer te weten te komen over elke individuele instelling. Ik zal ook niet in detail treden over de instellingen van het startveld. Als je geïnteresseerd bent, open je het bestand in Xcode en bekijk je de instellingen die ik heb gebruikt.

Stap 2: Het sterrenveld toevoegen aan de scènes

Voeg het volgende toe aan didMoveToView (_ :) in StartGameScene.swift.

override func didMoveToView (weergave: SKView) backgroundColor = SKColor.blackColor () laat starField = SKEmitterNode (fileNamed: "StarField") starField.position = CGPointMake (size.width / 2, size.height / 2) starField.zPosition = - 1000 addChild (starField)

We gebruiken een SKEmitterNode om de te laden StarField.sks bestand, stel het in positie en geef het een dieptepunt zPosition. De reden voor het dieptepunt zPosition is om te voorkomen dat de gebruiker op de startknop tikt. Het particle systeem genereert honderden deeltjes, dus door het echt laag in te stellen, overwinnen we dat probleem. U moet ook weten dat u alle deeltjeseigenschappen handmatig kunt configureren op een SKEmitterNode, hoewel het veel gemakkelijker is om de editor te gebruiken om een ​​te maken .sks bestand en laad het tijdens runtime.

Voeg nu het sterrenveld toe aan GameScene.swift en LevelCompleteScene.swift. De code is precies hetzelfde als hierboven.

9. Implementatie van de PulsatingText Klasse

Stap 1: Maak het PulsatingText Klasse

De StartGameScene en LevelCompleteScene heb tekst die herhaaldelijk groeit en krimpt. We zullen subclass SKLabeNode en gebruik een paar SKAction instanties om dit effect te bereiken.

Maak een nieuw Cocoa Touch Class dat is een subklasse van SKLabelNode,Noem maar op PulsatingText, en voeg de volgende code eraan toe.

import UIKit import SpriteKit-klasse PulsatingText: SKLabelNode func setTextFontSizeAndPulsate (theText: String, theFontSize: CGFloat) self.text = theText; self.fontSize = theFontSize let scaleSequence = SKAction.sequence ([SKAction.scaleTo (2, duration: 1), SKAction.scaleTo (1.0, duration: 1)]) laat scaleForever = SKAction.repeatActionForever (scaleSequence) self.runAction (scaleForever )

Een van de eerste dingen die je misschien hebt gemerkt is dat er geen initializer is. Als uw subklasse geen specifieke initializer definieert, neemt deze automatisch alle bijbehorende initializers van de superklasse over.

We hebben één methode setTextFontSizeAndPulsate (thetext: theFontSize :), wat precies doet wat het zegt. Het zet de SKLabelNode's tekst en lettertypegrootte eigenschappen en maakt een aantal SKAction exemplaren om de tekst op te schalen en vervolgens weer naar beneden te halen, waardoor een pulserend effect ontstaat.

Stap 2: toevoegen PulsatingText naar StartGameScene

Voeg de volgende code toe aan StartGameScene.swift in didMoveToView (_ :).

 override func didMoveToView (weergave: SKView) backgroundColor = SKColor.blackColor () laat invaderText = PulsatingText (fontNamed: "ChalkDuster") invaderText.setTextFontSizeAndPulsate ("INVADERZ", theFontSize: 50) invaderText.position = CGPointMake (size.width / 2 , size.height / 2 + 200) addChild (invaderText)

We initialiseren a PulsatingText aanleg, invaderText, en aanroepen setTextFontSizeAndPulsate (thetext: theFontSize :) ben ermee bezig. Vervolgens hebben we het ingesteld positie en voeg het toe aan de scène.

Stap 3: toevoegen PulsatingText naar LevelCompleteScene

Voeg de volgende code toe aan LevelCompleteScene.swift in didMoveToView (_ :).

 override func didMoveToView (weergave: SKView) self.backgroundColor = SKColor.blackColor () laat invaderText = PulsatingText (fontNamed: "ChalkDuster") invaderText.setTextFontSizeAndPulsate ("LEVEL COMPLETE", theFontSize: 50) invaderText.position = CGPointMake (size. width / 2, size.height / 2 + 200) addChild (invaderText)

Dit is precies hetzelfde als de vorige stap. Alleen de tekst die we doorgeven is anders.

10. Het spel verder nemen

Hiermee is het spel voltooid. Ik heb enkele suggesties voor hoe je de game verder zou kunnen uitdiepen. Binnen in de afbeeldingen map, er zijn drie verschillende invader-afbeeldingen. Wanneer u indringers aan de scène toevoegt, kiest u willekeurig een van deze drie afbeeldingen. U moet de initialisator van de indringer bijwerken om een ​​afbeelding als parameter te accepteren. Verwijs naar de Kogel klas voor een hint.

Er is ook een UFO-afbeelding. Probeer dit elke 15 seconden of zo over het scherm te laten verschijnen. Als de speler het raakt, geef ze een extra leven. Misschien wil je het aantal levens dat ze kunnen hebben beperken als je dit doet. Probeer ten slotte een HUD te maken voor het leven van de speler.

Dit zijn slechts enkele suggesties. Probeer de game zo te maken.

Conclusie

Dit brengt deze serie tot een einde. Je zou een game moeten hebben die sterk lijkt op het originele Space Invaders-spel. Ik hoop dat je deze tutorial nuttig hebt gevonden en iets nieuws hebt geleerd. Bedankt voor het lezen.