În partea anterioară a acestei serii, am implementat piedicile pentru clasele principale ale jocului. În acest tutorial, vom primi invadatorii în mișcare, gloanțe de ardere atât pentru invadatori și jucător, și să pună în aplicare detectarea coliziunii. Să începem.
Vom folosi scena Actualizați
pentru a muta invadatorii. Ori de câte ori doriți să mutați ceva manual, faceți clic pe Actualizați
metoda este în general în cazul în care doriți să faceți acest lucru.
Înainte de a face acest lucru, totuși, trebuie să actualizăm rightBounds
proprietate. A fost inițial stabilită 0, pentru că trebuie să folosim scena mărimea
pentru a seta variabila. Nu am reușit să facem acest lucru în afara metodelor clasei, astfel încât vom actualiza această proprietate în didMoveToView (_ :)
metodă.
suprascrie func didMoveToView (vizualizare: SKView) backgroundColor = SKColor.blackColor () rightBounds = auto.size.width - 30 setupInvaders () setupPlayer ()
Apoi, implementați moveInvaders
mai jos setupPlayer
metoda pe care ați creat-o în tutorialul anterior.
func moveInvaders () var changeDirection = false enumerateChildNodesWithName ("invader") nod, opriți în let invader = nod ca! SKSpriteNode permite invaderHalfWidth = invader.size.width / 2 invader.position.x - = CGFloat (auto.invaderSpeed) dacă (invader.position.x> self.rightBounds - invaderHalfWidth || invader.position.x < self.leftBounds + invaderHalfWidth) changeDirection = true if(changeDirection == true) self.invaderSpeed *= -1 self.enumerateChildNodesWithName("invader") node, stop in let invader = node as! SKSpriteNode invader.position.y -= CGFloat(46) changeDirection = false
Vom declara o variabilă, schimba directia
, pentru a urmări când invadatorii trebuie să schimbe direcția, să se miște în stânga sau să meargă la dreapta. Apoi folosim enumerateChildNodesWithName (usingBlock :)
, care caută copiii unui nod și cheamă închiderea o dată pentru fiecare nod de potrivire pe care îl găsește cu numele de potrivire "invadator". Închiderea acceptă doi parametri, nodul
este nodul care se potrivește cu Nume
și Stop
este un pointer la o variabilă booleană pentru a termina enumerarea. Nu vom folosi Stop
aici, dar este bine să știți pentru ce este folosit.
Am aruncat nodul
la un SKSpriteNode
exemplu care invadator
este o subclasă de, obține jumătate din lățimea sa invaderHalfWidth
, și să își actualizeze poziția. Atunci verificăm dacă este poziţie
este în limitele, leftBounds
și rightBounds
, și, dacă nu, am stabilit schimba directia
la Adevărat
.
Dacă schimba directia
este Adevărat
, ne negăm invaderSpeed
, care va schimba direcția în care se deplasează invadatorul. Apoi enumerăm prin invadatori și actualizăm poziția lor y. În cele din urmă, am stabilit schimba directia
înapoi la fals
.
moveInvaders
metoda este numită în Actualizați(_:)
metodă.
suprascrie func actual (actualTime: CFTimeInterval) moveInvaders ()
Dacă testați aplicația acum, ar trebui să vedeți invadatorii să se deplaseze la stânga, la dreapta și apoi în jos dacă ating limitele pe care le-am setat pe ambele părți.
fireBullet
Atât de des vrem ca unul dintre invadatori să tragă un glonț. În starea actuală, invadatorii din rândul de jos sunt pregătiți să tragă un glonț, pentru că se află în invadersWhoCanFire
mulțime.
Atunci când un invadator este lovit de un glonț al jucătorului, atunci rândul invadatorului un rând în sus și în aceeași coloană va fi adăugat la invadersWhoCanFire
array, în timp ce invadatorul care a fost lovit va fi eliminat. În acest fel, numai invadatorul de jos din fiecare coloană poate declanșa gloanțe.
Adaugă fireBullet
metoda pentru a InvaderBullet
clasă în InvaderBullet.swift.
func fireBullet (scena: SKScene) lăsați bullet = InvaderBullet (imageName: "laser", bulletSound: nil) bullet.position.x = auto.position.x bullet.position.y = auto.position.y - self.size. înălțime / 2 scene.addChild (bullet) permite moveBulletAction = SKAction.moveTo (CGPoint (x: self.position.x, y: 0 - bullet.size.height), duration: 2.0) letBLletAction = SKAction.removeFromParent .runAction (efectul SKAction.sequence ([moveBulletAction, removeBulletAction])
În fireBullet
metoda, instanțiăm un InvaderBullet
exemplu, trecând "Cu laser" pentru imageName
, și pentru că nu vrem să jucăm un sunet, trecem zero
pentru bulletSound
. Am stabilit-o poziţie
să fie la fel ca invadatorul, cu o mică dispunere în poziția y și să o adăugăm la scenă.
Creăm două SKAction
instanțe, moveBulletAction
și removeBulletAction
. moveBulletAction
acțiunea mută glonțul la un anumit punct pe o anumită durată în timp ce removeBulletAction
acțiunea o elimină din scenă. Invocând secvenţă(_:)
pe aceste acțiuni, acestea vor rula succesiv. De aceea am menționat waitForDuration
atunci când joci un sunet în partea anterioară a acestei serii. Dacă creați un SKAction
obiect invocând playSoundFileNamed (_: waitForCompletion :)
și stabilit waitForCompletion
la Adevărat
, atunci durata acelei acțiuni ar fi atâta timp cât sunetul se joacă, altfel ar trece imediat la următoarea acțiune din secvență.
invokeInvaderFire
Adaugă invokeInvaderFire
sub celelalte metode pe care le-ați creat GameScence.swift.
func invakeInvaderFire () let FireBullet = SKAction.runBlock () self.fireInvaderBullet () să aștepteToFireInvaderBullet = SKAction.waitForDuration (1.5) let invaderFire = SKAction.sequence ([fireBullet, waitToFireInvaderBullet]) repeatForeverAction = SKAction.repeatActionForever ) runAction (repeatForeverAction)
runBlock (_ :)
metodă a SKAction
clasa creează un SKAction
instanță și invocă imediat închiderea transmisă runBlock (_ :)
metodă. În închidere, invocăm fireInvaderBullet
metodă. Pentru că invocăm această metodă într-o închidere, trebuie să o folosim de sine
să-l suni.
Apoi creăm un SKAction
instanță numită waitToFireInvaderBullet
invocând waitForDuration (_ :)
, trecând în numărul de secunde de așteptat înainte de a porni. Apoi, vom crea un SKAction
instanță, invaderFire
, invocând secvenţă(_:)
metodă. Această metodă acceptă o colecție de acțiuni invocate de către invaderFire
acțiune. Vrem ca această secvență să se repete pentru totdeauna, așa că vom crea o acțiune numită repeatForeverAction
, treci în SKAction
obiecte de repetat și invocați runAction
, trecerea în repeatForeverAction
acțiune. Metoda runAction este declarată în SKNode
clasă.
fireInvaderBullet
Adaugă fireInvaderBullet
mai jos invokeInvaderFire
metoda introdusă în pasul anterior.
func fireInvaderBullet () permite randomInvader = invadersWhoCanFire.randomElement () randomInvader.fireBullet (self)
În această metodă, numim ceea ce pare a fi o metodă numită randomElement
care va returna un element aleatoriu din invadersWhoCanFire
array, și apoi apela fireBullet
metodă. Nu există, din păcate, nicio construcție randomElement
metoda pe mulțime
structura. Cu toate acestea, putem crea un mulțime
pentru a oferi această funcționalitate.
randomElement
Mergi la Fişier > Nou > Fişier… și alegeți Swift File. Facem ceva diferit decât înainte, deci asigurați-vă că alegeți Swift File si nu Cocoa Touch Class. presa Următor → și numele fișierului Utilități. Adăugați următoarele la Utilities.swift.
(int = arc4random_uniform (UInt32 (self.count))) întoarce auto [index]
Extinde mulțime
pentru a avea o metodă numită randomElement
. arc4random_uniform
funcția returnează un număr între 0 și orice treceți. Deoarece Swift nu convertește implicit tipuri de numerotare, trebuie să facem conversia noi înșine. În cele din urmă, vom returna elementul matricei la index index
.
Acest exemplu ilustrează cât de ușor este să adăugați funcționalitate structurii și clasei. Puteți citi mai multe despre crearea de extensii în Limba de programare Swift.
Cu toate astea din cale, acum putem trage gloanțele. Adăugați următoarele la didMoveToView (_ :)
metodă.
suprascrie func didMoveToView (vizualizare: SKView) ... setupPlayer () invokeInvaderFire ()
Dacă testați aplicația acum, în fiecare secundă ar trebui să vedeți unul dintre invadatorii din rândul inferior să tragă un glonț.
fireBullet (scena :)
Adăugați următoarea proprietate la Jucător
clasă în Player.swift.
Player clasa: SKSpriteNode private var canFire = true
Vrem să limităm de câte ori jucătorul poate trage un glonț. canFire
proprietatea va fi utilizată pentru a reglementa acest lucru. Apoi, adăugați următoarele la fireBullet (scena :)
metodă în Jucător
clasă.
func fireBullet (scena: SKScene) if (! canFire) return altceva canFire = false let bullet = PlayerBullet (imageName: "laser", bulletSound: "laser.mp3") bullet.position.x = x bullet.position.y = auto.position.y + auto.size.height / 2 scena.addChild (bullet) lasa moveBulletAction = SKAction.moveTo (CGPoint (x: self.position.x, y: scene.size.height + bullet.size.height), duration: 1.0) lasă removeBulletAction = SKAction.removeFromParent () bullet.runAction (SKAction.sequence ([moveBulletAction, removeBulletAction])) waitToEnableFire = SKAction.waitForDuration (0.5) runAction (waitToEnableFire, self.canFire = true)
În primul rând, asigurați-vă că playerul este capabil să declanșeze dacă verificați dacă canFire
este setat sa Adevărat
. Dacă nu, vom reveni imediat din metodă.
Dacă jucătorul poate trage, am setat canFire
la fals
așa că nu pot trage imediat un alt glonț. Apoi instanțiăm a PlayerBullet
exemplu, trecând "Cu laser" pentru imageNamed
parametru. Pentru că vrem să se joace un sunet când jucătorul face un glonț, intrăm "Laser.mp3" pentru bulletSound
parametru.
Apoi am setat poziția bulletului și l-am adăugat pe ecran. Următoarele câteva linii sunt identice cu cele de la Invadator
„sfireBullet
metodă prin faptul că mutăm glonțul și îl scoatem de pe scenă. Apoi, vom crea un SKAction
instanță, waitToEnableFire
, invocând waitForDuration (_ :)
clasă. În cele din urmă, invocăm runAction
, trecerea waitToEnableFire
, și la finalizarea set canFire
înapoi la Adevărat
.
Ori de câte ori utilizatorul atinge ecranul, vrem să tragem un glonț. Acest lucru este la fel de simplu ca și chemarea fireBullet
pe jucător
obiect în touchesBegan (_: withEvent :)
metodă a GameScene
clasă.
suprascrie funcțiileBegan (atinge: Set, cu eveniment Eveniment: UIEvent) player.fireBullet (self)
Dacă încercați acum aplicația, ar trebui să puteți declanșa un glonț când atingeți ecranul. De asemenea, ar trebui să auziți sunetul laser de fiecare dată când este declanșat un glonț.
Pentru a detecta când nodurile se ciocnesc sau fac contact între ele, vom folosi motorul fizic încorporat al lui Sprite Kit. Cu toate acestea, comportamentul implicit al motorului fizicii este că totul se ciocnește cu totul atunci când acestea au un corp fizic adăugat la ele. Avem nevoie de o modalitate de a separa ceea ce dorim să interacționăm unul cu celălalt și putem face acest lucru prin crearea de categorii la care apar corpuri fizice specifice.
Definiți aceste categorii utilizând o mască de biți care utilizează un număr întreg de 32 de biți cu 32 de steaguri individuale care pot fi activate sau deconectate. Acest lucru înseamnă, de asemenea, că puteți avea maxim 32 de categorii pentru jocul dvs. Acest lucru nu ar trebui să prezinte o problemă pentru majoritatea jocurilor, dar este ceva de reținut.
Adăugați următoarea definiție a structurii la GameScene
clasa, sub invaderNum
declarație în GameScene.swift.
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
Folosim o structură, CollsionCategories
, pentru a crea categorii pentru Invadator
, Jucător
, InvaderBullet
, și PlayerBullet
clase. Folosim deplasarea biților pentru a transforma biții.
Jucător
și InvaderBullet
ColiziuneInvaderBullet
pentru ColiziuneAdăugați următorul bloc de coduri la init (imageName: bulletSound :)
metoda în InvaderBullet.swift.
(imagineName: String, bulletSound: String?) super.init (imageName: imageName, bulletSound: bulletSound) self.physicsBody = SKPhysicsBody (textură: self.texture, size: self.size) self.physicsBody? .dynamic = adevarata auto.physicsBody? .usesPreciseCollisionDetection = adevarata auto.physicsBody? .categoryBitMask = CollisionCategories.InvaderBullet self.physicsBody? .contactTestBitMask = CollisionCategories.Player self.physicsBody? .collisionBitMask = 0x0
Există mai multe moduri de a crea un corp de fizică. În acest exemplu, folosim init (textura: dimensiune :)
inițializator, care va face detectarea coliziunii să utilizeze forma texturii pe care o parcurgem. Există mai multe alte inițializatoare disponibile, pe care le puteți vedea în referința de clasă SKPhysicsBody.
Am fi putut folosi cu ușurință init (rectangleOfSize :)
inițializator, deoarece gloanțele sunt de formă dreptunghiulară. Într-un joc atât de mic, nu contează. Cu toate acestea, trebuie să știți că utilizarea init (textura: dimensiune :)
metoda poate fi computațional costisitoare, deoarece trebuie să calculeze forma exactă a texturii. Dacă aveți obiecte cu formă dreptunghiulară sau circulară, atunci ar trebui să utilizați acele tipuri de inițializatoare dacă performanța jocului devine o problemă.
Pentru ca detecția coliziunilor să funcționeze, cel puțin unul dintre corpurile pe care le testează trebuie să fie marcat ca fiind dinamic. Prin setarea usesPreciseCollisionDetection
proprietate la Adevărat
, Sprite Kit utilizează o detectare mai precisă a coliziunii. Setați această proprietate la Adevărat
pe corpuri mici, cu mișcări rapide, precum gloanțele noastre.
Fiecare organism va aparține unei categorii și veți defini acest lucru prin setarea acestuia categoryBitMask
. Din moment ce acesta este InvaderBullet
clasa, am pus-o la CollisionCategories.InvaderBullet
.
Pentru a afla când acest organism a luat legătura cu un alt organism care vă interesează, setați contactBitMask
. Aici vrem să știm când InvaderBullet
a intrat în contact cu jucătorul, așa că vom folosi CollisionCategories.Player
. Deoarece o coliziune nu ar trebui să declanșeze forțe fizice, am stabilit collisionBitMask
la 0x0
.
Jucător
pentru CollisionAdăugați următoarele la init
metoda în Player.swift.
suprascrie init () permite textura = SKTexture (imageNamed: "player1") super.init (textură: textură, culoare: SKColor.clearColor (), dimensiune: texture.size ()) self.physicsBody = SKPhysicsBody textură, mărime: auto.size) self.physicsBody? .dynamic = adevărat self.physicsBody? .usesPreciseCollisionDetection = false self.physicsBody? .categoryBitMask = CollisionCategories.Player self.physicsBody? .contactTestBitMask = CollisionCategories.InvaderBullet | CollisionCategories.Invader auto.physicsBody? .CollisionBitMask = 0x0 animate ()
O mare parte din acest lucru ar trebui să fie familiar de la pasul anterior, așa că nu o voi rehash aici. Există două diferențe de observat totuși. Una este asta usesPreciseCollsionDetection
a fost setat la fals
, care este implicit. Este important să realizăm că doar unul dintre organismele de contact are nevoie de această proprietate Adevărat
(care a fost glontul). Cealaltă diferență este că, de asemenea, vrem să știm când jucătorul contactează un invadator. Puteți avea mai multe contactBitMask
categorie prin separarea lor cu bitul sau (|
) operator. În afară de asta, ar trebui să observați că este doar practic opus InvaderBullet
.
Invadator
și PlayerBullet
ColiziuneInvadator
pentru ColiziuneAdăugați următoarele la init
metoda în Invader.swift.
suprascrie init () permite textura = SKTexture (imageNamed: "invader1") super.init (textură: textură, culoare: SKColor.clearColor (), size: texture.size = SKPhysicsBody (textură: auto.texture, dimensiune: self.size) self.physicsBody? .Dynamic = adevărat self.physicsBody? .UsesPreciseCollisionDetection = false auto.physicsBody? .CategoryBitMask = CollisionCategories.Invader self.physicsBody? .ContactTestBitMask = CollisionCategories. PlayerBullet | CollisionCategories.Player self.physicsBody? .CollisionBitMask = 0x0
Toate acestea ar avea sens dacă ați urmat. Am creat physicsBody
, categoryBitMask
, și contactBitMask
.
PlayerBullet
pentru ColiziuneAdăugați următoarele la init (imageName: bulletSound :)
în PlayerBullet.swift. Din nou, implementarea ar trebui să fie cunoscută până acum.
(imagineName: String, bulletSound: String?) super.init (imageName: imageName, bulletSound: bulletSound) self.physicsBody = SKPhysicsBody (textură: self.texture, size: self.size) self.physicsBody? .dynamic = adevarul auto.physicsBody? .usesPreciseCollisionDetection = adevarata auto.physicsBody? .categoryBitMask = CollisionCategories.PlayerBullet self.physicsBody? .contactTestBitMask = CollisionCategories.Invader self.physicsBody? .collisionBitMask = 0x0
GameScene
Trebuie să înființăm GameScene
clasa pentru a pune în aplicare SKPhysicsContactDelegate
astfel încât să putem răspunde atunci când două corpuri se ciocnesc. Adăugați următoarele pentru a face GameScene
clasa este conformă cu SKPhysicsContactDelegate
protocol.
clasa de joc Scenă: SKScene, SKPhysicsContactDelegate
Apoi, trebuie să stabilim câteva proprietăți pe scenă physicsWorld
. Introduceți următoarele în partea de sus a paginii didMoveToView (_ :)
metoda în GameScene.swift.
suprascrie func didMoveToView (vizualizare: SKView) self.physicsWorld.gravity = CGVectorMake (0, 0) self.physicsWorld.contactDelegate = auto ...
Am setat gravitatie
proprietatea physicsWorld
la 0 astfel încât niciunul dintre corpurile de fizică din scenă nu este afectat de gravitate. Puteți face acest lucru și pe bază de corp, în loc de a stabili întreaga lume să nu aibă nici o gravitate prin setarea affectedByGravity
proprietate. Am setat și contactDelegate
proprietatea lumii fizicii de sine
, GameScene
instanță.
SKPhysicsContactDelegate
ProtocolPentru a se conforma GameScene
clasa la SKPhysicsContactDelegate
protocol, trebuie să punem în aplicare didBeginContact (_ :)
metodă. Această metodă este chemată atunci când două corpuri fac contact. Punerea în aplicare a directivei didBeginContact (_ :)
metoda arată astfel.
func didBeginContact (contact: SKPhysicsContact) var firstBody: SKPhysicsBody var secondBody: SKPhysicsBody if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask firstBody = contact.bodyA secondBody = contact.bodyB else firstBody = contact.bodyB secondBody = contact.bodyA if ((firstBody.categoryBitMask & CollisionCategories.Invader != 0) && (secondBody.categoryBitMask & CollisionCategories.PlayerBullet != 0)) NSLog("Invader and Player Bullet Conatact") if ((firstBody.categoryBitMask & CollisionCategories.Player != 0) && (secondBody.categoryBitMask & CollisionCategories.InvaderBullet != 0)) NSLog("Player and Invader Bullet Contact") if ((firstBody.categoryBitMask & CollisionCategories.Invader != 0) && (secondBody.categoryBitMask & CollisionCategories.Player != 0)) NSLog("Invader and Player Collision Contact")
Mai întâi declarăm două variabile firstBody
și secondBody
. Când două obiecte fac contact, nu știm care este corpul. Aceasta înseamnă că mai întâi trebuie să facem niște controale pentru a ne asigura firstBody
este cea cu cea mai mică categoryBitMask
.
Apoi, vom trece prin fiecare scenariu posibil folosind bitwise &
operator și categoriile de coliziune pe care le-am definit mai devreme pentru a verifica ce face contactul. Conectăm rezultatul la consola pentru a ne asigura că totul funcționează așa cum ar trebui. Dacă testați aplicația, toate contactele ar trebui să funcționeze corect.
Acesta a fost un tutorial destul de lung, dar acum avem invadatorii în mișcare, gloanțe fiind trase de la jucător și invadatori, și de detectare a contactelor care lucrează prin utilizarea măști de contact bit. Suntem pe punctul de a ajunge la jocul final. În următoarea și ultima parte a acestei serii, vom avea un joc complet.