En introduksjon til GameplayKit: Part 2

An Introduksjon til GameplayKit: Part 2
23
Del
6
Del
Dette Cyber ​​mandag Envato Tuts + kurs vil bli redusert til bare $ 3. Ikke gå glipp av
Dette innlegget er en del av en serie som heter An Introduction to GameplayKit.An Introduksjon til GameplayKit. Part 1AN Introduksjon til GameplayKit: Del 3

Dette er den andre delen av An Introduction to GameplayKit. Hvis du ennå ikke har gått gjennom den første delen, så anbefaler jeg å lese at opplæringen først før du fortsetter med denne.

Innledning

I denne opplæringen, jeg kommer til å lære deg om to- flere funksjoner i GameplayKit rammeverket du kan dra nytte av:

midler, mål og atferd

pathfinding

Ved å benytte midler, mål, og atferd, har vi tenkt å bygge i noen grunnleggende kunstig intelligens (AI) i spillet som vi startet i første del av denne serien. AI vil gjøre våre røde og gule fiendtlige prikker å målrette og bevege seg mot vår blå spiller prikk. Vi kommer også til å gjennomføre pathfinding å forlenge denne AI å navigere rundt hindringer.

For denne opplæringen, kan du bruke ditt eksemplar av det ferdige prosjektet fra første del av denne serien eller laste ned en ny kopi av den kildekoden fra GitHub.

1. Agenter, mål, og Behaviors

I GameplayKit, agenter, mål og atferd brukes i kombinasjon med hverandre for å definere hvordan ulike objekter beveger seg i forhold til hverandre gjennom hele scenen. For et enkelt objekt (eller SKShapeNode i spillet vårt), begynner du ved å opprette en agent, representert ved GKAgent klassen. Men for 2D-spill, som vårt, må vi bruke betongen GKAgent2D klassen.

GKAgent klasse er en underklasse av GKComponent. Dette betyr at spillet ditt må bruke en virksomheten som helhet og komponentbasert struktur som jeg viste deg i første opplæringen av denne serien.

Agenter representere et objekts posisjon, størrelse og hastighet. Du kan deretter legge til en atferd, representert ved GKBehaviour klasse, til denne agenten. Endelig legger du inn et sett av mål, representert ved GKGoal klasse, og legge dem til atferden objektet. Mål kan brukes til å lage mange forskjellige spillelementer, for eksempel:

går mot en agent

beveger seg bort fra en agent

gruppering tett sammen med andre midler

vandrende rundt en bestemt posisjon

Dine atferd objekt overvåker og beregner alle de målene som du legger til det og deretter reléer disse dataene tilbake til agenten. La oss se hvordan dette fungerer i praksis.

