Come costruire un gioco SpriteKit in Swift 3 (parte 3)

Pubblicato: 2022-03-10
Riassunto veloce ↬ Ti sei mai chiesto cosa serve per creare un gioco SpriteKit? I pulsanti sembrano un compito più grande di quanto dovrebbero essere? Ti sei mai chiesto come mantenere le impostazioni in un gioco? Creare giochi non è mai stato così facile su iOS dall'introduzione di SpriteKit. Nella terza parte di questa serie in tre parti, finiremo il nostro gioco RainCat e completeremo la nostra introduzione a SpriteKit. Se ti sei perso la lezione precedente, puoi recuperare il ritardo ottenendo il codice su GitHub. Ricorda che questo tutorial richiede Xcode 8 e Swift 3.

Ti sei mai chiesto cosa serve per creare un gioco SpriteKit? I pulsanti sembrano un compito più grande di quanto dovrebbero essere? Ti sei mai chiesto come mantenere le impostazioni in un gioco? Creare giochi non è mai stato così facile su iOS dall'introduzione di SpriteKit. Nella terza parte di questa serie in tre parti, finiremo il nostro gioco RainCat e completeremo la nostra introduzione a SpriteKit.

Se ti sei perso la lezione precedente, puoi recuperare il ritardo ottenendo il codice su GitHub. Ricorda che questo tutorial richiede Xcode 8 e Swift 3.

Ulteriori letture su SmashingMag: Link

  • Gamification e UX: dove gli utenti vincono o perdono
  • Design UX giocoso: costruire un gioco migliore
  • Combinare UX Design e psicologia per cambiare il comportamento degli utenti
Raincat, lezione 3
RainCat, lezione 3
Altro dopo il salto! Continua a leggere sotto ↓

Questa è la lezione tre del nostro viaggio RainCat. Nella lezione precedente, abbiamo trascorso una lunga giornata trascorrendo alcune semplici animazioni, comportamenti dei gatti, rapidi effetti sonori e musica di sottofondo.

Oggi ci concentreremo su quanto segue:

  • head-up display (HUD) per il punteggio;
  • menu principale — con pulsanti;
  • opzioni per silenziare i suoni;
  • opzione per uscire dal gioco.

Ancora più risorse

Le risorse per la lezione finale sono disponibili su GitHub. Trascina nuovamente le immagini in Assets.xcassets , proprio come abbiamo fatto nelle lezioni precedenti.

Dritta!

Abbiamo bisogno di un modo per tenere il punteggio. Per fare ciò, possiamo creare un head-up display (HUD). Questo sarà abbastanza semplice; sarà uno SKNode che contiene il punteggio e un pulsante per uscire dal gioco. Per ora, ci concentreremo solo sul punteggio. Il font che useremo è Pixel Digivolve, che puoi ottenere su Dafont.com. Come per l'utilizzo di immagini o suoni che non sono tuoi, leggi la licenza del font prima di usarlo. Questo afferma che è gratuito per uso personale, ma se ti piace davvero il carattere, puoi donare all'autore dalla pagina. Non puoi sempre fare tutto da solo, quindi restituire a coloro che ti hanno aiutato lungo il percorso è bello.

Successivamente, dobbiamo aggiungere il carattere personalizzato al progetto. Questo processo può essere complicato la prima volta.

Scarica e sposta il carattere nella cartella del progetto, in una cartella "Caratteri". L'abbiamo fatto alcune volte nelle lezioni precedenti, quindi seguiremo questo processo un po' più rapidamente. Aggiungi un gruppo denominato Fonts al progetto e aggiungi il file Pixel digivolve.otf .

Ora viene la parte difficile. Se ti manca questa parte, probabilmente non sarai in grado di utilizzare il carattere. Dobbiamo aggiungerlo al nostro file Info.plist . Questo file si trova nel riquadro sinistro di Xcode. Fare clic e vedrai l'elenco delle proprietà (o plist ). Fare clic con il pulsante destro del mouse sull'elenco e fare clic su "Aggiungi riga".

Aggiungi riga
Aggiungi una riga al plist .

