Questa è la terza parte di Un'introduzione a GameplayKit. Se non hai ancora affrontato la prima parte e la seconda parte, allora consiglio di leggere quei tutorial prima di continuare con questa.
In questo terzo e ultimo tutorial, ti spiegherò due ulteriori funzionalità che puoi utilizzare nei tuoi giochi:
In questo tutorial, per prima cosa utilizzeremo uno dei generatori di valori casuali di GameplayKit per ottimizzare il nostro algoritmo iniziale di deposizione dei nemici. Implementeremo quindi un sistema di regole di base in combinazione con un'altra distribuzione casuale per gestire il comportamento di respawn dei nemici.
Per questo tutorial, puoi utilizzare la tua copia del progetto completato dal secondo tutorial o scaricare una nuova copia del codice sorgente da GitHub.
I valori casuali possono essere generati in GameplayKit utilizzando qualsiasi classe conforme a GKRandom
protocollo. GameplayKit offre cinque classi conformi a questo protocollo. Queste classi contiene tre casuali fonti e due a caso distribuzioni. La principale differenza tra le fonti casuali e le distribuzioni casuali è che le distribuzioni usano una fonte casuale per produrre valori all'interno di un intervallo specifico e possono manipolare l'output del valore casuale in vari altri modi.
Le classi di cui sopra sono fornite dal framework in modo da poter trovare il giusto equilibrio tra prestazioni e casualità per il tuo gioco. Alcuni algoritmi di generazione del valore casuale sono più complessi di altri e conseguentemente influenzano le prestazioni.
Ad esempio, se hai bisogno di un numero casuale generato ogni fotogramma (sessanta volte al secondo), allora sarebbe meglio usare uno degli algoritmi più veloci. Al contrario, se si sta solo raramente generando un valore casuale, è possibile utilizzare un algoritmo più complesso per produrre risultati migliori.
Le tre classi di sorgenti casuali fornite dal framework GameplayKit sono GKARC4RandomSource
, GKLinearCongruentialRandomSource
, e GKMersenneTwisterRandomSource
.
GKARC4RandomSource
Questa classe utilizza l'algoritmo ARC4 ed è adatta per la maggior parte degli scopi. Questo algoritmo funziona producendo una serie di numeri casuali basati su un seme. È possibile inizializzare a GKARC4RandomSource
con un seme specifico se è necessario replicare un comportamento casuale da un'altra parte del gioco. Le sementi di una fonte esistente possono essere recuperate dalla sua seme
proprietà di sola lettura.
GKLinearCongruentialRandomSource
Questa classe di origine casuale utilizza l'algoritmo di generatore congruenziale lineare di base. Questo algoritmo è più efficiente e offre prestazioni migliori rispetto all'algoritmo ARC4, ma genera anche valori meno casuali. Puoi prendere un GKLinearCongruentialRandomSource
sementi dell'oggetto e crea una nuova sorgente con esso nello stesso modo di a GKARC4RandomSource
oggetto.
GKMersenneTwisterRandomSource
Questa classe usa il Mersenne Twister algoritmo e genera i risultati più casuali, ma è anche il meno efficiente. Proprio come le altre due classi di sorgenti casuali, puoi recuperare a GKMersenneTwisterRandomSource
seme dell'oggetto e usarlo per creare una nuova fonte.
Le due classi di distribuzione casuali in GameplayKit sono GKGaussianDistribution
e GKShuffledDistribution
.
GKGaussianDistribution
Questo tipo di distribuzione garantisce che i valori casuali generati seguano una distribuzione gaussiana, nota anche come distribuzione normale. Ciò significa che la maggior parte dei valori generati scenderà al centro dell'intervallo specificato.
Ad esempio, se si imposta a GKGaussianDistribution
oggetto con un valore minimo di 1, un valore massimo di 10, e una deviazione standard di 1, circa 69% dei risultati sarebbe o 4, 5, o 6. Spiegherò questa distribuzione in modo più dettagliato quando ne aggiungiamo uno al nostro gioco più avanti in questo tutorial.
GKShuffledDistribution
Questa classe può essere utilizzata per assicurarsi che i valori casuali siano distribuiti uniformemente nell'intervallo specificato. Ad esempio, se generi valori tra 1 e 10, e a 4 viene generato, un altro 4 non sarà generato fino a quando tutti gli altri numeri tra 1 e 10 sono stati anche generati.
È giunto il momento di mettere tutto questo in pratica. Stiamo per aggiungere due distribuzioni casuali al nostro gioco. Apri il tuo progetto in Xcode e vai a GameScene.swift. La prima distribuzione casuale che aggiungeremo è a GKGaussianDistribution
. Successivamente, aggiungeremo anche un GKShuffledDistribution
. Aggiungere le seguenti due proprietà al GameScene
classe.
var initialSpawnDistribution = GKGaussianDistribution (randomSource: GKARC4RandomSource (), lowestValue: 0, highestValue: 2) var respawnDistribution = GKShuffledDistribution (randomSource: GKARC4RandomSource (), lowestValue: 0, highestValue: 2)
In questo frammento, creiamo due distribuzioni con un valore minimo di 0 e un valore massimo di 2. Per il GKGaussianDistribution
, la media e la deviazione vengono calcolate automaticamente in base alle seguenti equazioni:
mean = (massimo - minimo) / 2
deviazione = (massimo - minimo) / 6
La media di una distribuzione gaussiana è il suo punto medio e la deviazione viene utilizzata per calcolare quale percentuale di valori dovrebbe essere all'interno di un determinato intervallo dalla media. La percentuale di valori all'interno di un determinato intervallo è:
Ciò significa che circa il 69% dei valori generati dovrebbe essere uguale a 1. Ciò comporterà più punti rossi in proporzione ai punti verdi e gialli. Per farlo funzionare, dobbiamo aggiornare il initialSpawn
metodo.
Nel per
loop, sostituire la seguente riga:
let respawnFactor = arc4random ()% 3 // Produce un valore compreso tra 0 e 2 (incluso)
con il seguente:
let respawnFactor = self.initialSpawnDistribution.nextInt ()
Il nextInt
il metodo può essere chiamato su qualsiasi oggetto conforme al GKRandom
protocollo e restituirà un valore casuale in base all'origine e, se applicabile, alla distribuzione che si sta utilizzando.
Crea ed esegui la tua app e spostati sulla mappa. Dovresti vedere molti più punti rossi rispetto ai punti verdi e gialli.
La seconda distribuzione casuale che useremo nel gioco entrerà in gioco quando si gestisce il comportamento di respawn basato sul sistema di regole.
I sistemi di regole GameplayKit vengono utilizzati per organizzare meglio la logica condizionale all'interno del gioco e introdurre anche la logica fuzzy. Introducendo la logica fuzzy, puoi rendere le entità all'interno del tuo gioco prendere decisioni basate su una serie di regole e variabili diverse, come la salute del giocatore, il numero di nemici attuali e la distanza dal nemico. Questo può essere molto vantaggioso rispetto al semplice Se
e interruttore
dichiarazioni.
Sistemi di regole, rappresentati dal GKRuleSystem
classe, hanno tre parti chiave per loro:
salienza
proprietà di qualsiasi regola per specificare quando si desidera che venga valutata.stato
proprietà di a GKRuleSystem
oggetto è un dizionario, a cui è possibile aggiungere qualsiasi dato, compresi i tipi di oggetto personalizzati. Questi dati possono quindi essere utilizzati dalle regole del sistema di regole quando si restituisce il risultato.Regole stesse, rappresentate dal GKRule
classe, hanno due componenti principali:
NSPredicate
oggetto o, come faremo in questo tutorial, un blocco di codice.vero
, è l'azione è eseguita. Questa azione è un blocco di codice in cui è possibile eseguire qualsiasi logica se i requisiti della regola sono stati soddisfatti. Questo è dove generalmente si asseriscono (aggiungere) o ritrarre (rimuovere) fatti all'interno del sistema di regole genitore.Vediamo come tutto questo funziona nella pratica. Per il nostro sistema di regole, creeremo tre regole che guardano:
Innanzitutto, aggiungi la seguente proprietà a GameScene
classe:
var ruleSystem = GKRuleSystem ()
Successivamente, aggiungi il seguente snippet di codice al file didMoveToView (_ :)
metodo:
let playerDistanceRule = GKRule (blockPredicate: (system: GKRuleSystem) -> Bool in se let valore = system.state ["spawnPoint"] come? NSValue let point = value.CGPointValue () lascia xDistance = abs (point.x - self.playerNode.position.x) let YDistance = abs (point.y - self.playerNode.position.y) let totalDistance = sqrt ((xDistance * xDistance) + (yDistance * yDistance)) totalDistance <= 200 return true else return false else return false ) (system: GKRuleSystem) -> Void in system.assertFact ("spawnEnemy") let nodeCountRule = GKRule (blockPredicate: (system: GKRuleSystem) -> Bool in se self.children.count <= 50 return true else return false ) (system: GKRuleSystem) -> Void in system.assertFact ("shouldSpawn", grado: 0.5) let nodePresentRule = GKRule (blockPredicate: (system: GKRuleSystem) -> Bool in se let valore = system.state ["spawnPoint"] come? NSValue dove self. nodesAtPoint (value.CGPointValue ()). count == 0 return true else return false) (system: GKRuleSystem) -> Void in let grade = system.gradeForFact ("shouldSpawn") system.assertFact (" shouldSpawn ", grade: (grade + 0.5)) self.ruleSystem.addRulesFromArray ([playerDistanceRule, nodeCountRule, nodePresentRule])
Con questo codice, ne creiamo tre GKRule
oggetti e aggiungerli al sistema di regole. Le regole asseriscono un fatto particolare all'interno del loro blocco di azioni. Se non si fornisce un valore di valutazione e si chiama semplicemente il assertFact (_ :)
metodo, come facciamo con il playerDistanceRule
, il fatto è dato un grado predefinito di 1.0.
Noterai che per il nodeCountRule
noi affermiamo solo il "ShouldSpawn"
fatto con un grado di 0.5. Il nodePresentRule
quindi asserisce questo stesso fatto e aggiunge un valore di grado di 0.5. Questo è fatto in modo che quando controlliamo il fatto in seguito, un valore di grado di 1.0 significa che entrambe le regole sono state soddisfatte.
Vedrai anche che entrambi playerDistanceRule
e nodePresentRule
accedere a "punto di spawn"
valore del sistema di regole stato
dizionario. Assegneremo questo valore prima di valutare il sistema delle regole.
Infine, trova e sostituisci il respawn
metodo nel GameScene
classe con la seguente implementazione:
func respawn () let endNode = GKGraphNode2D (point: float2 (x: 2048.0, y: 2048.0)) self.graph.connectNodeUsingObstacles (endNode) per il punto in self.spawnPoints self.ruleSystem.reset () self.ruleSystem.state ["spawnPoint"] = NSValue (CGPoint: point) self.ruleSystem.evaluate () if self.ruleSystem.gradeForFact ("shouldSpawn") == 1.0 var respawnFactor = self.respawnDistribution.nextInt () se self.ruleSystem.gradeForFact ("spawnEnemy") == 1.0 respawnFactor = self.initialSpawnDistribution.nextInt () nodo var: SKShapeNode? = nil switch respawnFactor caso 0: node = PointsNode (circleOfRadius: 25) nodo! .physicsBody = SKPhysicsBody (circleOfRadius: 25) nodo! .fillColor = UIColor.greenColor () caso 1: nodo = RedEnemyNode (circleOfRadius: 75) nodo! .physicsBody = SKPhysicsBody (circleOfRadius: 75) node! .fillColor = UIColor.redColor () caso 2: node = YellowEnemyNode (circleOfRadius: 50) nodo! .physicsBody = SKPhysicsBody (circleOfRadius: 50) nodo! .fillColor = UIColor.yellowColor ( ) default: break se let entity = node? .valueForKey ("entity") as? GKEntity, lasciare agent = node? .ValueForKey ("agent") come? GKAgent2D dove respawnFactor! = 0 entity.addComponent (agent) agent.delegate = node as? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (agent) let startNode = GKGraphNode2D (point: agent.position) self.graph.connectNodeUsingObstacles (startNode) let pathNodes = self.graph.findPathFromNode (startNode, toNode: endNode) as! [GKGraphNode2D] if! PathNodes.isEmpty let path = GKPath (graphNodes: pathNodes, raggio: 1.0) let followPath = GKGoal (toFollowPath: path, maxPredictionTime: 1.0, forward: true) let stayOnPath = GKGoal (toStayOnPath: path, maxPredictionTime: 1.0) let behavior = GKBehavior (goals: [followPath, stayOnPath]) agent.behavior = behavior self.graph.removeNodes ([startNode]) agent.mass = 0.01 agent.maxSpeed = 50 agent.maxAcceleration = 1000 nodo !. position = point node! .strokeColor = UIColor.clearColor () node! .physicsBody! .contactTestBitMask = 1 self.addChild (nodo!) self.graph.removeNodes ([endNode])
Questo metodo verrà chiamato una volta al secondo ed è molto simile al initialSpawn
metodo. Ci sono un certo numero di importanti differenze nel per
ciclo però.
reset
metodo. Questo deve essere fatto quando un sistema di regole viene valutato in sequenza. Ciò rimuove tutti i fatti fatti valere e i dati relativi per garantire che nessuna informazione sia rimasta dalla valutazione precedente che potrebbe interferire con quella successiva.stato
dizionario. Usiamo un NSValue
oggetto, perché il CGPoint
il tipo di dati non è conforme a Swift ANYOBJECT
protocollo e non può essere assegnato a questo NSMutableDictionary
proprietà.valutare
metodo."ShouldSpawn"
fatto. Se questo è uguale a 1, continuiamo a rinnovare il punto."SpawnEnemy"
fatto e, se uguale a 1, usa il generatore casuale distribuito normalmente per creare il nostro spawnFactor
.Il resto di respawn
il metodo è lo stesso di initialSpawn
metodo. Crea ed esegui il tuo gioco un'ultima volta. Anche senza muoverti, vedrai i nuovi punti spawn quando vengono soddisfatte le condizioni necessarie.
In questa serie su GameplayKit, hai imparato molto. Riassumiamo brevemente ciò che abbiamo trattato.
GameplayKit è un'aggiunta importante a iOS 9 e OS X El Capitan. Elimina molte delle complessità dello sviluppo del gioco. Spero che questa serie ti abbia motivato a sperimentare di più con il framework e scoprire di cosa è capace.
Come sempre, si prega di essere sicuri di lasciare i vostri commenti e feedback qui sotto.