SpriteKit de la zero tehnici avansate și optimizări

Introducere

În acest tutorial, cea de-a cincea și ultima tranșă a seriei SpriteKit From Scratch, ne uităm la câteva tehnici avansate pe care le puteți utiliza pentru a optimiza jocurile bazate pe SpriteKit pentru a îmbunătăți performanța și experiența utilizatorului.

Acest tutorial cere să executați Xcode 7.3 sau o versiune ulterioară, care include Swift 2.2 și SDK-urile iOS 9.3, tvOS 9.2 și OS X 10.11.4. Pentru a continua, puteți utiliza proiectul pe care l-ați creat în tutorialul anterior sau puteți descărca o copie nouă de la GitHub.

Grafica folosită pentru joc în această serie poate fi găsită pe GraphicRiver. GraphicRiver este o sursă excelentă pentru găsirea de ilustrații și grafică pentru jocurile dvs..

1. Atlasuri de textură

Pentru a optimiza utilizarea memoriei jocului, SpriteKit oferă funcționalitatea atlaselor de textură sub forma SKTextureAtlas clasă. Aceste atlase combină efectiv texturile pe care le specificați într-o textura unică și mare, care preia mai puțină memorie decât texturile individuale pe cont propriu. 

Din fericire, Xcode poate crea atlasuri de textura foarte usor pentru tine. Acest lucru se face în aceleași cataloage de active care sunt folosite pentru alte imagini și resurse din jocurile dvs. Deschideți proiectul și navigați la Assets.xcassets catalogul de active. În partea de jos a barei laterale din stânga, faceți clic pe + și selectați Noul Sprite Atlas opțiune.

Prin urmare, un catalog nou este adăugat unui nou dosar. Faceți clic o dată pe dosar pentru ao selecta și faceți din nou clic pentru al redenumi. Numeste obstacole. Apoi trageți Obstacol 1 și Obstacol 2 resurse în acest dosar. De asemenea, puteți șterge martorul spiriduș activ care Xcode generează dacă doriți, dar acest lucru nu este necesar. Când ați terminat, ați extins obstacole textura atlas ar trebui să arate astfel:

Acum este timpul să folosim atlasul texturii în cod. Deschis MainScene.swift și adăugați următoarea proprietate la MainScene clasă. Initializăm un atlas de textura folosind numele pe care l-am introdus în catalogul nostru de active.

lasa obstaclesAtlas = SKTextureAtlas (numit: "Obstacole")

Dacă nu este necesar, puteți încărca în memorie datele unui atlas de textură înainte de a fi utilizat. Acest lucru permite jocului dvs. să elimine orice întârziere care ar putea apărea atunci când încărcați atlasul texturii și preluați prima textură din ea. Preîncărcarea unui atlas de textură se face printr-o singură metodă și puteți rula și un bloc de cod personalizat odată ce încărcarea sa încheiat.

În MainScene , adăugați următorul cod la sfârșitul secțiunii didMoveToView (_ :) metodă:

override func didMoveToView (vizualizare: SKView) ... obstaclesAtlas.preloadWithCompletionHandler // Face ceva ce atlasul de textură a încărcat

Pentru a prelua o textura dintr-un atlas de textură, utilizați textureNamed (_ :) metoda cu numele pe care l-ați specificat în catalogul de active ca parametru. Să actualizăm spawnObstacle (_ :) metodă în MainScene pentru a folosi atlasul de textură pe care l-am creat acum un moment. Atragem textura din atlasul texturii și o folosim pentru a crea un nod sprite.