Quando viene visualizzata la nuova riga, inserisci quanto segue:

 Fonts provided by application

Quindi, sotto Item 0 , dobbiamo aggiungere il nome del nostro font. Il plist dovrebbe essere simile al seguente:

Pixel digivolve.otf
Carattere aggiunto a plist con successo!

Il carattere dovrebbe essere pronto per l'uso! Dovremmo fare un rapido test per assicurarci che funzioni come previsto. Passa a GameScene.swift e in sceneDidLoad aggiungi il codice seguente nella parte superiore della funzione:

 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)

Funziona?

Ciao mondo!
Testare il nostro SKLabelNode . Oh no! L'etichetta "Hello World" è tornata.

Se funziona, hai fatto tutto correttamente. Se no, allora qualcosa non va. Code With Chris ha una guida alla risoluzione dei problemi più approfondita, ma tieni presente che è per una versione precedente di Swift, quindi dovrai apportare piccole modifiche per portarlo su Swift 3.

Ora che possiamo caricare i caratteri personalizzati, possiamo iniziare sul nostro HUD. Elimina l'etichetta "Hello World", perché l'abbiamo usata solo per assicurarci che il nostro carattere venga caricato. L'HUD sarà uno SKNode , che agirà come un contenitore per i nostri elementi HUD. Questo è lo stesso processo che abbiamo seguito durante la creazione del nodo in background nella lezione uno.

Crea il file HudNode.swift usando i soliti metodi e inserisci il seguente codice:

 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)" } }

Prima di fare qualsiasi altra cosa, apri Constants.swift e aggiungi la seguente riga in fondo al file: la useremo per recuperare e mantenere il punteggio più alto:

 let ScoreKey = "RAINCAT_HIGHSCORE"

Nel codice abbiamo cinque variabili che riguardano il tabellone segnapunti. La prima variabile è l'effettivo SKLabelNode , che usiamo per presentare l'etichetta. La prossima è la nostra variabile per contenere il punteggio corrente; quindi la variabile che detiene il punteggio migliore. L'ultima variabile è un booleano che ci dice se stiamo presentando il punteggio più alto (usiamo questo per stabilire se è necessario eseguire una SKAction per aumentare la scala del tabellone e colorarlo del giallo del pavimento).

La prima funzione, setup(size:) , serve solo per impostare tutto. Abbiamo impostato SKLabelNode nello stesso modo in cui lo abbiamo fatto in precedenza. La classe SKNode non ha proprietà di dimensione per impostazione predefinita, quindi dobbiamo creare un modo per impostare una dimensione per posizionare la nostra etichetta scoreNode . Stiamo anche recuperando il punteggio più alto corrente da UserDefaults . Questo è un modo semplice e veloce per salvare piccoli blocchi di dati, ma non è sicuro. Poiché non siamo preoccupati per la sicurezza per questo esempio, UserDefaults va perfettamente bene.

Nel nostro addPoint() , stiamo incrementando la variabile del score corrente e controllando se l'utente ha ottenuto un punteggio elevato. Se hanno un punteggio alto, salviamo quel punteggio in UserDefaults e controlliamo se stiamo attualmente mostrando il punteggio migliore. Se l'utente ha ottenuto un punteggio elevato, possiamo animare la dimensione e il colore di scoreNode .

Nella funzione resetPoints() , impostiamo il punteggio corrente su 0 . Dobbiamo quindi verificare se stavamo mostrando il punteggio più alto e ripristinare le dimensioni e il colore sui valori predefiniti, se necessario.

Infine, abbiamo una piccola funzione chiamata updateScoreboard . Questa è una funzione interna per impostare il punteggio sul testo di scoreNode . Questo viene chiamato sia in addPoint() che resetPoints() .

Collegamento dell'HUD

Dobbiamo verificare se il nostro HUD funziona correttamente. Passa a GameScene.swift e aggiungi la seguente riga sotto la variabile foodNode nella parte superiore del file:

 private let hudNode = HudNode()

Aggiungi le seguenti due righe nella funzione sceneDidLoad() , vicino alla parte superiore:

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

