Create Space Invaders med Swift og Sprite Kit: Implementing Gameplay
25
Del
3
Share < .no> Dette Cyber mandag Envato Tuts + kurs vil bli redusert til bare $ 3. Ikke gå glipp av
Dette innlegget er en del av en serie som heter Lag Space Invaders med Swift og Sprite Kit.Create Space Invaders med Swift og Sprite Kit. Gjennomførings ClassesCreate Space Invaders med Swift og Sprite Kit: Ferdigstilling GameplayWhat Du vil bli Opprette
I forrige del av denne serien, gjennomførte vi den stubber for spillets hovedklasser. I denne opplæringen, vil vi få inntrengerne bevegelse, kuler avfyring for både inntrengerne og spiller, og gjennomføre dueller. La oss komme i gang.
1. Flytte Invaders
Vi vil bruke scenens oppdatering metode for å flytte inntrengerne. Når du ønsker å flytte noe manuelt, oppdateringen metoden er vanligvis der du ønsker å gjøre dette.
Før vi gjør dette selv, må vi oppdatere rightBounds eiendommen. Det ble i utgangspunktet satt til 0, fordi vi trenger å bruke scenens størrelse for å sette variabelen. Vi klarte ikke å gjøre det utenfor noen av klassens metoder så vil vi oppdatere denne eiendommen i didMoveToView (_ :) metode
styre func didMoveToView (vis. SKView) {bakgrunnsfarge = SKColor.blackColor () rightBounds = selvtillit. size.width -. 30 setupInvaders () setupPlayer ()}
Deretter implementere moveInvaders metoden under setupPlayer metode du opprettet i forrige tutorial
func moveInvaders () {var changeDirection = false enumerateChildNodesWithName ("invader") {node, stopp la inntrenger = node som i! SKSpriteNode la invaderHalfWidth = invader.size.width /2 invader.position.x - = CGFloat (self.invaderSpeed) if (invader.position.x > self.rightBounds - invaderHalfWidth || invader.position.x < self.leftBounds + invaderHalfWidth) {changeDirection = true}} if (changeDirection == true) {self.invaderSpeed * = -1 self.enumerateChildNodesWithName ("invader") {node, slutter i la inntrenger = node som! SKSpriteNode invader.position.y - = CGFloat (46)} changeDirection = false}}
Vi erklærer en variabel, changeDirection, for å holde orden når inntrengerne må endre retning, flytte seg til venstre eller flytte rett. Vi deretter bruke enumerateChildNodesWithName (usingBlock :) metoden, som søker en node barn og kaller nedleggelsen en gang for hver samsvar node den finner sammenstillings navnet "inntrenger". Nedleggelsen aksepterer to parametre, node er noden som samsvarer med navnet og stopp er en peker til en boolsk variabel til å si opp telling. Vi vil ikke være å bruke stopp her, men det er godt å vite hva det brukes til.
Vi kastet node til en SKSpriteNode eksempel som inntrengeren er en underklasse av, få halve bredden invaderHalfWidth, og oppdatere sin posisjon. Vi sjekker da om sin posisjon er innenfor grensene, leftBounds og rightBounds, og hvis ikke, setter vi changeDirection til sann.
Hvis changeDirection er sant, negere vi invaderSpeed, som vil endre retningen de inntrenger beveger seg i . Vi nummerere gjennom inntrengerne og oppdatere sin y posisjon. Til slutt satt vi changeDirection tilbake til false
moveInvaders metoden kalles i oppdateringen (_ :) metode
styre func oppdatering (currentTime:.. CFTimeInterval) {moveInvaders ()}
Hvis du teste programmet nå, bør du se inntrengerne gå til venstre, høyre og deretter ned hvis de når grensene vi har satt på hver side.
2. Firing Invader Bullets
Trinn 1: fireBullet
Hver så ofte vi ønsker en av inntrengerne til å fyre av en kule. Som det står nå, er inntrengerne i nederste rad satt opp til å fyre av en kule, fordi de er i invadersWhoCanFire array.
Når en inntrenger blir truffet av en spiller kule, da inntrengeren én rad opp og i samme kolonne vil bli lagt til invadersWhoCanFire array, mens inntrengeren som ble påkjørt vil bli fjernet. På denne måten bare den nederste inntrenger på hver kolonne kan skyte kuler
Legg til fireBullet metoden til InvaderBullet klassen i InvaderBullet.swift
func fireBullet (scene: SKScene).. {La bullet = InvaderBullet (imageName: "laser", bulletSound: null) bullet.position.x = self.position.x bullet.position.y = self.position.y - self.size.height /2 scene.addChild (bullet) la moveBulletAction = SKAction.moveTo (CGPoint (x: self.position.x, y: 0 - bullet.size.height), varighet: 2,0) la removeBulletAction = SKAction.removeFromParent () bullet.runAction (SKAction.sequence ([moveBulletAction, removeBulletAction]))}
I fireBullet metoden, instantiate vi en InvaderBullet eksempel bestått i "laser" for imageName, og fordi vi ikke ønsker en lyd å spille passerer vi i null for bulletSound. Vi setter sin posisjon til å være den samme som inntrenger-tallet, med en liten forskyvning på y posisjon, og legge den til scenen.
Vi skaper to SKAction tilfeller moveBulletAction og removeBulletAction. Den moveBulletAction handlingen beveger kulen til et visst punkt over en viss varighet mens removeBulletAction handlingen fjerner det fra scenen. Ved å påberope sekvensen (_ :) metoden på disse handlingene, vil de kjøre sekvensielt. Dette er grunnen til at jeg nevnte waitForDuration metoden når du spiller av en lyd i forrige del av denne serien. Hvis du oppretter en SKAction objekt ved å påberope playSoundFileNamed (_: waitForCompletion :) og satt waitForCompletion til sann, så varigheten av at handlingen ville være så lenge som spiller lyden, ellers ville hoppe umiddelbart til neste handling i sekvensen.
Trinn 2:. invokeInvaderFire
Legg til invokeInvaderFire metoden under de andre metodene du har opprettet i GameScence.swift
func invokeInvaderFire () {la fireBullet = SKAction.runBlock () {self.fireInvaderBullet ()} la waitToFireInvaderBullet = SKAction.waitForDuration (1,5) la invaderFire = SKAction.sequence ([fireBullet, waitToFireInvaderBullet]) la repeatForeverAction = SKAction.repeatActionForever (invaderFire) runAction (repeatForeverAction)}
runBlock (_ :) metoden i SKAction klassen skaper en SKAction forekomst og umiddelbart påkaller nedleggelsen sendes til runBlock (_ :) metoden. I nedleggelse, påberope vi fireInvaderBullet metoden. Fordi vi benytter denne metoden i en nedleggelse, må vi bruke selv for å kalle det.
Vi deretter opprette en SKAction eksempel heter waitToFireInvaderBullet ved å påberope waitForDuration (_ :), passerer i antall sekunder å vente før du flytter på. Deretter oppretter vi en SKAction eksempel invaderFire, ved å påberope sekvensen (_ :) metoden. Denne metoden kan brukes med en samling av tiltak som er påberopt av invaderFire handling. Vi ønsker denne sekvensen å gjenta for alltid slik at vi skaper en handling som heter repeatForeverAction, passerer i SKAction objekter for å gjenta, og påberope runAction, passerer i repeatForeverAction aksjon. Den runAction metoden er deklarert i SKNode klassen
Trinn 3:. FireInvaderBullet
Legg til fireInvaderBullet metoden under invokeInvaderFire metoden du skrev i forrige trinn
func fireInvaderBullet (. ) {la randomInvader = invadersWhoCanFire.randomElement () randomInvader.fireBullet (egen-)}
I denne metoden, kaller vi det som synes å være en metode som heter randomElement som ville returnere et tilfeldig element ut av invadersWhoCanFire array, og deretter ringe sin fireBullet metoden. Det er dessverre ingen innebygd randomElement metoden på Array struktur. Men vi kan skape en Array forlengelse for å gi denne funksjonaliteten
Trinn 4:. Implementere randomElement
Gå til Fil > New > Fil ... og velg Swift fil. Vi gjør noe annerledes enn før, så bare sørg for at du velger Swift File og ikke Cocoa Touch Class. Trykk Neste og navnet på filen Utilities.
Import Foundationextension Array {func randomElement () Legg til følgende Utilities.swift - >.; T {la index = Int (arc4random_uniform (uint32 (self.count))) tilbake selv [index]}}
Vi utvider Array strukturen for å ha en metode som heter randomElement. Den arc4random_uniform funksjonen returnerer et tall mellom 0 og uansett hva du passere i. Fordi Swift ikke implisitt konvertere talltyper, må vi gjøre konverteringen selv. Endelig tilbake vi elementet i matrisen ved indeks indeksen.
Dette eksempelet illustrerer hvor enkelt det er å legge til funksjonalitet til strukturen og klasser. Du kan lese mer om hvordan du oppretter utvidelser i The Swift Programming Language
Trinn 5:. Firing the Bullet
Med alt dette ut av veien, vi kan nå fyre av kuler. Legg til følgende til didMoveToView (_ :) metode
styre func didMoveToView (vis. SKView) {... setupPlayer () invokeInvaderFire ()}
Hvis du tester programmet nå, hver andre eller så du bør se en av inntrengerne fra den nederste raden fyre av en kule.
3. Firing Player Bullets
Trinn 1: fireBullet (scene :)
Legg til følgende eiendom til Player klasse i Player.swift
klasse Player. SKSpriteNode {private Var canFire = true < p> Vi ønsker å begrense hvor ofte spilleren kan fyre av en kule. Den canFire Eiendommen vil bli brukt til å regulere det. Deretter legger du til følgende til fireBullet (scene :) metode i spiller klassen
func fireBullet (scene:.! SKScene) {if canFire () {return} else {canFire = false la bullet = PlayerBullet (imageName: " laser ", bulletSound:" laser.mp3 ") bullet.position.x = self.position.x bullet.position.y = self.position.y + self.size.height /2 scene.addChild (bullet) la moveBulletAction = SKAction.moveTo (CGPoint (x: self.position.x, y: scene.size.height + bullet.size.height), varighet: 1,0) la removeBulletAction = SKAction.removeFromParent () bullet.runAction (SKAction.sequence ([ ,,,0],moveBulletAction, removeBulletAction])) la waitToEnableFire = SKAction.waitForDuration (0,5) runAction (waitToEnableFire, ferdigstillelse: {self.canFire = true})}}
Vi først sørge for at spilleren er i stand til å skyte ved å sjekke om canFire er satt til sann. Hvis det ikke er det, vi umiddelbart tilbake fra metoden.
Hvis spilleren kan skyte, setter vi canFire til false slik at de ikke umiddelbart kan skyte en annen kule. Vi deretter bruke et PlayerBullet eksempel bestått i "laser" for imageNamed parameter. Fordi vi ønsker en lyd å spille når spilleren fyrer av en kule, vi passere i "laser.mp3" for bulletSound parameter.
Vi satt kulen posisjon og legge den til skjermen. De neste par linjene er den samme som Invader er fireBullet metode ved at vi flytter i kule og fjerne det fra scenen. Deretter oppretter vi en SKAction eksempel waitToEnableFire, ved å påberope seg waitForDuration (_ :) klassemetode. Til slutt, påberope vi runAction, passerer i waitToEnableFire, og ved ferdigstillelse satt canFire tilbake til true
Trinn 2:. Firing spilleren Bullet
Når brukeren berører skjermen, vi ønsker å skyte en kule. Dette er så enkelt som å ringe fireBullet på spilleren objekt i touchesBegan (_: withEvent :) metoden i GameScene klassen
overstyring func touchesBegan (berører: Set < NSObject >, withEvent hendelsen. UIEvent) {spiller .fireBullet (egen-)}
Hvis du tester programmet nå, bør du være i stand til å avfyre en kule når du trykker på skjermen. I tillegg bør du høre laser lyden hver gang en kule er avfyrt.
4. Kollisjons Kategorier
For å oppdage når noder er å kollidere eller å ta kontakt med hverandre, vil vi bruke Sprite Kit innebygde fysikkmotor. Men standardvirkemåten til fysikkmotoren er at alt kolliderer med alt når de har en fysikk kroppen lagt til dem. Vi trenger en måte å skille det vi ønsker å kommunisere med hverandre, og vi kan gjøre dette ved å opprette kategorier for å hvilke spesifikke fysikk kropper tilhører.
Du definerer disse kategoriene ved hjelp av litt maske som bruker en 32-bit heltall med 32 individuelle flagg som kan være enten av eller på. Dette betyr også at du bare kan ha maksimalt 32 kategorier for spillet ditt. Dette bør ikke presentere et problem for de fleste spill, men det er noe å huske på.
Legg følgende struktur definisjon til GameScene klassen, under invaderNum erklæring i GameScene.swift.
struct CollisionCategories {statisk utleid Invader: uint32 = 0x1 < < 0 statiske la Player: uint32 = 0x1 < < En statisk let InvaderBullet: uint32 = 0x1 < < 2 statisk let PlayerBullet: uint32 = 0x1 < < 3}
Vi bruker en struktur, CollsionCategories, å opprette kategorier for Invader, spiller, InvaderBullet, og PlayerBullet klasser. Vi bruker litt skiftende for å slå de biter på.
5. Spiller og InvaderBullet Kollisjon
Trinn 1: Sette opp InvaderBullet for Kollisjon
Legg til følgende kode blokk til init (imageName. BulletSound :) metode i InvaderBullet.swift
styre init ( imageName: String, bulletSound: String) {super.init (imageName:? imageName, bulletSound: bulletSound) self.physicsBody = SKPhysicsBody (tekstur: self.texture, størrelse:? self.size) self.physicsBody .dynamic = sanne jeg. physicsBody? .usesPreciseCollisionDetection = true self.physicsBody? .categoryBitMask = CollisionCategories.InvaderBullet self.physicsBody? .contactTestBitMask = CollisionCategories.Player self.physicsBody? .collisionBitMask = 0x0}
Det er flere måter å lage en fysikk kroppen. I dette eksempelet bruker vi init (tekstur. Størrelse :) initializer, noe som vil gjøre dueller bruke formen på teksturen vi passere i Det finnes flere andre initializers tilgjengelig, som du kan se i SKPhysicsBody klassen referansen <. br>
Vi kunne lett ha brukt init (rectangleOfSize :) initializer, fordi kulene er rektangulær form. I en kamp denne lille det spiller ingen rolle. Vær imidlertid oppmerksom på at bruk av init (tekstur: størrelse :) metoden kan være beregningsmessig dyrt siden det har å beregne den nøyaktige formen på teksturen. Hvis du har objekter som er rektangulær eller rund i formen, så bør du bruke disse typer initializers hvis spillets ytelse blir et problem.
For dueller å jobbe, minst ett av likene du tester må være merket som dynamisk. Ved å sette usesPreciseCollisionDetection eiendom til sann, bruker Sprite Kit en mer presis dueller. Sett denne egenskapen til sann på små, raske bevegelige organer som våre kuler.
Hver kroppen vil tilhøre en kategori, og du definerer dette ved å sette sin categoryBitMask. Siden dette er den InvaderBullet klasse, setter vi det til CollisionCategories.InvaderBullet.
For å si når denne kroppen har tatt kontakt med en annen kropp som du er interessert i, setter du contactBitMask. Her ønsker vi å vite når InvaderBullet har tatt kontakt med spilleren, så vi bruker CollisionCategories.Player. Fordi en kollisjon ikke bør utløse noen fysikk krefter, setter vi collisionBitMask til 0x0
Trinn 2:. Sette opp Player for Collsion
Legg til følgende til init-metoden i Player. . swift
styre init () {la tekstur = SKTexture (imageNamed: "PLAYER1") super.init (tekstur: tekstur, farge: SKColor.clearColor (), størrelse: texture.size ()) self.physicsBody = SKPhysicsBody ? (tekstur: self.texture, størrelse: self.size)? self.physicsBody .dynamic = true self.physicsBody .usesPreciseCollisionDetection = false self.physicsBody .categoryBitMask = CollisionCategories.Player self.physicsBody .contactTestBitMask = CollisionCategories.InvaderBullet | CollisionCategories.Invader self.physicsBody? .collisionBitMask = 0x0 animere ()}
Mye av dette bør være kjent fra forrige trinn så jeg vil ikke rehash det her. Det er to forskjeller å legge merke til det. Det ene er at usesPreciseCollsionDetection er satt til false, som er standard. Det er viktig å innse at bare en av de kontaktorganer må denne egenskapen satt til true (som var kulen). Den andre forskjellen er at vi ønsker også å vite når spilleren kontakter en inntrenger. Du kan ha mer enn én contactBitMask kategori ved å skille dem med bitvis eller plakater (|) operator. Annet enn det, bør du merke det er bare utgangspunktet motsatt fra InvaderBullet. Legg til følgende til init-metoden i Invader.swift Dette bør alle være fornuftig hvis du har fulgt med. Vi setter opp physicsBody, categoryBitMask, og contactBitMask Legg til følgende i init (imageName: bulletSound :) i PlayerBullet.swift. Igjen, bør gjennomføringen være kjent nå Vi må sette opp GameScene klassen for å implementere SKPhysicsContactDelegate slik at vi kan svare når to legemer kolliderer. Legg til følgende for å gjøre GameScene klasse i samsvar med den SKPhysicsContactDelegate protokollen Deretter må vi sette opp noen egenskaper på scenens physicsWorld. Skriv inn følgende på toppen av didMoveToView (_ :) metode i GameScene.swift Vi setter alvoret eiendom physicsWorld til 0 slik at ingen av de fysikk organer i scenen er påvirket av tyngdekraft. Du kan også gjøre dette på en per kropp basis i stedet for å sette hele verden til å ha noen tyngdekraften ved å sette affectedByGravity eiendommen. Vi har også satt den contactDelegate eiendommen av fysikk verden til seg selv, den GameScene eksempel For å samsvare med den GameScene klassen til SKPhysicsContactDelegate protokollen, må vi gjennomføre didBeginContact (_:) metode. Denne metoden kalles når to organer gjør kontakt. Gjennomføringen av didBeginContact (_ :) metoden ser slik ut Vi erklærer første to variabler firstBody og secondBody. Når to objekter ta kontakt, vi vet ikke hvilket organ som er der. Dette betyr at vi først må gjøre noen kontroller for å sikre firstBody er den med den nedre categoryBitMask Deretter går vi gjennom hver mulig scenario ved hjelp bitvis &.; operatør og kollisjons kategoriene vi definerte tidligere for å sjekke hva som er å ta kontakt. Vi logger resultatet til konsollen for å sørge for at alt fungerer som det skal. Hvis du tester programmet, bør alle kontakter være riktig. Dette var en ganske lang tutorial, men vi har nå inntrengerne flytting, kuler ble avfyrt fra både spilleren og inntrengere og kontakt deteksjon arbeider ved hjelp av kontakt bit masker. Vi er på oppløpssiden til det endelige spillet. I neste og siste del av denne serien, vil vi ha en fullført spillet.
6. Invader og PlayerBullet Kollisjon
Trinn 1:. Sette opp Invader for Kollisjon
overstyring init () {la tekstur = SKTexture (imageNamed: "invader1") super.init (tekstur: tekstur, farge: SKColor.clearColor (), størrelse: texture.size ()) self.name = "invader" self.physicsBody = SKPhysicsBody (tekstur: self.texture , størrelse:???? self.size) self.physicsBody .dynamic = true self.physicsBody .usesPreciseCollisionDetection = false self.physicsBody .categoryBitMask = CollisionCategories.Invader self.physicsBody .contactTestBitMask = CollisionCategories.PlayerBullet | CollisionCategories.Player self.physicsBody? .collisionBitMask = 0x0}
Trinn 2:. Sette opp PlayerBullet for Kollisjon
styre init. (ImageName: String, bulletSound: String) {super.init (imageName: imageName, bulletSound: bulletSound) self.physicsBody = SKPhysicsBody (tekstur: self.texture, Størrelse:????? self.size) self.physicsBody .dynamic = true self.physicsBody .usesPreciseCollisionDetection = true self.physicsBody .categoryBitMask = CollisionCategories.PlayerBullet self.physicsBody .contactTestBitMask = CollisionCategories.Invader self.physicsBody .collisionBitMask = 0x0 }
7. Sette opp fysikk for GameScene
Trinn 1: Konfigurere fysikk verden
klasse GameScene. SKScene, SKPhysicsContactDelegate {
overstyring func didMoveToView (vis. SKView) {self.physicsWorld.gravity = CGVectorMake (0, 0) self.physicsWorld. contactDelegate = selv ...}
Trinn 2:.
Implementing SKPhysicsContactDelegate Protocol
func didBeginContact (kontakt: SKPhysicsContact) {var firstBody: SKPhysicsBody Var secondBody. SKPhysicsBody hvis 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 og spiller Bullet Conatact")} if ((firstBody.categoryBitMask & CollisionCategories.Player = 0) &! & (secondBody.categoryBitMask & ! CollisionCategories.InvaderBullet = 0)) {NSLog ("Player og Invader Bullet Kontakt»)} if ((firstBody.categoryBitMask & CollisionCategories.Invader = 0) &! & (secondBody.categoryBitMask &! CollisionCategories.Player = 0)) {NSLog ("Invader og spiller Kollisjon Kontakt»)}}
Konklusjon