Questa è la seconda parte di Un'introduzione a GameplayKit. Se non hai ancora affrontato la prima parte, allora consiglio di leggere quel tutorial prima di continuare con questo.
In questo tutorial, ho intenzione di insegnarti altre due funzionalità del framework GameplayKit che puoi sfruttare:
Utilizzando agenti, obiettivi e comportamenti, costruiremo alcune intelligenze artificiali di base (AI) nel gioco che abbiamo iniziato nella prima parte di questa serie. L'intelligenza artificiale consentirà ai nostri punti nemici rossi e gialli di puntare e passare verso il nostro punto giocatore blu. Stiamo anche implementando il path-finding per estendere questa IA per superare ostacoli.
Per questo tutorial, puoi utilizzare la tua copia del progetto completato dalla prima parte di questa serie o scaricare una nuova copia del codice sorgente da GitHub.
In GameplayKit, gli agenti, gli obiettivi e i comportamenti vengono utilizzati in combinazione l'uno con l'altro per definire in che modo diversi oggetti si muovono in relazione l'uno con l'altro nella scena. Per un singolo oggetto (o SKShapeNode
nel nostro gioco), si inizia creando un agente, rappresentato dal GKAgent
classe. Tuttavia, per i giochi 2D, come il nostro, abbiamo bisogno di usare il cemento GKAgent2D
classe.
Il GKAgent
la classe è una sottoclasse di GKComponent
. Ciò significa che il tuo gioco deve utilizzare una struttura basata su entità e componenti come ti ho mostrato nel primo tutorial di questa serie.
Gli agenti rappresentano la posizione, la dimensione e la velocità dell'oggetto. Quindi aggiungi un comportamento, rappresentato dal GKBehaviour
classe, a questo agente. Alla fine, crei un set di obiettivi, rappresentato dal GKGoal
classe e aggiungerli all'oggetto comportamento. Gli obiettivi possono essere utilizzati per creare diversi elementi di gioco, ad esempio:
L'oggetto del comportamento monitora e calcola tutti gli obiettivi che gli vengono aggiunti e li inoltra nuovamente all'agente. Vediamo come funziona in pratica.
Apri il tuo progetto Xcode e vai a PlayerNode.swift. Prima dobbiamo assicurarci che il PlayerNode
classe conforme al GKAgentDelegate
protocollo.
class PlayerNode: SKShapeNode, GKAgentDelegate ...
Quindi, aggiungere il seguente blocco di codice al PlayerNode
classe.
var agent = GKAgent2D () // MARK: Agent Delegate func agentWillUpdate (agent: GKAgent) se let agent2D = agent as? GKAgent2D agent2D.position = float2 (Float (position.x), Float (position.y)) func agentDidUpdate (agent: GKAgent) se let agent2D = agent as? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))
Iniziamo aggiungendo una proprietà al PlayerNode
classe in modo da avere sempre un riferimento all'oggetto agente del giocatore corrente. Successivamente, implementiamo i due metodi di GKAgentDelegate
protocollo. Implementando questi metodi, ci assicuriamo che il punto del giocatore visualizzato sullo schermo rispecchi sempre le modifiche apportate da GameplayKit.
Il agentWillUpdate (_ :)
il metodo è chiamato appena prima che GameplayKit guardi attraverso il comportamento e gli obiettivi di quell'agente per determinare dove dovrebbe muoversi. Allo stesso modo, il agentDidUpdate (_ :)
il metodo viene chiamato subito dopo che GameplayKit ha completato questo processo.
La nostra implementazione di questi due metodi assicura che il nodo che vediamo sullo schermo rifletta le modifiche apportate da GameplayKit e che GameplayKit utilizzi l'ultima posizione del nodo quando esegue i suoi calcoli.
Avanti, aperto ContactNode.swift e sostituire il contenuto del file con la seguente implementazione:
import UIKit import SpriteKit import GameplayKit class ContactNode: SKShapeNode, GKAgentDelegate var agent = GKAgent2D () // MARK: Agent Delegate func agentWillUpdate (agent: GKAgent) if let agent2D = agent as? GKAgent2D agent2D.position = float2 (Float (position.x), Float (position.y)) func agentDidUpdate (agent: GKAgent) se let agent2D = agent as? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))
Implementando il GKAgentDelegate
protocollo nel ContactNode
classe, permettiamo che tutti gli altri punti del nostro gioco siano aggiornati con GameplayKit così come il nostro punto giocatore.
È giunto il momento di impostare i comportamenti e gli obiettivi. Per farlo funzionare, dobbiamo occuparci di tre cose:
In primo luogo, aperto GameScene.swift e, alla fine del didMoveToView (_ :)
metodo, aggiungi le seguenti due righe di codice:
playerNode.entity.addComponent (playerNode.agent) playerNode.agent.delegate = playerNode
Con queste due righe di codice, aggiungiamo l'agente come componente e impostiamo il delegato dell'agente come il nodo stesso.
Quindi, sostituire l'implementazione di initialSpawn
metodo con la seguente implementazione:
func initialSpawn () per point in self.spawnPoints let respawnFactor = arc4random ()% 3 // Produce un valore compreso tra 0 e 2 (incluso) var node: 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 behavior = GKBehavior (obiettivo: GKGoal (toSeekAgent: playerNode.agent), peso: 1,0 ) agent.behavior = comportamento agent.mass = 0.01 agent.maxSpeed = 50 agent.maxAcceleration = 1000 nodo! .position = point node! .strokeColor = UIColor.clearColor () nodo! .physicsBody! .contactTestBitMask = 1 self.addChild (nodo!)
Il codice più importante che abbiamo aggiunto si trova nel Se
affermazione che segue il interruttore
dichiarazione. Passiamo attraverso questo codice riga per riga:
agenti
. Aggiungeremo questa proprietà al GameScene
classe in un momento.GKBehavior
oggetto con un singolo GKGoal
prendere di mira l'agente del giocatore attuale. Il peso
il parametro in questo inizializzatore viene utilizzato per determinare quali obiettivi devono avere la precedenza sugli altri. Ad esempio, immagina di avere un obiettivo di indirizzare un determinato agente e un obiettivo di allontanarti da un altro agente, ma desideri che l'obiettivo di targeting abbia la priorità. In questo caso, potresti dare un peso al goal di targeting 1
e l'allontanamento obiettivo un peso di 0.5
. Questo comportamento viene quindi assegnato all'agente del nodo nemico.massa
, massima velocità
, e MaxAcceleration
proprietà dell'agente. Questi influenzano la velocità con cui gli oggetti possono muoversi e girare. Sentiti libero di giocare con questi valori e vedere come influenza il movimento dei punti nemici.Quindi, aggiungere le seguenti due proprietà a GameScene
classe:
var agent: [GKAgent2D] = [] var lastUpdateTime: CFTimeInterval = 0.0
Il agenti
l'array verrà usato per mantenere un riferimento agli agenti nemici nella scena. Il LastUpdateTime
la proprietà verrà utilizzata per calcolare il tempo trascorso dall'ultimo aggiornamento della scena.
Infine, sostituire l'implementazione del aggiornare(_:)
metodo del GameScene
classe con la seguente implementazione:
override func update (currentTime: CFTimeInterval) / * Chiamato prima che ogni frame sia reso * / self.camera?.position = playerNode.position se self.lastUpdateTime == 0 lastUpdateTime = currentTime lascia delta = currentTime - lastUpdateTime lastUpdateTime = currentTime playerNode.agent.updateWithDeltaTime (delta) per l'agente negli agenti agent.updateWithDeltaTime (delta)
Nel aggiornare(_:)
metodo, calcoliamo il tempo trascorso dall'ultimo aggiornamento di scena e quindi aggiorniamo gli agenti con quel valore.
Crea ed esegui la tua app e inizia a muoverti nella scena. Vedrai che i punti nemici inizieranno lentamente a muoversi verso di te.
Come puoi vedere, mentre i punti nemici colpiscono il giocatore corrente, non navigano intorno alle barriere bianche, invece cercano di spostarsi attraverso di loro. Rendiamo i nemici un po 'più intelligenti con il path-finding.
Con il framework GameplayKit, puoi aggiungere complessi path-finding al tuo gioco combinando corpi fisici con classi e metodi GameplayKit. Per il nostro gioco, lo sistemeremo in modo che i punti nemici puntino al punto del giocatore e allo stesso tempo navighino attorno agli ostacoli.
Pathfinding in GameplayKit inizia con la creazione di a grafico della tua scena. Questo grafico è una raccolta di singole posizioni, anche denominate nodi, e collegamenti tra queste posizioni. Queste connessioni definiscono come un particolare oggetto può spostarsi da una posizione all'altra. Un grafico può modellare i percorsi disponibili nella scena in tre modi:
GKObstacleGraph
la classe è usata per il grafico, il GKPolygonObstacle
classe per gli ostacoli, e il GKGraphNode2D
classe per nodi (posizioni).GKGridGraph
la classe è usata per il grafico e il GKGridGraphNode
classe per i nodi.GKGraph
la classe è usata per il grafico e il GKGraphNode
classe per i nodi.Perché vogliamo che il giocatore punti nel nostro gioco per navigare attorno alle barriere bianche, useremo il GKObstacleGraph
classe per creare un grafico della nostra scena. Per iniziare, sostituire il spawnPoints
proprietà nel GameScene
classe con il seguente:
let spawnPoints = [CGPoint (x: 245, y: 3900), CGPoint (x: 700, y: 3500), CGPoint (x: 1250, y: 1500), CGPoint (x: 1200, y: 1950), CGPoint ( x: 1200, y: 2450), CGPoint (x: 1200, y: 2950), CGPoint (x: 1200, y: 3400), CGPoint (x: 2550, y: 2350), CGPoint (x: 2500, y: 3100), CGPoint (x: 3000, y: 2400), CGPoint (x: 2048, y: 2400), CGPoint (x: 2200, y: 2200)] var grafico: GKObstacleGraph!
Il spawnPoints
la matrice contiene alcune posizioni di spawn modificate ai fini di questo tutorial. Questo perché attualmente GameplayKit può solo calcolare percorsi tra oggetti relativamente vicini tra loro.
A causa della grande distanza predefinita tra i punti in questo gioco, è necessario aggiungere un paio di nuovi punti di spawn per illustrare il percorso. Nota che dichiariamo anche a grafico
proprietà di tipo GKObstacleGraph
per mantenere un riferimento al grafico che creeremo.
Quindi, aggiungere le seguenti due righe di codice all'inizio del didMoveToView (_ :)
metodo:
lascia ostacoli = SKNode.obstaclesFromNodePhysicsBodies (auto.children) graph = GKObstacleGraph (obstacles: obstacles, bufferRadius: 0.0)
Nella prima riga, creiamo una serie di ostacoli dai corpi fisici nella scena. Quindi creiamo l'oggetto grafico usando questi ostacoli. Il bufferRadius
il parametro in questo inizializzatore può essere utilizzato per forzare gli oggetti a non rientrare in una certa distanza da questi ostacoli. Queste linee devono essere aggiunte all'inizio del didMoveToView (_ :)
metodo, perché il grafico che creiamo è necessario dal momento in cui il initialSpawn
il metodo è chiamato.
Infine, sostituire il initialSpawn
metodo con la seguente implementazione:
func initialSpawn () let endNode = GKGraphNode2D (point: float2 (x: 2048.0, y: 2048.0)) self.graph.connectNodeUsingObstacles (endNode) per il punto in self.spawnPoints let respawnFactor = arc4random ()% 3 // Produrrà un valore compreso tra 0 e 2 (incluso) 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 behavior = GKBehavior (obiettivo: GKGoal (toSeekAgent: playerNode.agent), peso : 1.0) agent.behavior = behavior * / / *** BEGIN PATHFINDING *** / let startNode = GKGraphNode2D (point: agent.position) self.graph.connectNodeUsingObstacles (startNode) lascia pathNodes = self.graph.findPathFromNode (startNode, toNode: endNode) come! [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 = comportamento self.graph.removeNodes ([startNode]) / *** END PATHFINDING *** / agent.mass = 0.01 agent.maxSpeed = 50 agent.maxAcceleration = 1000 nodo! .Position = point node! .StrokeColor = UIColor.clearColor () nodo! .PhysicsBody! .ContactTestBitMask = 1 self.addChild (nodo!) Self.graph.removeNodes ([endNode])
Iniziamo il metodo creando un GKGraphNode2D
oggetto con le coordinate di spawn del giocatore di default. Successivamente, colleghiamo questo nodo al grafico in modo che possa essere utilizzato per trovare i percorsi.
La maggior parte della initialSpawn
il metodo rimane invariato. Ho aggiunto alcuni commenti per mostrarti dove si trova la porzione del path code del codice nel primo Se
dichiarazione. Passiamo attraverso questo codice passo dopo passo:
GKGraphNode2D
istanza e collegarlo al grafico.findPathFromNode (_: toNode :)
metodo sul nostro grafico.raggio
parametro funziona in modo simile al bufferRadius
parametro precedente e definisce quanto un oggetto può allontanarsi dal percorso creato.GKGoal
oggetti, uno per seguire il percorso e un altro per rimanere sul sentiero. Il maxPredictionTime
parametro consente all'obiettivo di calcolare al meglio in anticipo se qualcosa interrompe l'oggetto dal seguire / rimanere su quel particolare percorso.Noterai anche che rimuoviamo i nodi che creiamo dal grafico una volta che abbiamo finito con loro. Questa è una buona pratica da seguire in quanto garantisce che i nodi che hai creato non interferiscano con altri calcoli di identificazione dei percorsi più tardi.
Costruisci ed esegui la tua app un'ultima volta e vedrai due punti spawn molto vicini a te e inizi a muoverti verso di te. Potrebbe essere necessario eseguire il gioco più volte se entrambi generano punti verdi.
Importante!
In questo tutorial, abbiamo utilizzato la funzionalità di ricerca del percorso di GameplayKit per consentire ai punti nemici di colpire il punto del giocatore attorno agli ostacoli. Si noti che questo era solo per un esempio pratico di path-finding.
Per un gioco di produzione reale, sarebbe meglio implementare questa funzionalità combinando l'obiettivo di targeting del giocatore di prima in questo tutorial con un obiettivo di evitare l'ostacolo creato con il init (toAvoidObstacles: maxPredictionTime :)
metodo di convenienza, che puoi leggere di più in GKGoal
Riferimento di classe.
In questo tutorial, ti ho mostrato come puoi utilizzare agenti, obiettivi e comportamenti nei giochi che hanno una struttura di entità-componente. Mentre abbiamo creato solo tre obiettivi in questo tutorial, ce ne sono molti altri disponibili, di cui puoi leggere di più su GKGoal
Riferimento di classe.
Ti ho anche mostrato come implementare alcuni pathfinder avanzati nel tuo gioco creando un grafico, una serie di ostacoli e obiettivi per seguire questi percorsi.
Come puoi vedere, c'è una grande quantità di funzionalità messe a tua disposizione attraverso il framework GameplayKit. Nella terza e ultima parte di questa serie, ti insegnerò i generatori di valori casuali di GameplayKit e come creare il tuo sistema di regole per introdurre qualche logica fuzzy nel tuo gioco.
Come sempre, si prega di essere sicuri di lasciare i vostri commenti e feedback qui sotto.