Quindi, nella funzione spawnCat() , reimposta i punti nel caso in cui il gatto sia caduto dallo schermo. Aggiungi la seguente riga dopo aver aggiunto lo sprite del gatto alla scena:

 hudNode.resetPoints()

Successivamente, nella funzione handleCatCollision(contact:) , è necessario azzerare nuovamente il punteggio quando il gatto viene colpito dalla pioggia. Nell'istruzione switch alla fine della funzione, quando l'altro corpo è una RainDropCategory , aggiungi la riga seguente:

 hudNode.resetPoints()

Infine, dobbiamo comunicare al tabellone segnapunti quando l'utente ha guadagnato punti. Alla fine del file in handleFoodHit(contact:) , trova le seguenti righe fino a qui:

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

E sostituiscili con questo:

 hudNode.addPoint()

Ecco!

HUD sbloccato!
HUD sbloccato!

Dovresti vedere l'HUD in azione. Corri in giro e raccogli del cibo. La prima volta che raccogli il cibo, dovresti vedere il punteggio diventare giallo e crescere di scala. Quando vedi che succede, lascia che il gatto venga colpito. Se il punteggio si azzera, saprai di essere sulla strada giusta!

Punteggio alto!
Punteggio più alto di sempre (... al momento della scrittura)!

La scena successiva

Esatto, ci spostiamo in un'altra scena! Infatti, una volta completata, questa sarà la prima schermata della nostra app. Prima di fare qualsiasi altra cosa, apri Constants.swift e aggiungi la seguente riga in fondo al file: la useremo per recuperare e mantenere il punteggio più alto:

 let ScoreKey = "RAINCAT_HIGHSCORE"

Crea la nuova scena, posizionala nella cartella "Scene" e chiamala MenuScene.swift . Immettere il codice seguente nel file 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) } }

Poiché questa scena è relativamente semplice, non creeremo classi speciali. La nostra scena sarà composta da due pulsanti. Questi potrebbero essere (e forse meritare di essere) la loro classe di SKSpriteNodes , ma poiché sono abbastanza diversi, non avremo bisogno di creare nuove classi per loro. Questo è un suggerimento importante per quando crei il tuo gioco: devi essere in grado di determinare dove fermarti e refactoring del codice quando le cose si complicano. Dopo aver aggiunto più di tre o quattro pulsanti a un gioco, potrebbe essere il momento di interrompere e riformulare il codice del pulsante del menu nella sua classe.

Il codice sopra non sta facendo nulla di speciale; sta impostando le posizioni di quattro sprite. Stiamo anche impostando il colore di sfondo della scena, in modo che l'intero sfondo sia il valore corretto. Un ottimo strumento per generare codici colore da stringhe HEX per Xcode è UI Color. Il codice sopra sta anche impostando le trame per i nostri stati dei pulsanti. Il pulsante per avviare il gioco ha uno stato normale e uno stato premuto, mentre il pulsante audio è un interruttore. Per semplificare le cose per l'interruttore, cambieremo il valore alfa del pulsante audio alla pressione dell'utente. Stiamo anche estraendo e impostando il punteggio più alto SKLabelNode .

Il nostro MenuScene un bell'aspetto. Ora dobbiamo mostrare la scena quando l'app viene caricata. Passa a GameViewController.swift e trova la seguente riga:

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

Sostituiscilo con questo:

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

Questa piccola modifica caricherà MenuScene per impostazione predefinita, invece di GameScene .

La nostra nuova scena!
La nostra nuova scena! Nota 1,0 fotogrammi al secondo: nulla si muove, quindi non è necessario aggiornare nulla.

Stati del pulsante

I pulsanti possono essere complicati in SpriteKit. Sono disponibili molte opzioni di terze parti (ne ho anche fatto una io), ma in teoria devi solo conoscere i tre metodi touch:

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

