Cum să construiești un joc SpriteKit în Swift 3 (Partea 3)

Publicat: 2022-03-10
Rezumat rapid ↬ V-ați întrebat vreodată ce este nevoie pentru a crea un joc SpriteKit? Butoanele par o sarcină mai mare decât ar trebui să fie? V-ați întrebat vreodată cum să persistați setările într-un joc? Crearea jocurilor nu a fost niciodată mai ușoară pe iOS de la introducerea SpriteKit. În partea a treia a acestei serii din trei părți, vom finaliza jocul nostru RainCat și vom finaliza introducerea în SpriteKit. Dacă ați ratat lecția anterioară, puteți ajunge din urmă obținând codul pe GitHub. Rețineți că acest tutorial necesită Xcode 8 și Swift 3.

Te-ai întrebat vreodată ce este nevoie pentru a crea un joc SpriteKit? Butoanele par o sarcină mai mare decât ar trebui să fie? V-ați întrebat vreodată cum să persistați setările într-un joc? Crearea jocurilor nu a fost niciodată mai ușoară pe iOS de la introducerea SpriteKit. În partea a treia a acestei serii din trei părți, vom finaliza jocul nostru RainCat și vom finaliza introducerea în SpriteKit.

Dacă ați ratat lecția anterioară, puteți ajunge din urmă obținând codul pe GitHub. Rețineți că acest tutorial necesită Xcode 8 și Swift 3.

Citiți suplimentare despre SmashingMag: Link

  • Gamification și UX: Unde utilizatorii câștigă sau pierd
  • Design UX jucăuș: construirea unui joc mai bun
  • Combinând designul UX și psihologia pentru a schimba comportamentul utilizatorului
Raincat, lecția 3
RainCat, lecția 3
Mai multe după săritură! Continuați să citiți mai jos ↓

Aceasta este lecția trei din călătoria noastră RainCat. În lecția anterioară, am avut o zi lungă cu câteva animații simple, comportamente de pisică, efecte sonore rapide și muzică de fundal.

Astăzi ne vom concentra pe următoarele:

  • display heads-up (HUD) pentru punctare;
  • meniul principal — cu butoane;
  • opțiuni pentru dezactivarea sunetelor;
  • opțiunea de renunțare la joc.

Chiar mai multe active

Elementele pentru lecția finală sunt disponibile pe GitHub. Trageți din nou imaginile în Assets.xcassets , așa cum am făcut în lecțiile anterioare.

Atenție!

Avem nevoie de o modalitate de a păstra scorul. Pentru a face acest lucru, putem crea un afișaj heads-up (HUD). Acest lucru va fi destul de simplu; va fi un SKNode care conține scorul și un buton pentru a părăsi jocul. Deocamdată, ne vom concentra doar pe scor. Fontul pe care îl vom folosi este Pixel Digivolve, pe care îl puteți obține pe Dafont.com. Ca și în cazul utilizării de imagini sau sunete care nu sunt ale dvs., citiți licența fontului înainte de a-l folosi. Acesta afirmă că este gratuit pentru uz personal, dar dacă vă place foarte mult fontul, puteți dona autorului din pagină. Nu poți întotdeauna să faci totul singur, așa că este plăcut să dai înapoi celor care te-au ajutat pe parcurs.

Apoi, trebuie să adăugăm fontul personalizat la proiect. Acest proces poate fi dificil de prima dată.

Descărcați și mutați fontul în folderul proiectului, sub un folder „Fonts”. Am făcut acest lucru de câteva ori în lecțiile anterioare, așa că vom trece prin acest proces puțin mai repede. Adăugați un grup numit Fonts la proiect și adăugați fișierul Pixel digivolve.otf .

Acum vine partea dificilă. Dacă ratați această parte, probabil că nu veți putea folosi fontul. Trebuie să-l adăugăm la fișierul nostru Info.plist . Acest fișier se află în panoul din stânga al Xcode. Faceți clic pe el și veți vedea lista de proprietăți (sau plist ). Faceți clic dreapta pe listă și faceți clic pe „Adăugați rând”.

Adăugați un rând
Adăugați un rând la plist .

Când apare noul rând, introduceți următoarele:

 Fonts provided by application

Apoi, sub Item 0 , trebuie să adăugăm numele fontului nostru. plist ar trebui să arate astfel:

Pixel digivolve.otf
Font adăugat la plist cu succes!

Fontul ar trebui să fie gata de utilizare! Ar trebui să facem un test rapid pentru a ne asigura că funcționează conform intenției. Mutați la GameScene.swift și în sceneDidLoad adăugați următorul cod în partea de sus a funcției:

 let label = SKLabelNode(fontNamed: "PixelDigivolve") label.text = "Hello World!" label.position = CGPoint(x: size.width / 2, y: size.height / 2) label.zPosition = 1000 addChild(label)