Åpne Xcode prosjekt og naviger til PlayerNode.swift. Må vi først sørge for at PlayerNode klasse i samsvar med GKAgentDelegate protokollen
klasse PlayerNode. SKShapeNode, GKAgentDelegate {...

Deretter legger du til følgende kode blokk til PlayerNode klassen
var. middel = GKAgent2D () //MARK: Agent Delegatefunc agentWillUpdate (agent: GKAgent) {if la agent2D = middel som? GKAgent2D {agent2D.position = float2 (Float (position.x), Float (position.y))}} func agentDidUpdate (agent: GKAgent) {if la agent2D = middel som? GKAgent2D {self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))}}

Vi starter ved å legge en eiendom til PlayerNode klassen slik at vi alltid har en referanse til den aktuelle spilleren agent objekt. Neste, vi implementere de to metodene i GKAgentDelegate protokollen. Ved å implementere disse metodene, sørger vi for at spilleren prikk vises på skjermen vil alltid speile de endringene som GameplayKit gjør.

agentWillUpdate (_ :) metoden kalles like før GameplayKit ser gjennom atferd og målene for denne agenten å bestemme hvor den skal bevege seg. Likeledes agentDidUpdate (_ :) metoden kalles rett etter GameplayKit har fullført denne prosessen.

gjennomføring av disse to metodene gjør at noden vi ser på skjermen viser endringene GameplayKit gjør, og at GameplayKit benytter den siste posisjonen til noden når de utfører sine beregninger

Deretter åpner ContactNode.swift og erstatte filens innhold med følgende gjennomføringen.
import UIKitimport SpriteKitimport GameplayKitclass ContactNode: SKShapeNode, GKAgentDelegate {var agenten = GKAgent2D () //MARK: Agent delegat func agentWillUpdate (agent: GKAgent) {if la agent2D = middel som? GKAgent2D {agent2D.position = float2 (Float (position.x), Float (position.y))}} func agentDidUpdate (agent: GKAgent) {if la agent2D = middel som? GKAgent2D {self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))}}}

Ved å implementere GKAgentDelegate protokoll i ContactNode klassen, tillater vi for alle av de andre punktene i spillet vårt til å være oppdatert med GameplayKit samt vår spiller prikk.

Det er nå på tide å sette opp atferd og mål. For å gjøre dette arbeidet, må vi ta vare på tre ting:

Legg til spillerens node agent til sin enhet og sette sin delegat

Konfigurer agenter, atferd, og mål for. alle våre fiendtlige prikker.

Oppdater alle disse agentene til riktig tid.

For det første, åpne GameScene.swift, og på slutten av didMoveToView (_ :) metode , legge til følgende to linjer med kode:
playerNode.entity.addComponent (playerNode.agent) playerNode.agent.delegate = playerNode

Med disse to linjer med kode, legger vi agenten som en komponent og sette agentens delegere til å være den noden selv

Deretter erstatte gjennomføringen av initialSpawn metoden med følgende gjennomføringen.
func initialSpawn () {for punkt i self.spawnPoints {la respawnFactor = arc4random ()% 3 //Vil produsere en verdi mellom 0 og 2 (inkluderende) Var node: SKShapeNode? = Nil slå respawnFactor {case 0: node = PointsNode (circleOfRadius: 25)! Node .physicsBody = SKPhysicsBody (circleOfRadius: 25) node .fillColor = UIColor.greenColor () case 1: node = RedEnemyNode (circleOfRadius: 75) node! .physicsBody = SKPhysicsBody (circleOfRadius: 75)! node .fillColor = UIColor.redColor () case 2: node = YellowEnemyNode (circleOfRadius: 50)! node .physicsBody = SKPhysicsBody (circleOfRadius: 50) node .fillColor = UIColor.yellowColor (! ) standard: break} hvis la enhet = node .valueForKey ("enhet") som? GKEntity, la agenten = node? .valueForKey ("Agent") som? GKAgent2D hvor respawnFactor! = 0 {entity.addComponent (agent) agent.delegate = node som? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (agent) la atferd = GKBehavior (mål: GKGoal (toSeekAgent: playerNode.agent), vekt: 1,0 ) agent.behavior = atferd agent.mass = 0.01 agent.maxSpeed ​​= 50 agent.maxAcceleration = tusen} .strokeColor = UIColor.clearColor () node node! .position = punktet node! .physicsBody! .contactTestBitMask = 1 self.addChild (node!)}}

Det viktigste koden som vi har lagt ligger i hvis setningen som følger bryter uttalelse. La oss gå gjennom denne koden linje for linje.

  • Vi først legger agenten til foretaket som en komponent og konfigurere sin delegat

    Neste, vi tildele agentens posisjon og legger agenten til en lagret array, agenter. Vi vil legge til denne eiendom til GameScene klassen i et øyeblikk.
  • Vi deretter opprette en GKBehavior objekt med et enkelt GKGoal å målrette den aktuelle spillerens agent. Vekten parameter i denne initializer brukes til å bestemme hvilke mål bør gå foran andre. Tenk deg for eksempel at du har et mål å målrette en bestemt middel og et mål å bevege seg bort fra en annen agent, men du vil at målretting mål å ta preferanse. I dette tilfellet, kan man gi den måls mål en vekt på 1, og beveger seg bort mål en vekt på 0,5. Denne oppførselen er deretter tildelt til fienden node agent.
  • Til slutt, konfigurere vi massen, Maxspeed, og maxAcceleration egenskapene til agenten. Disse påvirker hvor raskt objektene kan flytte og slå. Føl deg fri til å leke seg med disse verdiene og se hvordan det påvirker bevegelse av fiendens prikker

    Deretter legger du til følgende to eiendommer til GameScene klassen.
    Var midler: [GKAgent2D] = [] Var lastUpdateTime: CFTimeInterval = 0.0

    agenter array vil bli brukt til å holde en referanse til de fiendtlige agenter i scenen. Den lastUpdateTime Eiendommen vil bli brukt til å beregne tiden som har gått siden den scenen ble sist oppdatert

    Til slutt erstatte gjennomføringen av oppdateringen (_ :) metoden i GameScene klassen med følgende gjennomføringen.
    overstyring func oppdateringen (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 oppdateringen (_ :) metode, vi beregne tiden som har gått siden den siste scenen oppdatering og deretter oppdatere agenter med denne verdien.

    Bygg og kjøre programmet ditt, og begynne å flytte rundt på scenen. Du vil se at fiendens prikker vil sakte begynne å bevege seg mot deg.

    Som du kan se, mens fiendens prikker gjøre målrette den aktuelle spilleren, trenger de ikke å navigere rundt i hvite barrierer, i stedet prøver de å bevege seg gjennom dem. La oss gjøre fiender litt smartere med pathfinding.

    2. Pathfinding

    Med GameplayKit rammeverk, kan du legge kompleks pathfinding til spillet ditt ved å kombinere fysikk organer med GameplayKit klasser og metoder. For spillet vårt, skal vi sette det opp slik at fiendtlige prikker vil målrette spilleren prikk og samtidig navigere rundt hindringer.

    pathfinding i GameplayKit begynner med å lage en graf over scenen. Denne grafen er en samling av individuelle steder, også referert til som noder, og forbindelsene mellom disse stedene. Disse tilkoblingene definere hvordan et bestemt objekt kan bevege seg fra ett sted til et annet. En graf kan modellere de tilgjengelige stiene i din scene i en av tre måter:

    En kontinuerlig rom som inneholder hindringer: Denne grafen modellen gir mulighet for glatte baner rundt hindringer fra ett sted til et annet. For denne modellen, er GKObstacleGraph klassen brukes for grafen, den GKPolygonObstacle klasse for hindringer, og GKGraphNode2D klasse for noder (steder)

    En enkel 2D rutenett. I dette tilfellet gyldige steder kan bare være de med heltall koordinater. Denne grafen modellen er nyttig når motivet har en tydelig rutenett og du trenger ikke glatte veier. Ved å bruke denne modellen, kan objekter bare bevege seg horisontalt eller vertikalt i en enkelt retning til enhver tid. For denne modellen, er GKGridGraph klassen brukes for grafen og GKGridGraphNode klasse for noder

    En samling av steder og forbindelsene mellom dem. Dette er den mest generiske grafen modell og anbefales for de tilfeller hvor objekter flytte mellom forskjellige områder, men deres bestemt sted innenfor denne plassen er ikke avgjørende for gameplay. For denne modellen, er GKGraph klassen brukes for grafen og GKGraphNode klasse for noder.

    Fordi vi ønsker at spilleren prikk i spillet vårt til å navigere rundt i hvite barrierer, vi kommer til å bruke GKObstacleGraph klasse for å lage en grafisk fremstilling av vår scene. Til å begynne, erstatte spawnPoints eiendom i GameScene klassen 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: 2 450), CGPoint (x: 1200, y: 2 950), CGPoint (x: 1200, y: 3400), CGPoint (x: 2550 y: 2 350), CGPoint (x: 2500, y: 3100), CGPoint (x: 3000, y: 2 400), CGPoint (x: 2 048, y: 2 400), CGPoint (x: 2200, y : 2 200)] Var grafen: GKObstacleGraph

    spawnPoints rekke inneholder noen endrede spawn steder for anvendelsen av denne opplæringen. Dette er fordi tiden GameplayKit kan bare beregne baner mellom objekter som er relativt nær hverandre.

    På grunn av den store standard avstanden mellom prikkene i dette spillet, må et par nye gyte poeng legges til illustrere pathfinding. Merk at vi også erklære en graf egenskap av typen GKObstacleGraph å holde en referanse til grafen vi vil skape

    Deretter må du legge til følgende to linjer med kode i starten av didMoveToView (_ :) metode:.
    la hindringer = SKNode.obstaclesFromNodePhysicsBodies (self.children) Diagram = GKObstacleGraph (hindringer: hindringer, bufferRadius: 0.0)

    I den første linjen, skaper vi en rekke hindringer fra fysikk organer i scenen. Vi deretter opprette grafen objekt med disse hindringene. Den bufferRadius parameter i denne initializer kan brukes til å tvinge gjenstander for å ikke komme innenfor en bestemt avstand fra disse hindringene. Disse linjene må legges ved starten av didMoveToView (_ :) metode, fordi grafen vi skaper er nødvendig etter den tid initialSpawn metoden kalles.

    Til slutt erstatte initialSpawn metoden 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? = Nil slå respawnFactor {case 0: node = PointsNode (circleOfRadius: 25)! Node .physicsBody = SKPhysicsBody (circleOfRadius: 25) node .fillColor = UIColor.greenColor () case 1: node = RedEnemyNode (circleOfRadius: 75) node! .physicsBody = SKPhysicsBody (circleOfRadius: 75)! node .fillColor = UIColor.redColor () case 2: node = YellowEnemyNode (circleOfRadius: 50)! node .physicsBody = SKPhysicsBody (circleOfRadius: 50) node .fillColor = UIColor.yellowColor (! ) standard: break} hvis la enhet = node .valueForKey ("enhet") som? GKEntity, la agenten = node? .valueForKey ("Agent") som? GKAgent2D hvor respawnFactor! = 0 {entity.addComponent (agent) agent.delegate = node som? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (agent) /* la atferd = GKBehavior (mål: GKGoal (toSeekAgent: playerNode.agent), vekt : 1,0) agent.behavior = atferd * //*** 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 {la path = GKPath (graphNodes: pathNodes, radius: 1,0)! La followPath = GKGoal (toFollowPath: bane, maxPredictionTime: 1,0, fremover: true) la stayOnPath = GKGoal (toStayOnPath: bane, maxPredictionTime: 1.0) la atferd = GKBehavior (mål: [followPath, stayOnPath]) agent.behavior = atferd} self.graph.removeNodes ([startNode]) /*** END pathfinding *** /agent.mass = 0.01 agent.maxSpeed ​​= 50 agent.maxAcceleration = 1000} node! .position = punktet node! .strokeColor = UIColor.clearColor () node! .physicsBody! .contactTestBitMask = 1 self.addChild (node!)} self.graph.removeNodes ([endNode]) }

    Vi begynner metoden ved å opprette en GKGraphNode2D objekt med standardspiller spawn koordinater. Deretter kobler vi denne noden til grafen, slik at det kan brukes ved å finne baner.

    Mesteparten av initialSpawn metode forblir uendret. Jeg har lagt noen kommentarer å vise deg hvor den pathfinding delen av koden ligger i første hvis setningen. La oss gå gjennom denne koden trinnvis:

    Vi skaper en annen GKGraphNode2D forekomst og koble denne til grafen

    Vi skaper en rekke noder som utgjør en sti ved å ringe. findPathFromNode (_:. toNode :) metoden på vår graf

    Hvis det er opprettet en rekke bane noder vellykket, vi da lage en sti fra dem. Radiusen parameter fungerer ligner på bufferRadius parameter fra før og definerer hvor mye et objekt kan bevege seg bort fra den opprettede banen.

  • Vi skaper to GKGoal objekter, en for å følge stien og en annen for å bo på banen. Den maxPredictionTime parameter gir mulighet for mål å regne som best den kan på forhånd om noe kommer til å avbryte objektet fra følgende /bor på den aktuelle banen.
  • Til slutt lager vi en ny atferd med disse to målene og tildele denne til agenten.

    Du vil også legge merke til at vi fjerner nodene vi skaper fra grafen når vi er ferdig med dem. Dette er en god praksis å følge som det sikrer at nodene du har laget ikke forstyrrer andre pathfinding beregninger senere.

    Bygg og kjøre programmet ditt en siste gang, og du vil se to prikker gyte veldig lukke til deg og begynner å bevege seg mot deg. Du må kanskje kjøre spillet flere ganger hvis de begge spawn som grønne prikker.

    Viktig!

    I denne opplæringen brukte vi GameplayKit sin pathfinding funksjon for å gjøre det mulig fiendtlige prikker å målrette spilleren dot rundt hindringer. Merk at dette bare var for et praktisk eksempel på pathfinding.

    For en faktisk produksjon spillet, ville det være best å implementere denne funksjonaliteten ved å kombinere spilleren målretting scoring tidligere i denne opplæringen med en hindring-unngå mål opprettet med init (toAvoidObstacles:. maxPredictionTime :) praktiske metoden, som du kan lese mer om i GKGoal Class Reference

    Konklusjon

    I denne opplæringen, viste jeg deg hvordan du kan utnytte agenter, mål, og atferd i spill som har en enhet-komponent struktur. Mens vi bare laget tre mål i denne opplæringen, det er mange flere tilgjengelige for deg, som du kan lese mer om i GKGoal Class Reference.

    Jeg viste deg også hvordan du skal gjennomføre noen avansert pathfinding i spillet ved lage en graf, et sett av hindringer, og mål å følge disse banene.

    Som du ser, det er en enorm mengde funksjonalitet gjort tilgjengelig for deg gjennom GameplayKit rammeverket. I den tredje og siste del av denne serien, vil jeg lære deg om GameplayKit tilfeldige verdi generatorer og hvordan du kan lage din egen regel system for å introdusere noen fuzzy logikk i spillet ditt.

    Som alltid må du huske å forlate dine kommentarer og tilbakemeldinger nedenfor.