Ne abbiamo parlato brevemente durante l'aggiornamento dell'ombrello, ma ora dobbiamo sapere quanto segue: quale pulsante è stato toccato, se l'utente ha rilasciato il tocco o ha fatto clic su quel pulsante e se l'utente lo sta ancora toccando. È qui che entra in gioco la nostra variabile selectedButton . Quando inizia un tocco, possiamo catturare il pulsante su cui l'utente ha iniziato a fare clic con quella variabile. Se trascinano fuori dal pulsante, possiamo gestirlo e dargli la trama appropriata. Quando rilasciano il tocco, possiamo quindi vedere se stanno ancora toccando all'interno del pulsante. Se lo sono, allora possiamo applicarvi l'azione associata. Aggiungi le seguenti righe in fondo 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") }

Questa è una semplice gestione dei pulsanti per i nostri due pulsanti. In touchesBegan(_ touches: with events:) , iniziamo controllando se sono presenti pulsanti attualmente selezionati. In tal caso, è necessario ripristinare lo stato del pulsante su non premuto. Quindi, dobbiamo verificare se è stato premuto un pulsante. Se ne viene premuto uno, mostrerà lo stato evidenziato per il pulsante. Quindi, impostiamo selectedButton sul pulsante da utilizzare negli altri due metodi.

In touchesMoved(_ touches: with events:) , controlliamo quale pulsante è stato originariamente toccato. Quindi, controlliamo se il tocco corrente è ancora entro i limiti di selectedButton e aggiorniamo lo stato evidenziato da lì. Lo stato evidenziato di startButton cambia la texture nella texture dello stato premuto, dove lo stato evidenziato di soundButton ha il valore alfa dello sprite impostato su 50%.

Infine, in touchesEnded(_ touches: with event:) , controlliamo di nuovo quale pulsante è selezionato, se presente, e quindi se il tocco è ancora entro i limiti del pulsante. Se tutti i casi sono soddisfatti, chiamiamo handleStartButtonClick() o handleSoundButtonClick() per il pulsante corretto.

Un tempo per l'azione

Ora che abbiamo disattivato il comportamento di base dei pulsanti, abbiamo bisogno di un evento da attivare quando vengono cliccati. Il pulsante più semplice da implementare è startButton . Al clic, dobbiamo solo presentare GameScene . Aggiorna handleStartButtonClick() nella funzione MenuScene.swift al codice seguente:

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

Se esegui ora l'app e premi il pulsante, il gioco inizierà!

Ora dobbiamo implementare l'interruttore muto. Abbiamo già un sound manager, ma dobbiamo essere in grado di dirgli se il muting è attivo o meno. In Constants.swift , è necessario aggiungere una chiave per persistere quando il silenziamento è attivo. Aggiungi la seguente riga:

 let MuteKey = "RAINCAT_MUTED"

Lo useremo per salvare un valore booleano in UserDefaults . Ora che è impostato, possiamo passare a SoundManager.swift . Qui è dove controlleremo e imposteremo UserDefaults per vedere se il muting è attivato o disattivato. Nella parte superiore del file, sotto la variabile trackPosition , aggiungi la seguente riga:

 private(set) var isMuted = false

Questa è la variabile che il menu principale (e qualsiasi altra cosa che riprodurrà il suono) controlla per determinare se il suono è consentito. Lo inizializziamo come false , ma ora dobbiamo controllare UserDefaults per vedere cosa vuole l'utente. Sostituisci la funzione init() con la seguente:

 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) }

Ora che abbiamo un valore predefinito per isMuted , abbiamo bisogno della possibilità di cambiarlo. Aggiungi il codice seguente in fondo 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 }

Questo metodo attiverà la nostra variabile silenziata e aggiornerà UserDefaults . Se il nuovo valore non viene disattivato, inizierà la riproduzione della musica; se il nuovo valore è disattivato, la riproduzione non inizierà. In caso contrario, interromperemo la riproduzione della traccia corrente. Dopodiché, dobbiamo modificare l'istruzione if in startPlaying() .

Trova la seguente riga:

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

E sostituiscilo con questo:

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

Ora, se il silenziamento è disattivato e il lettore audio non è impostato o il lettore audio corrente non è più in riproduzione, riprodurremo la traccia successiva.

Da qui, possiamo tornare a MenuScene.swift per terminare il nostro pulsante muto. Sostituisci handleSoundbuttonClick() con il codice seguente:

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

