Introduzione a GameplayKit parte 3

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.

introduzione

In questo terzo e ultimo tutorial, ti spiegherò due ulteriori funzionalità che puoi utilizzare nei tuoi giochi:

  • generatori di valori casuali
  • sistemi di regole

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.

1. Generatori di valori casuali

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 GKARC4RandomSourceGKLinearCongruentialRandomSource, 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 GKGaussianDistributionGKShuffledDistribution.

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 è:

  • 68,27% entro 1 deviazione dalla media
  • 95% entro 2 deviazioni dalla media
  • 100% entro 3 deviazioni dalla media

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.

2. Sistemi 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 Seinterruttore dichiarazioni.

Sistemi di regole, rappresentati dal GKRuleSystem classe, hanno tre parti chiave per loro:

  • ordine del giorno. Questo è l'insieme di regole che sono state aggiunte al sistema di regole. Per impostazione predefinita, queste regole vengono valutate nell'ordine in cui vengono aggiunte al sistema di regole. Puoi cambiare il salienza proprietà di qualsiasi regola per specificare quando si desidera che venga valutata.
  • Informazioni di stato. Il 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.
  • I fatti. I fatti all'interno di un sistema di regole rappresentano le conclusioni tratte dalla valutazione delle regole. Un fatto può anche essere rappresentato da qualsiasi tipo di oggetto all'interno del tuo gioco. Ogni fatto ha anche un corrispondente grado di appartenenza, che è un valore tra 0.0 e 1.0. Questo grado di appartenenza rappresenta l'inclusione o la presenza del fatto all'interno del sistema di regole.

Regole stesse, rappresentate dal GKRule classe, hanno due componenti principali:

  • Predicato. Questa parte della regola restituisce un valore booleano, che indica se i requisiti della regola sono stati soddisfatti o meno. Il predicato di una regola può essere creato usando un NSPredicate oggetto o, come faremo in questo tutorial, un blocco di codice.
  • Azione. Quando ritorna il predicato della regola 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:

  • la distanza dal punto di spawn al giocatore. Se questo valore è relativamente piccolo, renderemo il gioco più probabile che generino nemici rossi.
  • il numero di nodi corrente della scena. Se questo è troppo alto, non vogliamo aggiungere altri punti alla scena.
  • se è già presente un punto nel punto di spawn. Se non c'è, allora vogliamo procedere a generare un punto qui.

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 playerDistanceRulenodePresentRule 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ò.

  • Per prima cosa ripristiniamo il sistema di regole chiamando il suo 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.
  • Quindi assegniamo il punto di spawn alle regole del sistema 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à.
  • Valutiamo il sistema di regole chiamando il suo valutare metodo.
  • Quindi recuperiamo il grado di appartenenza del sistema di regole per il "ShouldSpawn" fatto. Se questo è uguale a 1, continuiamo a rinnovare il punto.
  • Infine, controlliamo il grado del sistema di regole per il "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.

Conclusione

In questa serie su GameplayKit, hai imparato molto. Riassumiamo brevemente ciò che abbiamo trattato.

  • Entità e componenti
  • Macchine di stato
  • Agenti, obiettivi e comportamenti
  • pathfinding
  • Generatori di valori casuali
  • Sistemi di regole

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.