Funcționează?

Salut Lume!
Testarea SKLabelNode -ului nostru. Oh nu! Eticheta „Hello World” a revenit.

Dacă funcționează, atunci ai făcut totul corect. Dacă nu, atunci ceva nu este în regulă. Code With Chris are un ghid de depanare mai aprofundat, dar rețineți că este pentru o versiune mai veche a Swift, așa că va trebui să faceți mici ajustări pentru a o aduce la Swift 3.

Acum că putem încărca fonturi personalizate, putem începe cu HUD-ul nostru. Ștergeți eticheta „Hello World”, deoarece am folosit-o doar pentru a ne asigura că fonturile noastre se încarcă. HUD-ul va fi un SKNode , acționând ca un container pentru elementele noastre HUD. Acesta este același proces pe care l-am urmat când am creat nodul de fundal în lecția unu.

Creați fișierul HudNode.swift folosind metodele obișnuite și introduceți următorul cod:

 import SpriteKit class HudNode : SKNode { private let scoreKey = "RAINCAT_HIGHSCORE" private let scoreNode = SKLabelNode(fontNamed: "PixelDigivolve") private(set) var score : Int = 0 private var highScore : Int = 0 private var showingHighScore = false /// Set up HUD here. public func setup(size: CGSize) { let defaults = UserDefaults.standard highScore = defaults.integer(forKey: scoreKey) scoreNode.text = "\(score)" scoreNode.fontSize = 70 scoreNode.position = CGPoint(x: size.width / 2, y: size.height - 100) scoreNode.zPosition = 1 addChild(scoreNode) } /// Add point. /// - Increments the score. /// - Saves to user defaults. /// - If a high score is achieved, then enlarge the scoreNode and update the color. public func addPoint() { score += 1 updateScoreboard() if score > highScore { let defaults = UserDefaults.standard defaults.set(score, forKey: scoreKey) if !showingHighScore { showingHighScore = true scoreNode.run(SKAction.scale(to: 1.5, duration: 0.25)) scoreNode.fontColor = SKColor(red:0.99, green:0.92, blue:0.55, alpha:1.0) } } } /// Reset points. /// - Sets score to zero. /// - Updates score label. /// - Resets color and size to default values. public func resetPoints() { score = 0 updateScoreboard() if showingHighScore { showingHighScore = false scoreNode.run(SKAction.scale(to: 1.0, duration: 0.25)) scoreNode.fontColor = SKColor.white } } /// Updates the score label to show the current score. private func updateScoreboard() { scoreNode.text = "\(score)" } }

Înainte de a face altceva, deschideți Constants.swift și adăugați următoarea linie în partea de jos a fișierului - o vom folosi pentru a prelua și a persista scorul cel mai mare:

 let ScoreKey = "RAINCAT_HIGHSCORE"

În cod, avem cinci variabile care se referă la tabloul de bord. Prima variabilă este SKLabelNode , pe care o folosim pentru a prezenta eticheta. Urmează variabila noastră pentru a păstra scorul curent; apoi variabila care deține cel mai bun punctaj. Ultima variabilă este un boolean care ne spune dacă prezentăm în prezent scorul mare (folosim acest lucru pentru a stabili dacă trebuie să rulăm o SKAction pentru a crește scara tabloului de bord și pentru a o colora în galbenul podelei).

Prima funcție, setup(size:) , este acolo doar pentru a configura totul. Am configurat SKLabelNode în același mod în care am făcut-o mai devreme. Clasa SKNode nu are în mod implicit nicio proprietate de dimensiune, așa că trebuie să creăm o modalitate de a seta o dimensiune pentru a poziționa eticheta noastră scoreNode . De asemenea, preluăm scorul maxim actual de la UserDefaults . Aceasta este o modalitate rapidă și ușoară de a salva bucăți mici de date, dar nu este sigură. Deoarece nu ne îngrijorează securitatea pentru acest exemplu, UserDefaults este perfect.

În addPoint() , incrementăm variabila score curent și verificăm dacă utilizatorul a obținut un scor mare. Dacă au un scor mare, atunci salvăm acel scor în UserDefaults și verificăm dacă în prezent afișăm cel mai bun scor. Dacă utilizatorul a obținut un scor mare, putem anima dimensiunea și culoarea scoreNode .

În funcția resetPoints() , setăm scorul curent la 0 . Apoi trebuie să verificăm dacă arătam scorul mare și să resetam dimensiunea și culoarea la valorile implicite, dacă este necesar.

În cele din urmă, avem o mică funcție numită updateScoreboard . Aceasta este o funcție internă pentru a seta scorul pentru textul lui scoreNode . Acesta este apelat atât în addPoint() cât și resetPoints() .

Conectarea HUD-ului

Trebuie să testăm dacă HUD-ul nostru funcționează corect. Treceți la GameScene.swift și adăugați următoarea linie sub variabila foodNode din partea de sus a fișierului:

 private let hudNode = HudNode()

Adăugați următoarele două linii în funcția sceneDidLoad() , aproape de partea de sus:

 hudNode.setup(size: size) addChild(hudNode)

Apoi, în funcția spawnCat() , resetați punctele în cazul în care pisica a căzut de pe ecran. Adăugați următoarea linie după adăugarea sprite-ului pisicii la scenă:

 hudNode.resetPoints()

Apoi, în funcția handleCatCollision(contact:) , trebuie să resetam scorul din nou când pisica este lovită de ploaie. În instrucțiunea switch de la sfârșitul funcției — când celălalt corp este o RainDropCategory — adăugați următoarea linie:

 hudNode.resetPoints()

În cele din urmă, trebuie să spunem tabloului de bord când utilizatorul a câștigat puncte. La sfârșitul fișierului în handleFoodHit(contact:) , găsiți următoarele rânduri până aici:

 //TODO increment points print("fed cat")

Și înlocuiți-le cu asta:

 hudNode.addPoint()

Voila!

HUD deblocat!
HUD deblocat!

Ar trebui să vedeți HUD-ul în acțiune. Alergați și adunați ceva de mâncare. Prima dată când colectați alimente, ar trebui să vedeți scorul devine galben și crește în scară. Când vezi că se întâmplă asta, lasă pisica să fie lovită. Dacă scorul se resetează, atunci vei ști că ești pe drumul cel bun!

Cel mai mare scor!
Cel mai mare scor vreodată (… la momentul scrierii)!

Următoarea Scenă

Așa e, trecem la altă scenă! De fapt, când este finalizat, acesta va fi primul ecran al aplicației noastre. Înainte de a face orice altceva, deschideți Constants.swift și adăugați următoarea linie în partea de jos a fișierului - o vom folosi pentru a prelua și a persista scorul cel mai mare:

 let ScoreKey = "RAINCAT_HIGHSCORE"

Creați noua scenă, plasați-o în folderul „Scene” și numiți-o MenuScene.swift . Introduceți următorul cod în fișierul MenuScene.swift :

 import SpriteKit class MenuScene : SKScene { let startButtonTexture = SKTexture(imageNamed: "button_start") let startButtonPressedTexture = SKTexture(imageNamed: "button_start_pressed") let soundButtonTexture = SKTexture(imageNamed: "speaker_on") let soundButtonTextureOff = SKTexture(imageNamed: "speaker_off") let logoSprite = SKSpriteNode(imageNamed: "logo") var startButton : SKSpriteNode! = nil var soundButton : SKSpriteNode! = nil let highScoreNode = SKLabelNode(fontNamed: "PixelDigivolve") var selectedButton : SKSpriteNode? override func sceneDidLoad() { backgroundColor = SKColor(red:0.30, green:0.81, blue:0.89, alpha:1.0) //Set up logo - sprite initialized earlier logoSprite.position = CGPoint(x: size.width / 2, y: size.height / 2 + 100) addChild(logoSprite) //Set up start button startButton = SKSpriteNode(texture: startButtonTexture) startButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - startButton.size.height / 2) addChild(startButton) let edgeMargin : CGFloat = 25 //Set up sound button soundButton = SKSpriteNode(texture: soundButtonTexture) soundButton.position = CGPoint(x: size.width - soundButton.size.width / 2 - edgeMargin, y: soundButton.size.height / 2 + edgeMargin) addChild(soundButton) //Set up high-score node let defaults = UserDefaults.standard let highScore = defaults.integer(forKey: ScoreKey) highScoreNode.text = "\(highScore)" highScoreNode.fontSize = 90 highScoreNode.verticalAlignmentMode = .top highScoreNode.position = CGPoint(x: size.width / 2, y: startButton.position.y - startButton.size.height / 2 - 50) highScoreNode.zPosition = 1 addChild(highScoreNode) } }

Deoarece această scenă este relativ simplă, nu vom crea cursuri speciale. Scena noastră va consta din două butoane. Acestea ar putea fi (și posibil merită să fie) propria lor clasă de SKSpriteNodes , dar pentru că sunt suficient de diferite, nu va fi nevoie să creăm noi clase pentru ei. Acesta este un sfat important pentru când vă construiți propriul joc: trebuie să puteți determina unde să vă opriți și să refactorați codul atunci când lucrurile devin complexe. Odată ce ați adăugat mai mult de trei sau patru butoane la un joc, ar putea fi timpul să vă opriți și să refactorați codul butonului de meniu în propria sa clasă.

Codul de mai sus nu face nimic special; stabilește pozițiile a patru sprite-uri. De asemenea, setăm culoarea de fundal a scenei, astfel încât întregul fundal să fie valoarea corectă. Un instrument frumos pentru a genera coduri de culoare din șiruri HEX pentru Xcode este UI Color. Codul de mai sus setează și texturile pentru stările butoanelor noastre. Butonul de pornire a jocului are o stare normală și o stare apăsată, în timp ce butonul de sunet este o comutare. Pentru a simplifica lucrurile pentru comutare, vom modifica valoarea alfa a butonului de sunet la apăsarea utilizatorului. De asemenea, tragem și SKLabelNode cu scorul înalt.

MenuScene arată destul de bine. Acum trebuie să arătăm scena când se încarcă aplicația. Treceți la GameViewController.swift și găsiți următoarea linie:

 let sceneNode = GameScene(size: view.frame.size)

Înlocuiește-l cu acesta:

 let sceneNode = MenuScene(size: view.frame.size)

Această mică modificare va încărca MenuScene în mod implicit, în loc de GameScene .

Noua noastră scenă!
Noua noastră scenă! Rețineți că 1,0 cadre pe secundă: nimic nu se mișcă, deci nu este nevoie să actualizați nimic.

Buton State

Butoanele pot fi dificile în SpriteKit. Sunt disponibile o mulțime de opțiuni terță parte (chiar eu am făcut una), dar, teoretic, trebuie să cunoașteți doar cele trei metode de atingere:

  • touchesBegan(_ touches: with event:)
  • touchesMoved(_ touches: with event:)
  • touchesEnded(_ touches: with event:)

Am abordat pe scurt acest lucru la actualizarea umbrelei, dar acum trebuie să știm următoarele: ce buton a fost atins, dacă utilizatorul a eliberat atingerea sau a făcut clic pe acel buton și dacă utilizatorul îl atinge în continuare. Aici intră în joc variabila noastră selectedButton . Când începe o atingere, putem captura butonul pe care utilizatorul a început să facă clic cu acea variabilă. Dacă trage în afara butonului, ne putem descurca cu asta și îi dăm textura corespunzătoare. Când eliberează atingerea, putem vedea dacă încă se ating în interiorul butonului. Dacă sunt, atunci îi putem aplica acțiunea asociată. Adăugați următoarele linii în partea de jos a MenuScene.swift :

 override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton != nil { handleStartButtonHover(isHovering: false) handleSoundButtonHover(isHovering: false) } // Check which button was clicked (if any) if startButton.contains(touch.location(in: self)) { selectedButton = startButton handleStartButtonHover(isHovering: true) } else if soundButton.contains(touch.location(in: self)) { selectedButton = soundButton handleSoundButtonHover(isHovering: true) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { // Check which button was clicked (if any) if selectedButton == startButton { handleStartButtonHover(isHovering: (startButton.contains(touch.location(in: self)))) } else if selectedButton == soundButton { handleSoundButtonHover(isHovering: (soundButton.contains(touch.location(in: self)))) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton == startButton { // Start button clicked handleStartButtonHover(isHovering: false) if (startButton.contains(touch.location(in: self))) { handleStartButtonClick() } } else if selectedButton == soundButton { // Sound button clicked handleSoundButtonHover(isHovering: false) if (soundButton.contains(touch.location(in: self))) { handleSoundButtonClick() } } } selectedButton = nil } /// Handles start button hover behavior func handleStartButtonHover(isHovering : Bool) { if isHovering { startButton.texture = startButtonPressedTexture } else { startButton.texture = startButtonTexture } } /// Handles sound button hover behavior func handleSoundButtonHover(isHovering : Bool) { if isHovering { soundButton.alpha = 0.5 } else { soundButton.alpha = 1.0 } } /// Stubbed out start button on click method func handleStartButtonClick() { print("start clicked") } /// Stubbed out sound button on click method func handleSoundButtonClick() { print("sound clicked") }

Aceasta este manipularea simplă a butoanelor pentru cele două butoane ale noastre. În touchesBegan(_ touches: with events:) , începem prin a verifica dacă avem butoane selectate în prezent. Dacă o facem, trebuie să resetam starea butonului la neapăsat. Apoi, trebuie să verificăm dacă este apăsat vreun buton. Dacă este apăsat unul, va afișa starea evidențiată pentru butonul. Apoi, setăm selectedButton la butonul pentru a fi utilizat în celelalte două metode.

În touchesMoved(_ touches: with events:) , verificăm ce buton a fost atins inițial. Apoi, verificăm dacă atingerea curentă este încă în limitele selectedButton și actualizăm starea evidențiată de acolo. Starea evidențiată a butonului de startButton schimbă textura în textura stării apăsate, unde starea evidențiată a soundButton are valoarea alfa a sprite-ului setată la 50%.

În cele din urmă, în touchesEnded(_ touches: with event:) , verificăm din nou ce buton este selectat, dacă există, și apoi dacă atingerea se află încă în limitele butonului. Dacă toate cazurile sunt satisfăcute, apelăm handleStartButtonClick() sau handleSoundButtonClick() pentru butonul corect.

Un timp pentru acțiune

Acum că avem comportamentul de bază al butonului în jos, avem nevoie de un eveniment care să se declanșeze atunci când sunt apăsați. Butonul mai ușor de implementat este startButton . La clic, trebuie doar să prezentăm GameScene . Actualizați handleStartButtonClick() în funcția MenuScene.swift la următorul cod:

 func handleStartButtonClick() { let transition = SKTransition.reveal(with: .down, duration: 0.75) let gameScene = GameScene(size: size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene, transition: transition) }

Dacă rulați aplicația acum și apăsați butonul, jocul va începe!

Acum trebuie să implementăm comutatorul de sunet. Avem deja un manager de sunet, dar trebuie să-i putem spune dacă dezactivarea sunetului este activată sau dezactivată. În Constants.swift , trebuie să adăugăm o cheie pentru a persista când dezactivarea sunetului este activată. Adăugați următorul rând:

 let MuteKey = "RAINCAT_MUTED"

Vom folosi acest lucru pentru a salva o valoare booleană în UserDefaults . Acum că este configurat, ne putem muta în SoundManager.swift . Aici vom verifica și seta UserDefaults pentru a vedea dacă dezactivarea este activată sau dezactivată. În partea de sus a fișierului, sub variabila trackPosition , adăugați următoarea linie:

 private(set) var isMuted = false

Aceasta este variabila pe care meniul principal (și orice altceva care va reda sunet) o verifică pentru a determina dacă sunetul este permis. O inițializam ca false , dar acum trebuie să verificăm UserDefaults pentru a vedea ce vrea utilizatorul. Înlocuiți funcția init() cu următoarele:

 private override init() { //This is private, so you can only have one Sound Manager ever. trackPosition = Int(arc4random_uniform(UInt32(SoundManager.tracks.count))) let defaults = UserDefaults.standard isMuted = defaults.bool(forKey: MuteKey) }

Acum că avem o valoare implicită pentru isMuted , avem nevoie de capacitatea de a o modifica. Adăugați următorul cod în partea de jos a SoundManager.swift :

 func toggleMute() -> Bool { isMuted = !isMuted let defaults = UserDefaults.standard defaults.set(isMuted, forKey: MuteKey) defaults.synchronize() if isMuted { audioPlayer?.stop() } else { startPlaying() } return isMuted }

Această metodă va comuta variabila noastră dezactivată, precum și va actualiza UserDefaults . Dacă noua valoare nu este dezactivată, va începe redarea muzicii; dacă noua valoare este dezactivată, redarea nu va începe. În caz contrar, vom opri redarea piesei curente. După aceasta, trebuie să edităm instrucțiunea if în startPlaying() .

Găsiți următoarea linie:

 if audioPlayer == nil || audioPlayer?.isPlaying == false {

Și înlocuiește-l cu asta:

 if !isMuted && (audioPlayer == nil || audioPlayer?.isPlaying == false) {

Acum, dacă dezactivarea sunetului este dezactivată și fie playerul audio nu este setat, fie playerul audio curent nu mai este redat, vom reda piesa următoare.

De aici, ne putem muta înapoi în MenuScene.swift pentru a finaliza butonul de sunet. Înlocuiți handleSoundbuttonClick() cu următorul cod:

 func handleSoundButtonClick() { if SoundManager.sharedInstance.toggleMute() { //Is muted soundButton.texture = soundButtonTextureOff } else { //Is not muted soundButton.texture = soundButtonTexture } }

Aceasta comută sunetul în SoundManager , verifică rezultatul și apoi setează în mod corespunzător textura pentru a arăta utilizatorului dacă sunetul este dezactivat sau nu. Aproape am terminat! Trebuie doar să setăm textura inițială a butonului la lansare. În sceneDidLoad() , găsiți următoarea linie:

 soundButton = SKSpriteNode(texture: soundButtonTexture)

Și înlocuiește-l cu asta:

 soundButton = SKSpriteNode(texture: SoundManager.sharedInstance.isMuted ? soundButtonTextureOff : soundButtonTexture)

Exemplul de mai sus folosește un operator ternar pentru a seta textura corectă.

Acum că muzica este conectată, putem trece la CatSprite.swift pentru a dezactiva mieunatul pisicii când dezactivarea este activată. În hitByRain() , putem adăuga următoarea instrucțiune if după eliminarea acțiunii de mers pe jos:

 if SoundManager.sharedInstance.isMuted { return }

Această declarație va returna dacă utilizatorul a dezactivat sunetul aplicației. Din această cauză, vom ignora complet efectele noastre currentRainHits , maxRainHits și efectele de sunet mieunat.

După toate acestea, acum este timpul să încercăm butonul nostru de sunet. Rulați aplicația și verificați dacă se redă și dacă sunetele sunt dezactivate în mod corespunzător. Opriți sunetul, închideți aplicația și redeschideți-o. Asigurați-vă că setarea sunetului persistă. Rețineți că, dacă doar dezactivați și rulați din nou aplicația din Xcode, este posibil să nu fi acordat suficient timp pentru ca UserDefaults să fie salvat. Joacă jocul și asigură-te că pisica nu miaună niciodată când ești dezactivat.

Testarea funcționalității butonului.

Ieșirea din joc

Acum că avem primul tip de buton pentru meniul principal, putem intra în afaceri dificile adăugând butonul de ieșire la scena jocului nostru. Unele interacțiuni interesante pot veni cu stilul nostru de joc; în prezent, umbrela se va deplasa oriunde utilizatorul atinge sau își mută atingerea. Evident, mutarea umbrelei către butonul de ieșire atunci când utilizatorul încearcă să iasă din joc este o experiență destul de slabă pentru utilizator, așa că vom încerca să oprim acest lucru.

Butonul de ieșire pe care îl implementăm va imita butonul de pornire a jocului pe care l-am adăugat mai devreme, o mare parte din proces rămânând la fel. Schimbarea va fi în modul în care gestionăm atingerile. quit_button și quit_button_pressed în fișierul Assets.xcassets și adăugați următorul cod în fișierul HudNode.swift :

 private var quitButton : SKSpriteNode! private let quitButtonTexture = SKTexture(imageNamed: "quit_button") private let quitButtonPressedTexture = SKTexture(imageNamed: "quit_button_pressed")

Aceasta va gestiona referința noastră quitButton , împreună cu texturile pe care le vom seta pentru stările butoanelor. Pentru a ne asigura că nu actualizăm din neatenție umbrela în timp ce încercăm să renunțăm, avem nevoie de o variabilă care să spună HUD-ului (și scenei jocului) că interacționăm cu butonul de ieșire și nu cu umbrela. Adăugați următorul cod sub variabila boolean showingHighScore :

 private(set) var quitButtonPressed = false

Din nou, aceasta este o variabilă pe care numai HudNode o poate seta, dar pe care alte clase o pot verifica. Acum că variabilele noastre sunt configurate, putem adăuga butonul la HUD. Adăugați următorul cod la funcția setup(size:) :

 quitButton = SKSpriteNode(texture: quitButtonTexture) let margin : CGFloat = 15 quitButton.position = CGPoint(x: size.width - quitButton.size.width - margin, y: size.height - quitButton.size.height - margin) quitButton.zPosition = 1000 addChild(quitButton)

Codul de mai sus va seta butonul de ieșire cu textura stării noastre neapăsate. De asemenea, setăm poziția în colțul din dreapta sus și zPosition la un număr mare pentru a o forța să deseneze întotdeauna deasupra. Dacă rulați jocul acum, acesta va apărea în GameScene , dar nu se va putea încă face clic pe el.

butonul Ieșire
Rețineți noul buton de ieșire din HUD-ul nostru.

Acum că butonul a fost poziționat, trebuie să putem interacționa cu el. În acest moment, singurul loc în care avem interacțiune în GameScene este atunci când interacționăm cu umbrellaSprite . În exemplul nostru, HUD-ul va avea prioritate față de umbrelă, astfel încât utilizatorii să nu fie nevoiți să mute umbrela din drum pentru a ieși. Putem crea aceleași funcții în HudNode.swift pentru a imita funcționalitatea tactilă din GameScene.swift . Adăugați următorul cod la HudNode.swift :

 func touchBeganAtPoint(point: CGPoint) { let containsPoint = quitButton.contains(point) if quitButtonPressed && !containsPoint { //Cancel the last click quitButtonPressed = false quitButton.texture = quitButtonTexture } else if containsPoint { quitButton.texture = quitButtonPressedTexture quitButtonPressed = true } } func touchMovedToPoint(point: CGPoint) { if quitButtonPressed { if quitButton.contains(point) { quitButton.texture = quitButtonPressedTexture } else { quitButton.texture = quitButtonTexture } } } func touchEndedAtPoint(point: CGPoint) { if quitButton.contains(point) { //TODO tell the gamescene to quit the game } quitButton.texture = quitButtonTexture }

Codul de mai sus seamănă mult cu codul pe care l-am creat pentru MenuScene . Diferența este că există un singur buton pe care să îl urmărim, astfel încât să putem gestiona totul în aceste metode de atingere. De asemenea, pentru că vom ști locația atingerii în GameScene , putem doar să verificăm dacă butonul nostru conține punctul de atingere.

Treceți la GameScene.swift și înlocuiți touchesBegan(_ touches with event:) și touchesMoved(_ touches: with event:) cu următorul cod:

 override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchBeganAtPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchMovedToPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchEndedAtPoint(point: point) } }

Aici, fiecare metodă tratează totul aproape în același mod. Îi spunem HUD-ului că utilizatorul a interacționat cu scena. Apoi, verificăm dacă butonul de ieșire captează momentan atingerile. Dacă nu este, atunci mutam umbrela. Am adăugat și funcția touchesEnded(_ touches: with event:) pentru a gestiona sfârșitul clicului pentru butonul de ieșire, dar încă nu o folosim pentru umbrellaSprite .

Făcând clic pe butonul de ieșire nu va muta umbrela, dar făcând clic în altă parte.

Acum că avem un buton, avem nevoie de o modalitate prin care acesta să afecteze GameScene . Adăugați următoarea linie în partea de sus a lui HudeNode.swift :

 var quitButtonAction : (() -> ())?

Aceasta este o închidere generică care nu are nicio intrare și nicio ieșire. Vom seta acest lucru cu cod în fișierul GameScene.swift și îl vom numi când facem clic pe butonul din HudNode.swift . Apoi, putem înlocui TODO din codul creat mai devreme în funcția touchEndedAtPoint(point:) cu aceasta:

 if quitButton.contains(point) && quitButtonAction != nil { quitButtonAction!() }

Acum, dacă setăm închiderea quitButtonAction , va fi apelat din acest punct.

Pentru a configura închiderea quitButtonAction , trebuie să trecem la GameScene.swift . În sceneDidLoad() , putem înlocui configurarea HUD-ului cu următorul cod:

 hudNode.setup(size: size) hudNode.quitButtonAction = { let transition = SKTransition.reveal(with: .up, duration: 0.75) let gameScene = MenuScene(size: self.size) gameScene.scaleMode = self.scaleMode self.view?.presentScene(gameScene, transition: transition) self.hudNode.quitButtonAction = nil } addChild(hudNode)

Rulați aplicația, apăsați pe redare, apoi apăsați pe Ieșire. Dacă te-ai întors la meniul principal, atunci butonul de ieșire funcționează conform intenției. În închiderea pe care am creat-o, am inițializat o tranziție la MenuScene . Și am setat această închidere a nodului HUD să ruleze atunci când se face clic pe butonul de ieșire. O altă linie importantă aici este atunci când setăm quitButtonAction la nil . Motivul pentru aceasta este că are loc un ciclu de reținere. Scena conține o referință la HUD, unde HUD-ul conține o referință la scenă. Deoarece există o referire la ambele obiecte, niciunul nu va fi eliminat atunci când va veni timpul pentru colectarea gunoiului. În acest caz, de fiecare dată când intrăm și ieșim din GameScene , o altă instanță a acesteia va fi creată și nu va fi lansată niciodată. Acest lucru este rău pentru performanță și aplicația va rămâne în cele din urmă fără memorie. Există mai multe moduri de a evita acest lucru, dar în cazul nostru putem elimina doar referința la GameScene din HUD, iar scena și HUD-ul vor fi terminate odată ce ne întoarcem la MenuScene . Krakendev are o explicație mai profundă a tipurilor de referință și a modului de a evita aceste cicluri.

Acum, treceți la GameViewController.swift și eliminați sau comentați următoarele trei rânduri de cod:

 view.showsPhysics = true view.showsFPS = true view.showsNodeCount = true

Cu datele de depanare din drum, jocul arată foarte bine! Felicitări: în prezent suntem în versiune beta! Consultați codul final de astăzi pe GitHub.

Gânduri finale

Aceasta este ultima lecție a unui tutorial în trei părți și, dacă ați ajuns până aici, ați lucrat mult la jocul dvs. În acest tutorial, ai trecut de la o scenă care nu avea absolut nimic în ea, la un joc finalizat. Felicitări! În lecția unu, am adăugat podeaua, picăturile de ploaie, fundalul și sprite-ul umbrelă. Ne-am jucat și cu fizica și ne-am asigurat că picăturile noastre de ploaie nu se adună. Am început cu detectarea coliziunilor și am lucrat la eliminarea nodurilor, astfel încât să nu rămânem fără memorie. Am adăugat, de asemenea, o anumită interacțiune cu utilizatorul, permițând umbrelei să se miște spre locul în care utilizatorul atinge ecranul.

În lecția a doua, am adăugat pisica și mâncarea, împreună cu metode personalizate de depunere a icrelor pentru fiecare dintre ele. Ne-am actualizat detectarea coliziunilor pentru a permite pisica și sprite-urile de mâncare. Am lucrat și la mișcarea pisicii. Pisica și-a câștigat un scop: să mănânce fiecare bucată de mâncare disponibilă. Am adăugat o animație simplă pentru pisică și am adăugat interacțiuni personalizate între pisică și ploaie. În cele din urmă, am adăugat efecte sonore și muzică pentru a face să pară un joc complet.

În această ultimă lecție, am creat un afișaj heads-up pentru a menține eticheta de scor, precum și butonul de ieșire. Am gestionat acțiuni între noduri și am permis utilizatorului să renunțe cu un apel invers de la nodul HUD. Am adăugat, de asemenea, o altă scenă în care utilizatorul se poate lansa și la care poate reveni după ce face clic pe butonul de ieșire. Ne-am ocupat de procesul de pornire a jocului și de control al sunetului în joc.

Unde să mergi de aici

Am alocat mult timp pentru a ajunge atât de departe, dar încă mai este multă muncă în acest joc. RainCat continuă dezvoltarea și este disponibil în App Store. Mai jos este o listă de dorințe și trebuie adăugate. Unele dintre articole au fost adăugate, în timp ce altele sunt încă în așteptare:

  • Adăugați pictograme și un ecran de deschidere.
  • Finalizați meniul principal (simplificat pentru tutorial).
  • Remediați erorile, inclusiv picăturile de ploaie necinstite și depunerea mai multor alimente.
  • Refactorizați și optimizați codul.
  • Schimbați paleta de culori a jocului în funcție de scor.
  • Actualizați dificultatea în funcție de scor.
  • Animați pisica când mâncarea este chiar deasupra ei.
  • Integrați Centrul de jocuri.
  • Acordați credit (inclusiv credit adecvat pentru melodii).

Urmăriți pe GitHub, deoarece aceste modificări vor fi făcute în viitor. Dacă aveți întrebări despre cod, nu ezitați să ne scrieți la [email protected] și îl putem discuta. Dacă anumite subiecte primesc suficientă atenție, poate putem scrie un alt articol care să discute subiectul.

Mulțumesc!

Vreau să mulțumesc tuturor oamenilor care au ajutat în procesul de creare a jocului și de dezvoltare a articolelor care îl însoțesc.

  • Cathryn Rowe Pentru arta inițială, design și editare și pentru publicarea articolelor în Garajul nostru.
  • Morgan Wheaton Pentru designul final al meniului și paletele de culori (care vor arăta grozav odată ce implementez aceste funcții – rămâneți pe fază).
  • Nikki Clark Pentru anteturile și divizoarele minunate din articole și pentru ajutor la editarea articolelor.
  • Laura Levisay Pentru toate GIF-urile minunate din articole și pentru că mi-ai trimis GIF-uri drăguțe de pisici pentru sprijin moral.
  • Tom Hudson Pentru ajutor la editarea articolelor și fără de care această serie nu ar fi fost realizată deloc.
  • Lani DeGuire Pentru ajutor cu editarea articolelor, care a fost o mulțime de muncă.
  • Jeff Moon Pentru ajutor la editarea lecției trei și a jocului de ping-pong. Mult ping-pong.
  • Tom Nelson Pentru că m-a ajutat să vă asigurați că tutorialul funcționează așa cum ar trebui.

Serios, a fost nevoie de o mulțime de oameni pentru a pregăti totul pentru acest articol și pentru a-l lansa în magazin.

Mulțumesc tuturor celor care citesc și această propoziție.