Questo attiva o disattiva il suono in SoundManager , controlla il risultato e quindi imposta in modo appropriato la trama per mostrare all'utente se il suono è disattivato o meno. Abbiamo quasi finito! Abbiamo solo bisogno di impostare la trama iniziale del pulsante all'avvio. In sceneDidLoad() , trova la riga seguente:

 soundButton = SKSpriteNode(texture: soundButtonTexture)

E sostituiscilo con questo:

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

L'esempio precedente utilizza un operatore ternario per impostare la trama corretta.

Ora che la musica è collegata, possiamo passare a CatSprite.swift per disabilitare il miagolio del gatto quando il silenziamento è attivo. In hitByRain() , possiamo aggiungere la seguente istruzione if dopo aver rimosso l'azione walking:

 if SoundManager.sharedInstance.isMuted { return }

Questa istruzione restituirà se l'utente ha disattivato l'audio dell'app. Per questo motivo, ignoreremo completamente i nostri currentRainHits , maxRainHits ed effetti sonori miagolanti.

Dopo tutto questo, ora è il momento di provare il nostro pulsante muto. Esegui l'app e verifica se sta riproducendo e silenziando i suoni in modo appropriato. Disattiva l'audio, chiudi l'app e riaprila. Assicurati che l'impostazione di disattivazione dell'audio persista. Tieni presente che se disattivi l'audio e riesegui l'app da Xcode, potresti non aver concesso abbastanza tempo per il salvataggio di UserDefaults . Gioca e assicurati che il gatto non miagoli mai quando sei muto.

Testare la funzionalità dei pulsanti.

Uscita dal gioco

Ora che abbiamo il primo tipo di pulsante per il menu principale, possiamo entrare in qualche affare complicato aggiungendo il pulsante Esci alla nostra scena di gioco. Alcune interazioni interessanti possono venire fuori con il nostro stile di gioco; attualmente, l'ombrello si sposterà dove l'utente tocca o sposta il suo tocco. Ovviamente, l'ombrello che si sposta sul pulsante Esci quando l'utente sta tentando di uscire dal gioco è un'esperienza utente piuttosto scadente, quindi cercheremo di impedire che ciò accada.

Il pulsante Esci che stiamo implementando imiterà il pulsante di avvio del gioco che abbiamo aggiunto in precedenza, con gran parte del processo che rimarrà lo stesso. Il cambiamento sarà nel modo in cui gestiamo i tocchi. Ottieni le tue quit_button e quit_button_pressed nel file Assets.xcassets e aggiungi il codice seguente al file HudNode.swift :

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

Questo gestirà il nostro riferimento quitButton , insieme alle trame che imposteremo per gli stati dei pulsanti. Per assicurarci di non aggiornare inavvertitamente l'ombrello durante il tentativo di uscire, abbiamo bisogno di una variabile che indichi all'HUD (e alla scena di gioco) che stiamo interagendo con il pulsante di uscita e non con l'ombrello. Aggiungi il codice seguente sotto la variabile booleana showingHighScore :

 private(set) var quitButtonPressed = false

Ancora una volta, questa è una variabile che solo HudNode può impostare ma che altre classi possono controllare. Ora che le nostre variabili sono impostate, possiamo aggiungere il pulsante all'HUD. Aggiungi il seguente codice alla funzione 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)

Il codice sopra imposterà il pulsante di uscita con la trama del nostro stato non premuto. Stiamo anche impostando la posizione nell'angolo in alto a destra e impostando zPosition su un numero alto per forzarlo a disegnare sempre in alto. Se esegui il gioco ora, verrà visualizzato in GameScene , ma non sarà ancora selezionabile.

Pulsante Esci
Nota il nuovo pulsante Esci nel nostro HUD.

Ora che il pulsante è stato posizionato, dobbiamo essere in grado di interagire con esso. In questo momento, l'unico posto in cui abbiamo interazione in GameScene è quando stiamo interagendo con umbrellaSprite . Nel nostro esempio, l'HUD avrà la priorità sull'ombrello, in modo che gli utenti non debbano spostare l'ombrello per uscire. Possiamo creare le stesse funzioni in HudNode.swift per imitare la funzionalità touch in GameScene.swift . Aggiungi il seguente codice a 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 }

