Accanto a tutte le nuove funzionalità e framework in iOS 9 e OS X El Capitan, con le versioni di quest'anno Apple ha anche creato un framework completamente nuovo per gli sviluppatori di giochi, GameplayKit. Con le API grafiche esistenti (SpriteKit, SceneKit e Metal) che semplificano la creazione di giochi di grande impatto su iOS e OS X, Apple ha ora rilasciato GameplayKit per semplificare la creazione di giochi che giocare bene. Questo nuovo framework contiene molte classi e funzionalità che possono essere utilizzate per aggiungere facilmente logica complessa ai tuoi giochi.
In questo primo tutorial, ti spiegherò due aspetti principali del framework GameplayKt:
Questo tutorial richiede che tu stia correndo Xcode 7 sopra OS X Yosemite o più tardi. Sebbene non sia richiesto, si consiglia di far funzionare un dispositivo fisico iOS 9 poiché otterrai prestazioni molto migliori durante il test del gioco basato su SpriteKit utilizzato in questo tutorial.
Per prima cosa devi scaricare il progetto di avvio per questa serie di tutorial da GitHub. Dopo averlo fatto, apri il progetto in Xcode ed eseguilo su iOS Simulator o sul tuo dispositivo.
Vedrai che è un gioco molto semplice in cui controlli un punto blu e navighi sulla mappa. Quando ti scontri con un punto rosso, perdi due punti. Quando ti scontri con un punto verde, guadagni un punto. Quando entri in collisione con un punto giallo, il tuo punto viene congelato per un paio di secondi.
In questa fase, è un gioco molto semplice, ma nel corso di questa serie di tutorial, e con l'aiuto di GameplayKit, aggiungeremo molte più funzionalità ed elementi di gameplay.
Il primo aspetto importante del nuovo framework GameplayKit è basato su un concetto di strutturazione del codice entità e componenti. Funziona consentendo a te, lo sviluppatore, di scrivere codice comune utilizzato da molti tipi di oggetti diversi nel tuo gioco mantenendolo ben organizzato e gestibile. Il concetto di entità e componenti ha lo scopo di eliminare l'approccio basato sull'eredità comune per condividere funzionalità comuni tra i tipi di oggetto. Il modo più semplice per comprendere questo concetto è con alcuni esempi, quindi immagina il seguente scenario:
Stai costruendo un gioco di difesa della torre con tre tipi principali di torri, Fuoco, Ghiaccio, e Guarire. I tre tipi di torri condividono alcuni dati comuni, come la salute, le dimensioni e la forza. Le tue torri Fuoco e Ghiaccio devono essere in grado di colpire i nemici in arrivo per sparare mentre la tua torre Heal no. Tutto ciò che la tua torre Heal deve fare è riparare le tue altre torri entro un certo raggio quando subiscono danni.
Con questo modello di gioco di base in mente, vediamo come il tuo codice potrebbe essere organizzato usando un eredità struttura:
Torre
classe contenente dati comuni quali salute, dimensioni e forza.Firetower
, IceTower
, e HealTower
classi che erediterebbero dal Torre
classe.HealTower
classe, hai la logica responsabile per la cura delle tue altre torri entro un certo raggio.Finora, questa struttura è a posto, ma ora sorge un problema quando è necessario implementare l'abilità di targeting delle torri di fuoco e ghiaccio. Ti basta copiare e incollare lo stesso codice in entrambi i tuoi Firetower
e IceTower
classi? Se è necessario apportare modifiche, sarà necessario modificare il codice in più di un posto, che è noioso e soggetto a errori. Oltre a ciò, cosa succede se vuoi aggiungere un nuovo tipo di torre che ha anche bisogno di questa funzionalità di targeting. Lo copia e incolla una terza volta?
Il modo migliore sembra essere mettere questa logica di targeting nel genitore Torre
classe. Ciò consentirebbe di avere solo una copia del codice che deve essere modificata in un unico posto. Aggiungere questo codice qui, tuttavia, renderebbe il Torre
classe molto più grande e più complicata di quanto deve essere quando non tutte le sue sottoclassi hanno bisogno di quella funzionalità. Se vuoi anche aggiungere più funzionalità condivise tra i tuoi tipi di torre, il tuo Torre
la classe diventerebbe gradualmente sempre più grande, il che renderebbe difficile la sua collaborazione.
Come puoi vedere, mentre è possibile creare un modello di gioco basato su eredità, può diventare molto rapidamente e facilmente disorganizzato e difficile da gestire.
Ora, vediamo come questo stesso modello di gioco può essere strutturato usando entità e componenti:
Firetower
, IceTower
, e HealTower
entità. Altre entità potrebbero essere create per altri tipi di torri che si desidera aggiungere in seguito.BasicTower
componente che dovrebbe contenere salute, dimensioni, forza, ecc.Guarigione
componente.Targeting
componente dovrebbe contenere il codice necessario per colpire i nemici in arrivo.Usando GameplayKit e questa struttura avresti quindi un tipo di entità unico per ogni tipo di torre nel tuo gioco. Per ogni singola entità, è possibile aggiungere i componenti desiderati che si desidera. Per esempio:
Firetower
e IceTower
le entità avrebbero ciascuno un BasicTower
e Targeting
componente ad esso collegato.HealTower
entità avrebbe entrambi a BasicTower
e a Guarigione
componente.Come puoi vedere, usando una struttura basata su entità e componenti, il tuo modello di gioco è ora molto più semplice e versatile. La tua logica di targeting deve essere scritta una volta sola e si collega solo alle entità di cui ha bisogno. Allo stesso modo, i tuoi dati di base della torre possono ancora essere facilmente condivisi tra tutte le tue torri senza mettere insieme tutte le altre funzionalità comuni.
Un'altra cosa fantastica di questa struttura basata su entità e componenti è che i componenti possono essere aggiunti e rimossi dalle entità ogni volta che lo si desidera. Ad esempio, se si desidera disabilitare le torri Heal in determinate condizioni, è sufficiente rimuovere il Guarigione
componente dalla tua entità fino a quando non vengono soddisfatte le condizioni giuste. Allo stesso modo, se volessi una delle tue torri di fuoco per ottenere un'abilità di guarigione temporanea, potresti semplicemente aggiungere un Guarigione
componente al tuo Firetower
entità per un determinato periodo di tempo.
Ora che sei a tuo agio con il concetto di una struttura del modello di gioco basata su entità e componenti, crearne uno nel nostro stesso gioco. In Xcode File Inspector, trovare la Entità cartella all'interno del tuo progetto. Per comodità, ci sono già tre classi di entità per te, ma ora creerai una nuova entità da zero.
Scegliere File> Nuovo> File ... o premere Comando-N per creare una nuova classe. Assicurati di selezionare il Cocoa Touch Class modello dal iOS> Origine sezione. Dai un nome alla classe Giocatore e renderlo una sottoclasse di GKEntity
.
Vedrai che immediatamente all'apertura del tuo nuovo file Xcode verrà visualizzato un errore. Per risolvere questo problema, aggiungi la seguente dichiarazione di importazione sotto quella esistente importa UIKit
dichiarazione:
Importa GameplayKit
Ritornare a PlayerNode.swift e aggiungere la seguente proprietà al PlayerNode
classe:
var entity = Player ()
Quindi, vai a componenti cartella nel tuo progetto Xcode e crea una nuova classe come hai fatto prima. Questa volta, dai il nome alla classe FlashingComponent e renderlo una sottoclasse di GKComponent
come mostrato di seguito.
Il componente che hai appena creato gestirà il lampeggio visivo del nostro punto blu quando viene colpito da un punto rosso e si trova nel suo stato invulnerabile. Sostituisci il contenuto di FlashingComponent.swift con il seguente:
import UIKit import SpriteKit import GameplayKit class FlashingComponent: GKComponent var nodeToFlash: SKNode! func startFlashing () let fadeAction = SKAction.sequence ([SKAction.fadeOutWithDuration (0.75), SKAction.fadeInWithDuration (0.75)]) nodeToFlash.runAction (SKAction.repeatActionForever (fadeAction), withKey: "flash") deinit nodeToFlash. removeActionForKey ("flash")
L'implementazione mantiene semplicemente un riferimento a un SKNode
oggetto e ripete dissolvenza in entrata e dissolvenza in sequenza finché il componente è attivo.
Ritornare a GameScene.swift e aggiungi il seguente codice da qualche parte all'interno del didMoveToView (_ :)
metodo:
// Aggiunta del componente let flash = FlashingComponent () flash.nodeToFlash = playerNode flash.startFlashing () playerNode.entity.addComponent (flash)
Creiamo a FlashingComponent
oggetto e impostarlo per eseguire il suo lampeggio sul punto del giocatore. L'ultima riga quindi aggiunge il componente all'entità per mantenerlo attivo ed in esecuzione.
Crea ed esegui la tua app. Ora vedrai che il tuo punto blu si dissolve lentamente verso l'interno e l'esterno ripetutamente.
Prima di proseguire, elimina il codice che hai appena aggiunto didMoveToView (_ :)
metodo. Successivamente, si aggiungerà questo codice ma solo quando il punto blu entrerà nel suo stato invulnerabile.
In GameplayKit, le macchine a stati forniscono un modo per identificare ed eseguire facilmente attività basate sulla corrente stato di un oggetto particolare. Prendendo spunto dal precedente esempio di difesa della torre, potrebbero includere alcuni stati possibili per ogni torre Attivo
, Disabilitato
, e Distrutto
. Uno dei principali vantaggi delle macchine a stati è che è possibile specificare in quali stati può spostarsi un altro stato. Con i tre stati di esempio menzionati sopra, utilizzando una macchina a stati, è possibile impostare la macchina a stati in modo che:
Disabilitato
quando Attivo
e viceversaDistrutto
quando entrambi Attivo
o Disabilitato
Attivo
o Disabilitato
una volta è stato Distrutto
Nel gioco per questo tutorial, lo terremo molto semplice e avremo solo un normale e invulnerabile stato.
Nel tuo progetto State Machine cartella, creare due nuove classi. Nominali NormalState
e InvulnerableState
rispettivamente, essendo entrambi una sottoclasse di GKState
classe.
Sostituisci il contenuto di NormalState.swift con il seguente:
import UIKit import SpriteKit import GameplayKit class NormalState: GKState var node: PlayerNode init (withNode: PlayerNode) node = withNode override func isValidNextState (stateClass: AnyClass) -> Bool switch stateClass case è InvulnerableState.Type: return true default : return false override func didEnterWithPreviousState (previousState: GKState?) if let _ = previousState as? InvulnerableState node.entity.removeComponentForClass (FlashingComponent) node.runAction (SKAction.fadeInWithDuration (0.5))
Il NormalState
la classe contiene quanto segue:
isValidNextState (_ :)
metodo. L'implementazione di questo metodo restituisce un valore booleano, che indica se la classe di stato corrente può spostarsi o meno nella classe di stato fornita dal parametro method.didEnterWithPreviousState (_ :)
metodo di callback. Nell'implementazione del metodo, controlliamo se lo stato precedente era il InvulnerableState
stato e, se è vero, rimuove il componente lampeggiante dall'entità del giocatore.Ora aperto InvulnerableState.swift e sostituire il suo contenuto con il seguente:
import UIKit import GameplayKit class InvulnerableState: GKState var node: PlayerNode init (withNode: PlayerNode) node = withNode function override isValidNextState (stateClass: AnyClass) -> Bool switch stateClass case is NormalState.Type: return true default: return false override func didEnterWithPreviousState (previousState: GKState?) if let _ = previousState as? NormalState // Aggiunta del componente let flash = FlashingComponent () flash.nodeToFlash = node flash.startFlashing () node.entity.addComponent (flash)
Il InvulnerableState
la classe è molto simile al NormalState
classe. La differenza principale è che entrando in questo stato si aggiunge il componente lampeggiante all'entità del giocatore piuttosto che rimuoverlo.
Ora che le tue lezioni di stato sono entrambe complete, aperte PlayerNode.swift di nuovo e aggiungere le seguenti righe al PlayerNode
classe:
var stateMachine: GKStateMachine! func enterNormalState () self.stateMachine.enterState (NormalState)
Questo snippet di codice aggiunge una nuova proprietà a PlayerNode
classe e implementa un metodo di convenienza per tornare allo stato normale.
Ora aperto GameScene.swift e, alla fine del didMoveToView (_ :)
metodo, aggiungere le seguenti due righe:
playerNode.stateMachine = GKStateMachine (stati: [NormalState (withNode: playerNode), InvulnerableState (withNode: playerNode)]) playerNode.stateMachine.enterState (NormalState)
In queste due righe di codice, ne creiamo una nuova GKStateMachine
con i due stati e digli di entrare nel NormalState
.
Infine, sostituire l'implementazione del handleContactWithNode (_ :)
metodo del GameScene
classe con la seguente implementazione:
func handleContactWithNode (contact: ContactNode) se contact è PointsNode NSNotificationCenter.defaultCenter (). postNotificationName ("updateScore", object: self, userInfo: ["score": 1]) else if contact è RedEnemyNode && playerNode.stateMachine. stato attuale! è NormalState NSNotificationCenter.defaultCenter (). postNotificationName ("updateScore", oggetto: self, userInfo: ["score": -2]) playerNode.stateMachine.enterState (InvulnerableState) playerNode.performSelector ("enterNormalState", withObject: nil, afterDelay: 5.0) else if contact is YellowEnemyNode && playerNode.stateMachine.currentState! è NormalState self.playerNode.enabled = false contact.removeFromParent ()
Quando il punto blu del giocatore si scontra con un punto nemico rosso, il giocatore entra nel InvulnerableState
stato per cinque secondi e poi tornare al NormalState
stato. Controlliamo anche qual è lo stato attuale del giocatore e eseguiamo solo la logica relativa al nemico se è il NormalState
stato.
Crea ed esegui l'app un'ultima volta e spostati sulla mappa finché non trovi un punto rosso. Quando ti scontri con il punto rosso, vedrai che il tuo punto blu entra nel suo stato invulnerabile e lampeggia per cinque secondi.
In questo tutorial, ti ho presentato due degli aspetti principali del framework GameplayKit, entità e componenti, e macchine di stato. Ti ho mostrato come puoi utilizzare entità e componenti per strutturare il tuo modello di gioco e mantenere tutto organizzato. L'utilizzo di componenti è un modo molto semplice per condividere la funzionalità tra gli oggetti nei tuoi giochi.
Vi ho anche mostrato le basi delle macchine di stato, compreso il modo in cui è possibile specificare quali stati possono essere sottoposti a un particolare stato e come eseguire codice quando viene inserito un particolare stato.
Rimanete sintonizzati per la seconda parte di questa serie in cui porteremo questo gioco ad un altro livello aggiungendo un po 'di intelligenza artificiale, meglio conosciuta come AI. L'IA consentirà ai punti nemici di colpire il giocatore e trovare il percorso migliore per raggiungere il giocatore.
Come sempre, se avete commenti o domande, lasciateli nei commenti qui sotto.