func spawnObstacle (timer: NSTimer) if player.hidden timer.invalidate () return permite spriteGenerator = GKShuffledDistribution (lowestValue: 1, highestValue: = SKSpriteNode (textură: textura) obstacle.xScale = 0.3 obstacle.yScale = 0.3 let fizicsBody = SKPhysicsBody (circleOfRadius: 15) fizicsBody.contactTestBitMask = 0x00000001 fizicsBody.pinned = adevărat fizicăBody.allowsRotation = false obstacle.physicsBody = physicsBody let center = .width / 2.0, difference = CGFloat (85.0) var x: CGFloat = 0 laneGenerator = GKShuffledDistribution (lowestValue: 1, highestValue: (x, x, y): (player.position.y + 800)) addChild ("x" = " obstacol) obstacle.lightingBitMask = 0xFFFFFFFF obstacle.shadowCastBitMask = 0xFFFF FFFF

Rețineți că, dacă jocul dvs. profită de Resursele On-Demand (ODR), puteți specifica cu ușurință una sau mai multe etichete pentru fiecare atlas de textură. Odată ce ați accesat cu succes etichetele de resurse corecte cu API-urile ODR, puteți folosi atlasul dvs. de textură exact așa cum am făcut-o în spawnObstacle (_ :) metodă. Puteți citi mai multe despre Resursele On-Demand într-un alt tutorial al meu.

2. Salvarea și încărcarea scenelor

SpriteKit vă oferă, de asemenea, posibilitatea de a salva și de a încărca cu ușurință scene spre și de la stocarea permanentă. Acest lucru permite jucătorilor să renunțe la jocul dvs., să îl relanseze mai târziu și să fie până la același punct din joc ca și înainte.

Salvarea și încărcarea jocului dvs. sunt tratate de către NSCoding protocol, care SKScene clasa se conformează deja. Implementarea de către SpriteKit a metodelor cerute de acest protocol permite în mod automat salvarea și încărcarea tuturor detaliilor din scenă. Dacă doriți, puteți suprascrie aceste metode pentru a salva unele date personalizate împreună cu scenele dvs..

Deoarece jocul nostru este foarte simplu, vom folosi o simplă bool pentru a indica dacă mașina sa prăbușit. Aceasta vă arată modul de salvare și încărcare a datelor personalizate care sunt legate de o scenă. Adăugați următoarele două metode ale NSCoding protocol la MainScene clasă.

// MARK: - Protocolul NSCoding necesar pentru init? (Coder aDecoder: NSCoder) super.init (coder: aDecoder) lasa carHasCrashed = aDecoder.decodeBoolForKey ("carCrashed") print ("car hurt: \ (carHasCrashed) func encodeWithCoder (aCoder: NSCoder) super.encodeWithCoder (aCoder) permite carHasCrashed = player.hidden aCoder.encodeBool (carHasCrashed, pentruKey: "carCrashed")

Dacă nu sunteți familiarizați cu NSCoding protocol, encodeWithCoder (_ :) Metoda se ocupă de salvarea scenei dvs. și a inițializatorului cu un singur NSCoder parametrul se ocupă de încărcare.

Apoi, adăugați următoarea metodă la MainScene clasă. saveScene () metoda creează un NSData reprezentarea scenei, folosind NSKeyedArchiver clasă. Pentru a păstra lucrurile simple, stocăm datele în NSUserDefaults.

func saveScene () let sceneData = NSKeyedArchiver.archivedDataWithRootObject (auto) NSUserDefaults.standardUserDefaults () setObject (scenaData, pentruKey: "currentScene")

Apoi, înlocuiți punerea în aplicare a didBeginContactMethod (_ :) în MainScene clasă cu următoarele:

func didBeginContact (contact: SKPhysicsContact) if contact.bodyA.node == player || contact.bodyB.node == player if a permite exploziePath = NSBundle.mainBundle () pathForResource ("Explosion", ofType: "sks"), permite smokePath = NSBundle.mainBundle () pathForResource ("Smoke" sks "), să explice explozia = NSKeyedUnarchiver.unarchiveObjectWithFile (explosionPath) ca? SKEmitterNode, permiteți smoke = NSKeyedUnarchiver.unarchiveObjectWithFile (smokePath) ca? SKEmitterNode player.removeAllActions () player.hidden = adevărat player.physicsBody? .CategoryBitMask = 0 cameră? .RemoveAllActions () explosion.position = player.position smoke.position = poziția jucătorului addChild (smoke) addChild (explosion) saveScene )

Prima modificare făcută acestei metode este editarea nodurilor jucătorului categoryBitMask mai degrabă decât să-l eliminați complet din scenă. Acest lucru asigură că, la reîncărcarea scenei, nodul jucătorului este încă acolo, chiar dacă nu este vizibil, dar nu sunt detectate coliziuni duplicate. Cealaltă schimbare făcută îl sună pe saveScene () metoda pe care am definit-o mai devreme odată ce a fost executată logica exploziei personalizate.

În cele din urmă, deschisă ViewController.swift și înlocuiți viewDidLoad () cu următoarea implementare:

override functie viewDidLoad () super.viewDidLoad () permite skView = Vizualizare SKV (frame: view.frame) var scena: MainScene? dacă permiteți saveSceneData = NSUserDefaults.standardUserDefaults (). objectForKey ("currentScene") ca? NSData, să salvați SaveScene = NSKeyedUnarchiver.unarchiveObjectWithData (savedSceneData) ca? MainScene scena = salvatScene altfel dacă permite url = NSBundle.mainBundle () URLForResource ("MainScene", cu extensia "sks"), lasă newSceneData = NSData (contentsOfURL: url), lets newScene = NSKeyedUnarchiver.unarchiveObjectWithData (newSceneData) ? MainScene scena = newScene skView.presentScene (scena) view.insertSubview (skView, atIndex: 0) lasa left = LeftLane (player: scene! .Player) let middle = MiddleLane (jucător: scena! .player) stateMachine = LaneStateMachine (state: [stânga, mijlocul, dreapta]) stateMachine? .enterState (MiddleLane)

La încărcarea scenei, mai întâi verificăm dacă există date salvate în standard NSUserDefaults. Dacă da, vom prelua aceste date și vom recrea MainScene obiect folosind NSKeyedUnarchiver clasă. Dacă nu, vom obține adresa URL a fișierului de scenă pe care l-am creat în Xcode și vom încărca datele din acesta într-un mod similar.

Rulați aplicația și rulați-vă cu un obstacol cu ​​mașina. În această etapă, nu vedeți nicio diferență. Rulați aplicația din nou, totuși, și ar trebui să vedeți că scena dvs. a fost restaurată la exact cum a fost atunci când tocmai ați prăbușit mașina.

3. Bucla de animație

Înainte de fiecare lansare a jocului, SpriteKit execută o serie de procese într-o anumită ordine. Acest grup de procese este denumit animație. Aceste procese reprezintă acțiunile, proprietățile fizice și constrângerile pe care le-ați adăugat la scenă.

Dacă, din orice motiv, trebuie să rulați un cod personalizat între oricare dintre aceste procese, puteți fie să înlocuiți anumite metode specifice în dvs. SKScene subclasa sau specificați un delegat care este în conformitate cu SKSceneDelegate protocol. Rețineți că, dacă atribuiți unui delegat scenei dvs., nu se invocă implementările clasei următoarelor metode.

Procesele de buclă de animație sunt următoarele:

Pasul 1

Scena îl numește Actualizați(_:) metodă. Această metodă are un singur NSTimeInterval parametru, care vă oferă ora curentă a sistemului. Acest interval de timp poate fi util, deoarece vă permite să calculați timpul necesar pentru afișarea cadrului anterior.

Dacă valoarea este mai mare de 1/60 din secundă, jocul dvs. nu rulează la nivelul de 60 de cadre pe secundă (FPS) pe care SpriteKit dorește. Aceasta înseamnă că este posibil să fie necesar să modificați unele aspecte ale scenei dvs. (de exemplu, particule, numărul de noduri) pentru a reduce complexitatea acesteia.

Pasul 2

Scena execută și calculează acțiunile pe care le-ați adăugat la nodurile dvs. și le poziționează corespunzător.

Pasul 3

Scena îl numește didEvaluateActions () metodă. Aici puteți efectua orice logică personalizată înainte ca SpriteKit să continue cu buclă de animație.

Pasul 4

Scena își realizează simulările fizice și schimba scena în consecință.

Pasul 5

Scena îl numește didSimulatePhysics () metodă, pe care o puteți suprascrie cu didEvaluateActions () metodă.

Pasul 6

Scena aplică restricțiile pe care le-ați adăugat la nodurile dvs..

Pasul 7

Scena îl numește didApplyConstraints () care este disponibil pentru tine de a suprascrie.

Pasul 8

Scena îl numește didFinishUpdate () metoda pe care o puteți suprascrie. Aceasta este metoda finală pe care o puteți schimba înainte de apariția sa pentru ca acest cadru să fie finalizat.

Pasul 9

În cele din urmă, scena redă conținutul și actualizează conținutul SKView în consecinţă.

Este important să rețineți că, dacă utilizați a SKSceneDelegate obiect mai degrabă decât o subclasă particularizată, fiecare metodă câștigă un parametru suplimentar și își schimbă ușor numele. Parametrul suplimentar este un SKScene obiect, care vă permite să determinați în ce scenă se execută metoda în raport cu. Metodele definite de SKSceneDelegate protocol sunt denumite după cum urmează:

  • actualizare (_: forScene :)
  • didEvaluateActionsForScene (_ :)
  • didSimulatePhysicsForScene (_ :)
  • didApplyConstraintsForScene (_ :)
  • didFinishUpdateForScene (_ :)

Chiar dacă nu utilizați aceste metode pentru a face schimbări în scenă, acestea pot fi totuși foarte utile pentru depanare. Dacă jocul dvs. rămâne în mod constant și rata cadrelor scade la un anumit moment al jocului dvs., puteți să înlocuiți orice combinație dintre metodele de mai sus și să găsiți intervalul de timp dintre fiecare apelat. Acest lucru vă permite să identificați cu exactitate dacă acțiunile, fizica, constrângerile sau graficele dvs. sunt prea complexe pentru ca jocul dvs. să ruleze la 60 FPS.

4. Cele mai bune practici de performanță

Desen lot

Atunci când redați scena, SpriteKit, în mod implicit, rulează prin nodurile din scenele dvs. copii și le atrage pe ecran în aceeași ordine ca și în matrice. Acest proces este, de asemenea, repetat și cu buclă pentru orice noduri copil pe care un anumit nod ar putea avea.

Enumerarea prin nodurile copilului individual înseamnă că SpriteKit execută un apel pentru fiecare nod. În timp ce pentru scene simple această metodă de redare nu are un impact semnificativ asupra performanței, deoarece scena dvs. câștigă mai multe noduri acest proces devine foarte ineficient.

Pentru a face redarea mai eficientă, puteți organiza nodurile din scenă în straturi distincte. Acest lucru se face prin zPosition proprietate a SKNode clasă. Cel mai înalt nod zPosition este "mai aproape" de ecran, ceea ce înseamnă că este redat pe celelalte noduri din scenă. De asemenea, nodul cu cel mai mic zPosition într-o scenă apare la foarte "spate" și poate fi suprapusă de orice alt nod.

După ce organizați nodurile în straturi, puteți seta un SKView obiecte ignoreSiblingOrder proprietate la Adevărat. Acest lucru are ca rezultat SpriteKit folosind zPosition valorile pentru a face o scenă mai degrabă decât ordinea copii matrice. Acest proces este mult mai eficient ca orice noduri cu aceleași zPosition sunt împărțite împreună într-un apel de remiză, în loc să aibă unul pentru fiecare nod.

Este important să rețineți că zPosition valoarea unui nod poate fi negativă dacă este necesar. Nodurile din scenă sunt în continuare redate în ordine crescătoare zPosition.

Evitați animațiile personalizate

Amandoua SKAction și SKConstraint clasele conțin un număr mare de reguli pe care le puteți adăuga unei scene pentru a crea animații. Fiind parte a cadrului SpriteKit, acestea sunt optimizate cât de mult pot fi și se potrivesc de asemenea perfect cu buclă de animație SpriteKit.

Gama largă de acțiuni și constrângeri care vă sunt oferite permit aproape orice animație posibilă pe care ați putea dori să o faceți. Din aceste motive, este recomandat să utilizați întotdeauna acțiuni și constrângeri în scenele dvs. pentru a crea animații în loc să efectuați o logică personalizată în altă parte a codului.

În unele cazuri, mai ales dacă aveți nevoie să animați un grup rezonabil de mari de noduri, câmpurile fizice de forță pot fi chiar capabile să producă rezultatul dorit. Campurile de forță sunt chiar mai eficiente, deoarece sunt calculate alături de restul simulărilor de fizică ale SpriteKit.

Masti bit

Scenele dvs. pot fi optimizate și mai mult, utilizând doar măștile de biți potrivite pentru nodurile din scenă. Pe lângă faptul că sunt esențiale pentru detecția coliziunilor fizice, măștile de biți determină și modul în care simulările fizice regulate și iluminarea afectează nodurile într-o scenă.

Pentru orice pereche de noduri dintr-o scenă, indiferent dacă acestea se vor ciocni sau nu, SpriteKit monitorizează unde sunt relativ unele cu altele. Aceasta înseamnă că, dacă este lasată cu măștile implicite cu toți biții activi, SpriteKit urmărește unde fiecare nod este în scenă în comparație cu fiecare alt nod. Puteți simplifica foarte mult simulările fizicii SpriteKit prin definirea unor măști de biți adecvate, astfel încât să fie urmărite numai relațiile dintre nodurile care pot copleși potențial.

De asemenea, o lumină în SpriteKit afectează numai un nod dacă este logic ȘI din măștile de biți de categorie este o valoare diferită de zero. Prin editarea acestor categorii, astfel încât numai nodurile cele mai importante din scenă dvs. sunt afectate de o anumită lumină, puteți reduce foarte mult complexitatea unei scene.

Concluzie

Acum ar trebui să știți cum puteți optimiza în continuare jocurile SpriteKit utilizând mai multe tehnici avansate, cum ar fi atlasele de textură, desenul lotului și măștile de biți optimizate. De asemenea, ar trebui să vă simțiți confortabil cu salvarea și încărcarea scenelor pentru a oferi jucătorilor o experiență de ansamblu mai bună.

De-a lungul acestei serii am analizat o mulțime de caracteristici și funcționalități ale cadrului SpriteKit în iOS, tvOS și OS X. Există chiar și subiecte mai avansate dincolo de sfera de aplicare a acestei serii, cum ar fi atât personalizate OpenGL ES, cât și Shaders Metal ca câmpuri fizice și articulații.

Dacă doriți să aflați mai multe despre aceste subiecte, vă recomandăm să începeți cu referința SpriteKit Framework și citiți-le pe clasele relevante.

Ca întotdeauna, asigurați-vă că părăsiți comentariile și comentariile dvs. în comentariile de mai jos.

Cod