Il codice sopra è molto simile al codice che abbiamo creato per MenuScene . La differenza è che c'è un solo pulsante di cui tenere traccia, quindi possiamo gestire tutto all'interno di questi metodi touch. Inoltre, poiché conosceremo la posizione del tocco in GameScene , possiamo semplicemente verificare se il nostro pulsante contiene il punto di tocco.

Passa a GameScene.swift e sostituisci i touchesBegan(_ touches with event:) e touchesMoved(_ touches: with event:) con il codice seguente:

 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) } }

Qui, ogni metodo gestisce tutto più o meno allo stesso modo. Stiamo dicendo all'HUD che l'utente ha interagito con la scena. Quindi, controlliamo se il pulsante Esci sta attualmente catturando i tocchi. In caso contrario, spostiamo l'ombrello. Abbiamo anche aggiunto la touchesEnded(_ touches: with event:) per gestire la fine del clic per il pulsante Esci, ma non la stiamo ancora usando per umbrellaSprite .

Facendo clic sul pulsante Esci non si sposterà l'ombrello, ma facendo clic altrove lo farà.

Ora che abbiamo un pulsante, abbiamo bisogno di un modo per farlo influenzare GameScene . Aggiungi la seguente riga all'inizio di HudeNode.swift :

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

Questa è una chiusura generica che non ha input e nessun output. Lo imposteremo con il codice nel file GameScene.swift e lo chiameremo quando faremo clic sul pulsante in HudNode.swift . Quindi, possiamo sostituire TODO nel codice che abbiamo creato in precedenza nella funzione touchEndedAtPoint(point:) con questo:

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

Ora, se impostiamo la chiusura quitButtonAction , verrà chiamato da questo punto.

Per impostare la chiusura di quitButtonAction , dobbiamo passare a GameScene.swift . In sceneDidLoad() , possiamo sostituire la nostra configurazione HUD con il seguente codice:

 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)

Esegui l'app, premi Riproduci, quindi esci. Se sei tornato al menu principale, il pulsante Esci funziona come previsto. Nella chiusura che abbiamo creato, abbiamo inizializzato una transizione al MenuScene . E impostiamo questa chiusura sul nodo HUD in modo che venga eseguita quando si fa clic sul pulsante Esci. Un'altra riga importante qui è quando impostiamo quitButtonAction su nil . La ragione di ciò è che si sta verificando un ciclo di ritenzione. La scena contiene un riferimento all'HUD in cui l'HUD contiene un riferimento alla scena. Poiché esiste un riferimento a entrambi gli oggetti, nessuno dei due verrà eliminato quando arriva il momento della raccolta dei rifiuti. In questo caso, ogni volta che entriamo e usciamo da GameScene , ne verrà creata un'altra istanza e non verrà mai rilasciata. Questo è negativo per le prestazioni e l'app finirà per esaurire la memoria. Esistono diversi modi per evitarlo, ma nel nostro caso possiamo semplicemente rimuovere il riferimento a GameScene dall'HUD e la scena e l'HUD verranno terminati una volta tornati a MenuScene . Krakendev ha una spiegazione più approfondita dei tipi di riferimento e di come evitare questi cicli.

Ora passa a GameViewController.swift e rimuovi o commenta le seguenti tre righe di codice:

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

Con i dati di debug fuori mano, il gioco sembra davvero buono! Congratulazioni: siamo attualmente in versione beta! Dai un'occhiata al codice finale di oggi su GitHub.

Pensieri finali

Questa è l'ultima lezione di un tutorial in tre parti e, se sei arrivato così lontano, hai appena lavorato molto sul tuo gioco. In questo tutorial, sei passato da una scena che non conteneva assolutamente nulla a un gioco completato. Congratulazioni! Nella lezione uno, abbiamo aggiunto il pavimento, le gocce di pioggia, lo sfondo e gli sprite dell'ombrello. Abbiamo anche giocato con la fisica e fatto in modo che le nostre gocce di pioggia non si accumulassero. Abbiamo iniziato con il rilevamento delle collisioni e abbiamo lavorato sull'eliminazione dei nodi in modo da non esaurire la memoria. Abbiamo anche aggiunto alcune interazioni con l'utente consentendo all'ombrello di spostarsi verso il punto in cui l'utente tocca lo schermo.

