Creați Space Invaders cu Swift și Sprite Kit Finisarea gameplay-ului

Ce veți crea

În partea anterioară a acestei serii, am făcut mutarea invadatorilor, gloanțele de foc și jucătorii invadatori și am implementat detectarea coliziunilor. În cea de-a patra și ultima parte a acestei serii, vom adăuga capacitatea de a muta jucătorul utilizând accelerometrul, de a gestiona nivelurile și de a asigura că jucătorul moare atunci când este lovit de un glonț. Să începem.

1. Finalizarea procesului Jucător Clasă

Pasul 1: Adăugarea proprietăților

Adăugați următoarele proprietăți la Jucător clasa de mai jos canFire proprietate.

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

de neinvins proprietatea va fi folosită pentru a face jucătorul invincibil temporar atunci când își pierde viața. vieți proprietatea este numărul de vieți pe care le are jucătorul înainte de a fi ucis.

Folosim un observator de proprietate pe vieți proprietate, care va fi numit de fiecare dată când valoarea sa este stabilită. didSet observatorul este chemat imediat după stabilirea valorii noi a proprietății. Făcând acest lucru, de fiecare dată când decrementăm vieți de proprietate se verifică automat dacă vieți este mai mică decât zero, apelând ucide dacă este. Dacă jucătorul are vieți rămase, respawn metoda este invocată. Observatorii proprietății sunt foarte folositori și pot salva multe coduri suplimentare.

Pasul 2: respawn

respawn metoda face jucătorul invincibil pentru o perioadă scurtă de timp și distruge jucătorul în și out pentru a indica că este temporar invincibil. Punerea în aplicare a directivei respawn metoda arată astfel:

