Dette er den andre delen av En introduksjon til GameplayKit. Hvis du ikke har gått gjennom den første delen, anbefaler jeg at du leser den første opplæringen før du fortsetter med denne.
I denne opplæringen skal jeg lære deg om to funksjoner i GameplayKit-rammen du kan dra nytte av:
Ved å utnytte agenter, mål og oppførsel skal vi bygge i noen grunnleggende kunstig intelligens (AI) i spillet som vi startet i første del av denne serien. AI vil gjøre det mulig for våre røde og gule fiendens prikker å målrette mot og flytte til vår blåspiller prikk. Vi skal også implementere pathfinding for å utvide på denne AI for å navigere rundt hindringer.
For denne opplæringen kan du bruke kopien av det gjennomførte prosjektet fra første del av denne serien eller laste ned en ny kopi av kildekoden fra GitHub.
I GameplayKit brukes agenter, mål og oppførsel i kombinasjon med hverandre for å definere hvordan ulike objekter beveger seg i forhold til hverandre i hele scenen. For et enkelt objekt (eller SKShapeNode
i vårt spill), begynner du med å skape en middel, representert av GKAgent
klasse. Men for 2D-spill, som vår, må vi bruke betongen GKAgent2D
klasse.
De GKAgent
klassen er en underklasse av GKComponent
. Dette betyr at spillet ditt må bruke en enhet- og komponentbasert struktur som jeg viste deg i den første opplæringen i denne serien.
Agenter representerer et objekts posisjon, størrelse og hastighet. Deretter legger du til en oppførsel, representert av GKBehaviour
klasse, til denne agenten. Til slutt lager du et sett med mål, representert av GKGoal
klassen, og legg dem til oppførselsobjektet. Mål kan brukes til å lage mange forskjellige spillelementer, for eksempel:
Din oppførselsobjekt overvåker og beregner alle målene du legger til i det, og relayer disse dataene tilbake til agenten. La oss se hvordan dette fungerer i praksis.
Åpne ditt Xcode-prosjekt og naviger til PlayerNode.swift. Vi må først sørge for at PlayerNode
klassen stemmer overens med GKAgentDelegate
protokollen.
klasse PlayerNode: SKShapeNode, GKAgentDelegate ...
Deretter legger du til følgende kodeblokk til PlayerNode
klasse.
var agent = GKAgent2D () // MARK: Agent Delegat func agentWillUpdate (agent: GKAgent) hvis la agent2D = agent som? GKAgent2D agent2D.position = float2 (Float (posisjon.x), Float (position.y)) func agentDidUpdate (agent: GKAgent) hvis la agent2D = agent som? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))
Vi starter med å legge til en eiendom til PlayerNode
klasse slik at vi alltid har en referanse til nåværende spillers agentobjekt. Deretter implementerer vi de to metodene til GKAgentDelegate
protokoll. Ved å implementere disse metodene sikrer vi at spillerens prikk som vises på skjermen, alltid vil speile de endringene som GameplayKit gjør.
De agentWillUpdate (_ :)
Metoden kalles like før GameplayKit ser gjennom atferdens oppførsel og mål for å avgjøre hvor den skal bevege seg. På samme måte, agentDidUpdate (_ :)
Metoden kalles rett etter at GameplayKit har fullført denne prosessen.
Implementeringen av disse to metodene sikrer at noden vi ser på skjermen, gjenspeiler endringene GameplayKit gjør, og at GameplayKit bruker den siste posisjonen til noden når den utfører sine beregninger.
Deretter åpne ContactNode.swift og erstatt filens innhold med følgende implementering:
import UIKit import SpriteKit import GameplayKit klasse KontaktNode: SKShapeNode, GKAgentDelegate var agent = GKAgent2D () // MARK: Agent Delegat func agentWillUpdate (agent: GKAgent) hvis la agent2D = agent som? GKAgent2D agent2D.position = float2 (Float (posisjon.x), Float (position.y)) func agentDidUpdate (agent: GKAgent) hvis la agent2D = agent som? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))
Ved å implementere GKAgentDelegate
protokoll i ContactNode
klassen, tillater vi at alle de andre punktene i spillet vårt er oppdatert med GameplayKit, så vel som vår spillerpotte.
Det er nå på tide å sette opp atferd og mål. For å gjøre dette arbeidet må vi ta vare på tre ting:
For det første, åpne GameScene.swift og på slutten av didMoveToView (_ :)
metode, legg til følgende to linjer med kode:
playerNode.entity.addComponent (playerNode.agent) playerNode.agent.delegate = playerNode
Med disse to kodelinjene legger vi agenten til som en komponent og setter agentens delegat til å være selve noden.
Neste, erstatt implementeringen av initialSpawn
metode med følgende implementering:
func initialSpawn () for punkt i self.spawnPoints la respawnFactor = arc4random ()% 3 // Vil produsere en verdi mellom 0 og 2 (inkluderende) var node: SKShapeNode? = null bryter respawnFactor case 0: node = PointsNode (circleOfRadius: 25) node! .physicsBody = SKPhysicsBody (circleOfRadius: 25) node! .fillColor = UIColor.greenColor () tilfelle 1: node = RedEnemyNode (circleOfRadius: 75) node! .physicsBody = SKPhysicsBody (circleOfRadius: 75) node! .fillColor = UIColor.redColor () tilfelle 2: node = YellowEnemyNode (circleOfRadius: 50) node! .physicsBody = SKPhysicsBody (circleOfRadius: 50) node! .fillColor = UIColor.yellowColor ) standard: break hvis la enheten = node? .valueForKey ("entity") som? GKEntity, la agent = node? .ValueForKey ("agent") som? GKAgent2D hvor respawnFactor! = 0 entity.addComponent (agent) agent.delegate = node som? ContactNode agent.position = float2 (x: Float (punkt.x), y: Float (point.y)) agents.append (agent) la oppførsel = GKBehavior (mål: GKGoal (toSeekAgent: playerNode.agent), vekt: 1,0 ) agent.behavior = oppførsel agent.mass = 0.01 agent.maxSpeed = 50 agent.maxAcceleration = 1000 node! .position = punktnode! .strokeColor = UIColor.clearColor () node! .physicsBody! .contactTestBitMask = 1 self.addChild (node!)
Den viktigste koden vi har lagt til er plassert i hvis
uttalelse som følger bytte om
uttalelse. La oss gå gjennom denne kodelinjen etter linje:
agenter
. Vi legger til denne egenskapen i GameScene
i et øyeblikk.GKBehavior
objekt med en enkelt GKGoal
å målrette mot aktørens nåværende agent. De vekt
parameter i denne initialisatoren brukes til å bestemme hvilke mål som skal ha forrang over andre. For eksempel, tenk at du har et mål å målrette mot en bestemt agent og et mål å flytte bort fra en annen agent, men du vil at målrettingsmålet skal foretrekkes. I dette tilfellet kan du gi målmål målet en vekt på 1
og flytting bort målet en vekt på 0.5
. Denne oppførselen er da tilordnet til fiendens nodes agent.masse
, maksimal hastighet
, og maxAcceleration
egenskaper av agenten. Disse påvirker hvor raskt objektene kan bevege seg og snu. Føl deg fri til å leke med disse verdiene og se hvordan det påvirker bevegelsen av fiendens prikker.Deretter legger du til følgende to egenskaper til GameScene
klasse:
var agenter: [GKAgent2D] = [] var lastUpdateTime: CFTimeInterval = 0.0
De agenter
array vil bli brukt til å holde en referanse til fiendens agenter i scenen. De lastUpdateTime
Egenskapen vil bli brukt til å beregne tiden som har gått siden scenen ble sist oppdatert.
Endelig, erstatt implementeringen av Oppdater(_:)
metode av GameScene
klasse med følgende implementering:
overstyr func-oppdatering (currentTime: CFTimeInterval) / * Kalt før hver ramme gjengis * / self.camera?.position = playerNode.position hvis self.lastUpdateTime == 0 lastUpdateTime = currentTime la delta = currentTime - lastUpdateTime lastUpdateTime = currentTime playerNode.agent.updateWithDeltaTime (delta) for agent i agenter agent.updateWithDeltaTime (delta)
I Oppdater(_:)
metode beregner vi tiden som har gått siden siste sceneoppdatering og oppdaterer agenter med den verdien.
Bygg og kjør appen din, og begynn å flytte rundt på scenen. Du vil se at fiendens prikker vil sakte begynne å bevege seg mot deg.
Som du kan se, mens fiendens prikker målretter mot den nåværende spilleren, navigerer de ikke rundt de hvite barrierer, i stedet prøver de å bevege seg gjennom dem. La oss gjøre fiender litt smartere med å finne veien.
Med GameplayKit-rammen kan du legge til komplisert pathfinding til spillet ditt ved å kombinere fysikklegemer med GameplayKit klasser og metoder. For vårt spill skal vi sette det opp slik at fiendens prikker vil målrette spillerens prikk og samtidig navigere rundt hindringer.
Pathfinding i GameplayKit begynner med å skape en kurve av scenen din. Denne grafen er en samling av individuelle steder, også referert til som noder, og forbindelser mellom disse stedene. Disse forbindelsene definerer hvordan en bestemt gjenstand kan bevege seg fra ett sted til et annet. En graf kan modellere de tilgjengelige banene i scenen din på en av tre måter:
GKObstacleGraph
klassen brukes til grafen, den GKPolygonObstacle
klasse for hindringer, og GKGraphNode2D
klasse for noder (steder).GKGridGraph
klassen brukes til grafen og GKGridGraphNode
klasse for noder.GKGraph
klassen brukes til grafen og GKGraphNode
klasse for noder.Fordi vi vil at spilleren prikken i spillet vårt for å navigere rundt de hvite barrierene, skal vi bruke GKObstacleGraph
klassen for å lage en graf av vår scene. For å begynne, erstatt spawnPoints
eiendom i GameScene
klasse med følgende:
la 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: 3400), CGPoint (x: 1200, y: 2950), CGPoint (x: 1200, y: 3400), CGPoint (x: 2550, 3100), CGPoint (x: 3000, y: 2400), CGPoint (x: 2048, y: 2400), CGPoint (x: 2200, y: 2200)] var graf: GKObstacleGraph!
De spawnPoints
array inneholder noen endrede gyte steder i forbindelse med denne opplæringen. Dette skyldes at GameplayKit for øyeblikket bare kan beregne stier mellom objekter som er relativt nær hverandre.
På grunn av den store standardavstanden mellom prikker i dette spillet må et par nye gytepunkter legges til for å illustrere pathfinding. Legg merke til at vi også erklære en kurve
type eiendom GKObstacleGraph
for å holde en referanse til grafen vi skal opprette.
Deretter legger du til følgende to kodelinjer ved starten av didMoveToView (_ :)
metode:
la hindringer = SKNode.obstaclesFromNodePhysicsBodies (self.children) graph = GKObstacleGraph (hindringer: hindringer, bufferRadius: 0.0)
I første linje lager vi en rekke hindringer fra fysikkorganene i scenen. Vi lager deretter grafobjektet ved hjelp av disse hindringene. De bufferRadius
parameter i denne initialisatoren kan brukes til å tvinge objekter til å ikke komme innenfor en viss avstand fra disse hindringene. Disse linjene må legges til ved starten av didMoveToView (_ :)
metode, fordi grafen vi lager er nødvendig for tiden den initialSpawn
Metoden kalles.
Endelig, erstatt initialSpawn
metode med følgende implementering:
func initialSpawn () la endNode = GKGraphNode2D (punkt: float2 (x: 2048.0, y: 2048.0)) self.graph.connectNodeUsingObstacles (endNode) for punkt i self.spawnPoints la respawnFactor = arc4random ()% 3 // Vil produsere en verdi mellom 0 og 2 (inkluderende) var node: SKShapeNode? = null bryter respawnFactor case 0: node = PointsNode (circleOfRadius: 25) node! .physicsBody = SKPhysicsBody (circleOfRadius: 25) node! .fillColor = UIColor.greenColor () tilfelle 1: node = RedEnemyNode (circleOfRadius: 75) node! .physicsBody = SKPhysicsBody (circleOfRadius: 75) node! .fillColor = UIColor.redColor () tilfelle 2: node = YellowEnemyNode (circleOfRadius: 50) node! .physicsBody = SKPhysicsBody (circleOfRadius: 50) node! .fillColor = UIColor.yellowColor ) standard: break hvis la enheten = node? .valueForKey ("entity") som? GKEntity, la agent = node? .ValueForKey ("agent") som? GKAgent2D hvor respawnFactor! = 0 entity.addComponent (agent) agent.delegate = node som? ContactNode agent.position = float2 (x: Float (punkt.x), y: Float (point.y)) agents.append (agent) / * la oppførsel = GKBehavior (mål: GKGoal (toSeekAgent: playerNode.agent), vekt : 1.0) agent.behavior = oppførsel * / / *** BEGIN PATHFINDING *** / la startNode = GKGraphNode2D (punkt: agent.position) self.graph.connectNodeUsingObstacles (startNode) la pathNodes = self.graph.findPathFromNode (startNode, toNode: endNode) som! [GKGraphNode2D] hvis! PathNodes.isEmpty let path = GKPath (graphNodes: pathNodes, radius: 1.0) la følgePath = GKGoal (toFollowPath: path, maxPredictionTime: 1.0, forward: true) la stayOnPath = GKGoal (toStayOnPath: path, maxPredictionTime: 1.0) la oppførsel = GKBehavior (mål: [followPath, stayOnPath]) agent.behavior = oppførsel self.graph.removeNodes ([startNode]) / *** END PATHFINDING *** / agent.mass = 0.01 agent.maxSpeed = 50 agent. Maxx = acceleration
Vi starter metoden ved å lage en GKGraphNode2D
objekt med standard spilleren gyte koordinater. Deretter kobler vi denne noden til grafen slik at den kan brukes når vi finner stier.
Mesteparten av initialSpawn
Metoden forblir uendret. Jeg har lagt til noen kommentarer for å vise deg hvor passorddelen av koden er plassert i den første hvis
uttalelse. La oss gå gjennom denne koden trinn for trinn:
GKGraphNode2D
forekomst og koble dette til grafen.findPathFromNode (_: toNode :)
metode på grafen vår.radius
parameteren fungerer i likhet med bufferRadius
parameter fra før og definerer hvor mye en gjenstand kan bevege seg bort fra den opprettede banen.GKGoal
objekter, en for å følge stien og en annen for å holde seg på banen. De maxPredictionTime
parameteren tillater målet å beregne så godt det kan forut for tiden om noe skal forstyrre objektet fra å følge / forbli på den aktuelle banen.Du vil også legge merke til at vi fjerner noder vi lager fra grafen når vi er ferdige med dem. Dette er en god praksis å følge da det sikrer at knutepunktene du har opprettet, ikke forstyrrer andre beregninger av veiviser senere.
Bygg og kjør appen din en gang, og du vil se to prikker gyte deg veldig nær deg og begynne å bevege seg mot deg. Du må kanskje kjøre spillet flere ganger hvis de begge gyter som grønne prikker.
Viktig!
I denne opplæringen brukte vi GameplayKit's pathfinding-funksjon for å aktivere fiendens prikker for å målrette spilleren dot rundt hindringer. Vær oppmerksom på at dette bare var et praktisk eksempel på opplæring.
For et faktisk produksjonsspill, ville det være best å implementere denne funksjonaliteten ved å kombinere målrettet mål for mål fra tidligere i denne opplæringen med et hinder-unngått mål opprettet med init (toAvoidObstacles: maxPredictionTime :)
bekvemmelighetsmetode, som du kan lese mer om i GKGoal
Klasse referanse.
I denne veiledningen viste jeg deg hvordan du kan bruke agenter, mål og oppførsel i spill som har en enhetskomponentstruktur. Mens vi bare opprettet tre mål i denne opplæringen, er det mange flere tilgjengelige for deg, som du kan lese mer om i GKGoal
Klasse referanse.
Jeg viste deg også hvordan du implementerer noen avanserte pathfinding i spillet ditt ved å lage en graf, et sett med hindringer og mål å følge disse banene.
Som du kan se, er det en stor mengde funksjonalitet som er tilgjengelig for deg gjennom GameplayKit-rammen. I den tredje og siste delen av denne serien vil jeg lære deg om GameplayKits tilfeldige verdi generatorer og hvordan du lager ditt eget regelsystem for å introdusere litt fuzzy logikk inn i spillet ditt.
Som alltid, vær så snill å legge igjen dine kommentarer og tilbakemelding nedenfor.