Nella lezione due, abbiamo aggiunto il gatto e il cibo, insieme a metodi di deposizione delle uova personalizzati per ciascuno di essi. Abbiamo aggiornato il nostro rilevamento delle collisioni per consentire il gatto e gli sprite del cibo. Abbiamo anche lavorato sul movimento del gatto. Il gatto ha ottenuto uno scopo: mangiare ogni pezzetto di cibo disponibile. Abbiamo aggiunto una semplice animazione per il gatto e aggiunte interazioni personalizzate tra il gatto e la pioggia. Infine, abbiamo aggiunto effetti sonori e musica per farlo sembrare un gioco completo.

In quest'ultima lezione, abbiamo creato un display heads-up per contenere la nostra etichetta del punteggio, così come il nostro pulsante per uscire. Abbiamo gestito le azioni tra i nodi e consentito all'utente di uscire con una richiamata dal nodo HUD. Abbiamo anche aggiunto un'altra scena in cui l'utente può lanciarsi e tornare dopo aver fatto clic sul pulsante Esci. Abbiamo gestito il processo per l'avvio del gioco e per il controllo del suono nel gioco.

Dove andare da qui

Abbiamo impiegato molto tempo per arrivare così lontano, ma c'è ancora molto lavoro da fare in questo gioco. RainCat continua ancora lo sviluppo ed è disponibile nell'App Store. Di seguito è riportato un elenco di desideri e necessità da aggiungere. Alcuni degli elementi sono stati aggiunti, mentre altri sono ancora in sospeso:

  • Aggiungi icone e una schermata iniziale.
  • Finalizzare il menu principale (semplificato per il tutorial).
  • Risolti i bug, tra cui gocce di pioggia canaglia e la generazione di più cibo.
  • Refactor e ottimizzare il codice.
  • Cambia la tavolozza dei colori del gioco in base al punteggio.
  • Aggiorna la difficoltà in base al punteggio.
  • Anima il gatto quando il cibo è proprio sopra di esso.
  • Integra Game Center.
  • Dai credito (incluso il credito adeguato per i brani musicali).

Tieni traccia su GitHub perché queste modifiche verranno apportate in futuro. In caso di domande sul codice, non esitare a scriverci all'indirizzo [email protected] e possiamo discuterne. Se alcuni argomenti ottengono abbastanza attenzione, forse possiamo scrivere un altro articolo che discute l'argomento.

Grazie!

Voglio ringraziare tutte le persone che hanno aiutato nel processo di creazione del gioco e lo sviluppo degli articoli che lo accompagnano.

  • Cathryn Rowe Per la grafica iniziale, il design e l'editing, e per la pubblicazione degli articoli nel nostro Garage.
  • Morgan Wheaton Per il design finale del menu e le tavolozze dei colori (che sembreranno fantastiche una volta che avrò effettivamente implementato queste funzionalità, restate sintonizzati).
  • Nikki Clark Per le fantastiche intestazioni e divisori negli articoli e per l'aiuto con la modifica degli articoli.
  • Laura Levisay Per tutte le fantastiche GIF negli articoli e per avermi inviato GIF di gatti carini per supporto morale.
  • Tom Hudson Per aiuto con la modifica degli articoli e senza il quale questa serie non sarebbe stata realizzata affatto.
  • Lani DeGuire Per l'aiuto con la modifica degli articoli, che è stato un sacco di lavoro.
  • Jeff Moon Per aiuto nell'editing della lezione tre e il ping-pong. Tanto ping pong.
  • Tom Nelson Per aver contribuito a garantire che il tutorial funzioni come dovrebbe.

Seriamente, ci sono volute un sacco di persone per preparare tutto per questo articolo e pubblicarlo nel negozio.

Grazie anche a tutti coloro che leggono questa frase.