funcția fadeOutInction = (FadeOutInction = FadeOutAction = FadeOutInction = FadeOutInction = FadeOutInction = FadeOutInction = FadeOutInction = FadeOutInction = FadeOutInction = 5) lasă setInvicibleFalse = SKAction.runBlock () self.invincible = false runAction (SKAction.sequence ([fadeOutInAction, setInvicibleFalse]) 

Noi am stabilit de neinvins la Adevărat și să creeze un număr de SKAction obiecte. Până acum, ar trebui să fii familiarizat cu modul în care SKAction clasa de lucrări.

Pasul 3: a muri

a muri metoda este destul de simplă. Verifică dacă de neinvins este fals și, dacă este, decrementează vieți variabil.

 func die () if (invincibil == false) trăiește - = 1

Pasul 4: ucide

ucide metoda se resetează invaderNum la 1 și îl returnează pe utilizator StartGameScene astfel încât să poată începe un nou joc.

func kill () invaderNum = 1 ani jocOverScene = StartGameScene (dimensiune: auto.scene! .size) gameOverScene.scaleMode = auto.scene! .scaleMode lasa transitionType = SKTransition.flipHorizontalWithDuration (0.5) self.scene! .view! .presentScene (gameOverScene, tranziție: transitionType) 

Acest cod trebuie să vă fie cunoscut deoarece este aproape identic cu codul pe care l-am folosit pentru a trece la GameScene de la StartGameScene. Rețineți că forțăm să dezlegăm scenă  pentru a accesa scena mărimea și scaleMode proprietăţi.

Acest lucru completează Jucător clasă. Acum trebuie să sunăm a muri  și ucide metode în didBeginContact (_ :) metodă.

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

Acum putem testa totul. O modalitate rapidă de a testa a muri metoda este prin a comenta moveInvaders apel în Actualizați(_:) metodă. După ce jucătorul moare și respinge de trei ori, ar trebui să te întorci la StartGameScene.

Pentru a testa ucide metoda, asigurați-vă că moveInvaders apelul nu este comentat. Seteaza invaderSpeed proprietate la o valoare mare, de exemplu, 200. Invadatorii ar trebui să ajungă la player foarte repede, ceea ce are ca rezultat o ucidere instantanee. Schimbare invaderSpeed înapoi la după ce ați terminat de testare.

2. Terminarea invadatorilor de ardere

Pe măsură ce jocul se află chiar acum, numai ultimul rând de invadatori poate declanșa gloanțe. Avem deja detectarea coliziunii când un glonț al jucătorului lovește un invadator. În acest pas, vom elimina un invadator care este lovit de un glonț și va adăuga un rând de invadatori până la gama de invadatori care pot declanșa. Adăugați următoarele la didBeginContact (_ :) metodă.

func didBeginContact (contact: SKPhysicsContact) ... dacă ((firstBody.categoryBitMask & CollisionCategories.Invader! = 0) && (secondBody.categoryBitMask & CollisionCategories.PlayerBullet! = 0) if (contact.bodyA.node? .parent == nil || contact.bodyB.node? .parent == nil) return permite invadatorilorPerRow = invaderNum * 2 + 1 let theInvader = firstBody.node as! Invadator lăsați newInvaderRow = theInvader.invaderRow - 1 let newInvaderColumn =Invader.invaderColumn dacă (newInvaderRow> = 1) self.enumerateChildNodesWithName ("invader") nod, stop în let invader = nod ca! Invadator dacă invader.invaderRow == newInvaderRow && invader.invaderColumn == newInvaderColumn self.invadersWhoCanFire.append (invadator) stop.memory = true invadaIndex = findIndex (invadersWhoCanFire, valueToFind: firstBody.node? invaderIndex! = nil) invadersWhoCanFire.removeAtIndex (invaderIndex!) theInvader.removeFromParent () secondBody.node? .removeFromParent ()

Am eliminat NSLog declarație și verificați mai întâi dacă contact.bodyA.node? .parent și contact.bodyB.node? .parent nu sunt zero. Acestea vor fi zero dacă am procesat deja acest contact. În acest caz, revenim din funcție.

Noi calculam invadersPerRow așa cum am făcut și mai înainte theInvader la firstBody.node, turnând-o într-un Invadator. Apoi, vom obține newInvaderRow prin scăderea 1 si newInvaderColumn, care rămâne același.

Vrem doar să permitem invadatorilor să declanșeze focalizarea newInvaderRow este mai mare sau egal cu 1, altfel am încerca să setăm un invadator în rând ca să poți trage. Nu există niciun rând 0 astfel încât aceasta ar putea cauza o eroare.

Apoi, enumerăm prin invadatori, căutând invadatorul care are rândul și coloana corectă. Odată ce este găsit, îl adăugăm la invadersWhoCanFire matrice și apel stop.memory la Adevărat astfel încât enumerarea se va opri devreme.

Trebuie să găsim invadatorul care a fost lovit cu un glonț în invadersWhoCanFire astfel încât să putem elimina. În mod normal, matricele au un fel de funcționalitate, cum ar fi Index de metoda sau ceva similar pentru a realiza acest lucru. La momentul scrierii, nu există o astfel de metodă pentru matrice în limba Swift. Biblioteca standard Swift definește a găsi pe care am putea să o folosim, dar am găsit o metodă în secțiunile referitoare la medicamentele generice din Ghidul Limbaj de Programare Swift, care va realiza ceea ce avem nevoie. Funcția este bine numită findIndex. Adăugați următoarele în partea de jos a paginii GameScene.swift.

func findIndex(matrice: [T], valoareToFind: T) -> Int? pentru (index, valoare) în enumerate (array) if value == valueToFind return index return zero 

Dacă sunteți interesat de modul în care funcționează această funcție, vă recomandăm să citiți mai multe despre medicamentele generice în Ghidul limbii de programare Swift.

Acum că avem o metodă pe care o putem folosi pentru a găsi invadatorul, îl invocăm, trecând prin invadersWhoCanFire array și theInvader. Verificăm dacă invaderIndex nu este egal cu zero și eliminați invadatorul din invadersWhoCanFire folosind matricea removeAtIndex (index: Int) metodă.

Acum puteți testa dacă funcționează așa cum ar trebui. O modalitate ușoară ar fi să comentezi unde e chemarea player.die în didBeginContact (_ :) metodă. Asigurați-vă că eliminați comentariul când ați terminat testarea. Observați că programul se blochează dacă ucizi toți invadatorii. Vom remedia acest lucru în pasul următor.

Aplicația se blochează, deoarece avem SKAction repeatActionForever (_ :) cerând invadatorii să tragă gloanțe. În acest moment, nu mai sunt invadatori rămași pentru a trage gloanțele, astfel încât jocurile se blochează. Putem rezolva acest lucru prin verificarea este gol proprietate pe invadersWhoCanFire matrice. Dacă matricea este goală, nivelul sa terminat. Introduceți următoarele în fireInvaderBullet metodă.

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

Nivelul este complet, ceea ce înseamnă că noi creștem invaderNum, care este folosit pentru nivele. De asemenea, invocăm nivel complet, pe care încă mai trebuie să-l creăm în etapele care vin.

3. Finalizarea unui nivel

Trebuie să avem un anumit număr de niveluri. Dacă nu, după mai multe runde vom avea atât de mulți invadatori încât nu se vor potrivi pe ecran. Adăugați o proprietate maxLevels la GameScene clasă.

Clasa jocului Scenă: SKScene, SKPhysicsContactDelegate ... permiteți jucătorului: Player = Player () permite maxLevels = 3

Acum adaugati nivel complet la sfârșitul metodei GameScene.swift.

func nivelComplete () 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()  

Mai întâi verificăm dacă invaderNum este mai mică sau egală cu maxLevels am stabilit. Dacă da, trecem la LevelCompletScene, altfel am resetat invaderNum la 1 și sunați joc nou. LevelCompleteScene nu există încă și nici nu joc nou astfel încât să abordăm aceste probleme la un moment dat în următoarele două etape.

4. Implementarea LevelCompleteScene Clasă

Creaza un nou Cocoa Touch Class numit LevelCompleteScene care este o subclasă de SKScene. Punerea în aplicare a clasei arată astfel:

Importul fundației import SpriteKit clasa LevelCompleteScene: SKScene override func didMoveToView (vizualizare: SKView) auto.backgroundColor = SKColor.blackColor () permite startGameButton = SKSpriteNode (imageNamed: "nextlevelbtn") startGameButton.position = CGPointMake (size.width / size.height / 2 - 100) startGameButton.name = "nextlevel" addChild (startGameButton) suprascrie func atingeBegan (atinge: Set, cu eveniment Eveniment: UIEvent) lasa touch = atinge prima data ca! UITdot lăsați touchLocation = touch.locationInNode (auto) lasă touchedNode = self.nodeAtPoint (touchLocation) dacă (touchedNode.name == "nextlevel") lăsa gameOverScene = GameScene (dimensiune: size) gameOverScene.scaleMode = scaleMode let transitionType = SKTransition. flipHorizontalWithDuration (0.5) vizualizare? .presentScene (gameOverScene, transition: transitionType)

Implementarea este identică cu StartGameScreen clasa, cu excepția setării Nume proprietatea startGameButton la "Nivelul următor". Acest cod ar trebui să fie familiar. Dacă nu, reveniți la prima parte a acestui tutorial pentru o reîmprospătare.

5. joc nou

joc nou metoda pur și simplu trecerea înapoi la StartGameScene. Adăugați următoarele în partea de jos a paginii GameScene.swift.

func newGame () lasa jocOverScene = StartGameScene (dimensiune: dimensiune) gameOverScene.scaleMode = scaleMode lasa transitionType = SKTransition.flipHorizontalWithDuration (0.5) view? .presentScene (gameOverScene, transition: transitionType) 

Dacă testați aplicația, puteți să jucați câteva nivele sau să pierdeți câteva jocuri, dar jucătorul nu are cum să se miște și acest lucru face ca jocul să fie plictisitor. Să rezolvăm asta în pasul următor.

6. Mutarea playerului utilizând accelerometrul

Vom folosi accelerometrul pentru a muta jucătorul. Mai întâi trebuie să importăm CoreMotion cadru. Adăugați o declarație de import pentru cadrul din partea de sus a paginii GameScene.swift.

importați SpriteKit import CoreMotion

De asemenea, avem nevoie de câteva proprietăți noi.

permiteți maxLevels = 3 ani motionManager: CMMotionManager = CMMotionManager () var accelerationX: CGFloat = 0.0

Apoi, adăugați o metodă setupAccelerometer la fundul GameScene.swift.

func accelerometru () motionManager.accelerometerUpdateInterval = 0.2 motionManager.startAccelerometerUpdatesToQueue (NSOperationQueue.currentQueue (), cuHandler: (accelerometerData: CMAccelerometruData !, eroare: NSError!) în accelerare = accelerometruData.acceleration self.accelerationX = CGFloat (acceleration.x )

Aici am setat accelerometerUpdateInterval, care este intervalul în secunde pentru furnizarea de actualizări la handler. am găsit 0.2 funcționează bine, puteți încerca diferite valori dacă doriți. În interiorul manipulatorului; o închidere, vom obține accelerometerData.acceleration, care este o structură de tip CMAcceleration.

struct CMAcelerare var x: Dublu var: Dublu var z: Dublu init () init (x x: Dublu, y y: Dublu, z z: Dublu)

Suntem interesați doar de X proprietate și vom folosi conversia de tip numeric pentru al arunca la CGFloat pentru noi accelerationX proprietate.

Acum că avem accelerationX set de proprietate, putem muta jucătorul. Noi facem asta în didSimulatePhysics metodă. Adăugați următoarele în partea de jos a paginii GameScene.swift.

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

Invoca setupAccelerometer în didMoveToView (_ :) și ar trebui să puteți muta jucătorul cu accelerometrul. Există o singură problemă. Jucătorul se poate deplasa în afara ecranului în ambele părți și este nevoie de câteva secunde pentru a-l lua înapoi. Putem rezolva aceasta folosind motoarele fizice și coliziuni. Facem acest lucru în pasul următor.

override funcția didMoveToView (vizualizare: SKView) ... setupInvaders () setupPlayer () invokeInvaderFire () setupAccelerometer ()

7. Restricționarea mișcării jucătorului

După cum sa menționat în pasul anterior, jucătorul se poate muta off-screen. Aceasta este o rezolvare simplă folosind motorul fizicii Sprite Kit. Mai întâi, adăugați un nou CollisionCategory numit EdgeBody.

struct CollisionCategories inversor static: 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 

Stabiliți acest lucru ca pe jucător collisionBitMask în init metodă.

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

În cele din urmă, vom crea un physicsBody pe scena în sine. Adăugați următoarele la didMoveToView (vizualizați: SKView) metoda în GameScene.swift.

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

Inițializăm un corp fizic invocând init (edgeLoopFromRect :), trecând în scenă cadru. Initializatorul creează o buclă de margine de pe cadrul scenei. Este important să rețineți că o margine nu are volum sau masă și este tratată întotdeauna ca și cum proprietatea dinamică este egală cu fals. De asemenea, muchiile se pot ciocni doar cu corpurile de fizică bazate pe volum, pe care le are jucătorul nostru.

Am setat și categoryBitMask la CollisionCategories.EdgeBody. Dacă testați aplicația, s-ar putea să observați că nava dvs. nu se mai poate deplasa în afara ecranului, dar uneori se rotește. Atunci când un corp fizic se ciocnește cu un alt corp fizic, este posibil ca aceasta să aibă ca rezultat o rotație. Acesta este comportamentul implicit. Pentru a remedia acest lucru, am stabilit allowsRotation la fals în Player.swift.

override init () ... self.physicsBody? .collisionBitMask = CollisionCategories.EdgeBody auto.physicsBody? .allowsRotation = false animate ()

8. Camp câmp

Pasul 1: Crearea câmpului Star

Jocul are un câmp în mișcare în fundal. Putem crea câmpul de pornire folosind motorul cu particule Sprite Kit.

Creați un fișier nou și selectați Resursă de la iOS secțiune. Alege SpriteKit Particle File ca șablon și faceți clic pe Următor →. Pentru Șablon de particule alege ploaie și salvați-l ca pe Starfield. Clic Crea pentru a deschide fișierul în editor. Pentru a vedea opțiunile, deschideți SKNode Inspector pe dreapta.


În loc să treceți prin fiecare setare aici, ceea ce ar dura mult timp, ar fi mai bine să citiți documentația pentru a afla despre fiecare setare individuală. Nu voi intra în detaliu despre setările câmpului de pornire. Dacă sunteți interesat, deschideți fișierul în Xcode și consultați setările pe care le-am folosit.

Pasul 2: Adăugarea câmpului Star la scene

Adăugați următoarele la didMoveToView (_ :) în StartGameScene.swift.

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

Noi folosim un SKEmitterNode pentru a încărca StarField.sks fișier, setați-l poziţie și să-i dai jos zPosition. Motivul pentru cei mici zPosition este să vă asigurați că nu împiedică utilizatorul să atingă butonul de pornire. Sistemul de particule generează sute de particule, așadar prin stabilirea cu adevărat scăzută depășim această problemă. De asemenea, trebuie să știți că puteți configura manual toate proprietățile particulelor pe un SKEmitterNode, deși este mult mai ușor de utilizat editorul pentru a crea un .SKS fișier și încărcați-l la runtime.

Acum adăugați câmpul stea la GameScene.swift și LevelCompleteScene.swift. Codul este exact la fel ca mai sus.

9. Implementarea PulsatingText Clasă

Pasul 1: Creați PulsatingText Clasă

StartGameScene și LevelCompleteScene au text care crește și se micșorează în mod repetat. Vom subclasa SKLabeNode și folosiți câteva SKAction pentru a realiza acest efect.

Creaza un nou Cocoa Touch Class care este o subclasă de SKLabelNode,numeste PulsatingText, și adăugați codul următor.

importă Importul UIKit Clasa SpriteKit PulsatingText: SKLabelNode func setTextFontSizeAndPulsate (Text: String, theFontSize: CGFloat) self.text = TheText; auto.fontSize = theFontSize permite scaraSequence = SKAction.sequence ([SKAction.scaleTo (2, duration: 1), SKAction.scaleTo (1.0, duration: 1)]) scaFacever = SKAction.repeatActionForever (scaleSequence) )

Unul dintre primele lucruri pe care le-ați observat este că nu există un inițializator. Dacă subclasa dvs. nu definește un inițializator desemnat, acesta moștenește automat toate inițialele sale desemnate de superclass.

Avem o metodă setTextFontSizeAndPulsate (theText: theFontSize :), care face exact ceea ce spune. Setează SKLabelNode„s text și marimea fontului proprietăți și creează un număr de SKAction instanțe pentru a face scalarea textului și apoi înapoi în jos, creând un efect pulsatoriu.

Pasul 2: Adăugați PulsatingText la StartGameScene

Adăugați următorul cod la StartGameScene.swift în didMoveToView (_ :).

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

Inițializăm a PulsatingText instanță, invaderText, și invoca setTextFontSizeAndPulsate (theText: theFontSize :) pe el. Apoi am stabilit-o poziţie și adăugați-o la scenă.

Pasul 3: Adăugați PulsatingText la LevelCompleteScene

Adăugați următorul cod la LevelCompleteScene.swift în didMoveToView (_ :).

 override funcția didMoveToView (vizualizare: SKView) auto.backgroundColor = SKColor.blackColor () permite invaderText = PulsatingText (fontNamed: "ChalkDuster") invaderText.setTextFontSizeAndPulsate ("LEVEL COMPLETE", theFontSize: 50) invaderText.position = CGPointMake lățime / 2, size.height / 2 + 200) addChild (invaderText)

Acesta este exact același pas ca și cel precedent. Numai textul în care trecem este diferit.

10. Luarea jocului în continuare

Aceasta termină jocul. Am câteva sugestii pentru a vă putea extinde jocul. În interiorul imagini dosar, există trei imagini invadatoare diferite. Când adăugați invadatori în scenă, alegeți aleator unul dintre aceste trei imagini. Va trebui să actualizați inițializatorul invadatorului pentru a accepta o imagine ca parametru. Consultați Glonţ clasa pentru un indiciu.

Există, de asemenea, o imagine OZN. Încercați să faceți acest lucru și să vă deplasați pe ecran la fiecare cincisprezece secunde. Dacă jucătorul îl lovește, dă-i o viață suplimentară. Poate doriți să limitați numărul de vieți pe care aceștia le pot avea dacă faceți acest lucru. În cele din urmă, încercați să faceți un HUD pentru viața jucătorilor.

Acestea sunt doar câteva sugestii. Încercați și faceți jocul propriu.

Concluzie

Acest lucru duce la încheierea acestei serii. Ar trebui să aveți un joc care seamănă foarte mult cu jocul inițial Space Invaders. Sper că ați găsit acest tutorial util și ați învățat ceva nou. Vă mulțumim pentru lectură.

Cod