Dit is het tweede deel van Een inleiding tot GameplayKit. Als je het eerste deel nog niet hebt doorgenomen, raad ik aan eerst die tutorial te lezen voordat je verdergaat met deze.
In deze tutorial ga ik je leren over nog twee andere functies van het GameplayKit-framework waar je gebruik van kunt maken:
Door agenten, doelen en gedrag te gebruiken, gaan we wat basis kunstmatige intelligentie (AI) inbouwen in de game die we in het eerste deel van deze serie begonnen. De AI zorgt ervoor dat onze rode en gele vijandige stippen zich kunnen richten en naar onze blauwe spelerpunt kunnen bewegen. We gaan ook padvinden implementeren om op deze AI uit te breiden om obstakels te omzeilen.
Voor deze zelfstudie kunt u uw exemplaar van het voltooide project uit het eerste deel van deze serie gebruiken of een nieuw exemplaar van de broncode downloaden van GitHub.
In GameplayKit worden agenten, doelen en gedrag in combinatie met elkaar gebruikt om te definiëren hoe verschillende objecten ten opzichte van elkaar in de hele scène bewegen. Voor een enkel object (of SKShapeNode
in onze game), begin je met het maken van een middel, vertegenwoordigd door de GKAgent
klasse. Voor 2D-spellen, zoals de onze, moeten we echter het beton gebruiken GKAgent2D
klasse.
De GKAgent
class is een subklasse van GKComponent
. Dit betekent dat je game een entiteit- en component-gebaseerde structuur moet gebruiken, zoals ik je in de eerste tutorial van deze serie liet zien.
Agents vertegenwoordigen de positie, grootte en snelheid van een object. Je voegt dan een toe gedrag, vertegenwoordigd door de GKBehaviour
klasse, voor deze agent. Eindelijk, creëer je een set van doelen, vertegenwoordigd door de GKGoal
klasse en voeg ze toe aan het gedragsobject. Doelen kunnen worden gebruikt om veel verschillende gameplay-elementen te maken, bijvoorbeeld:
Uw gedragsobject bewaakt en berekent alle doelen die u eraan toevoegt en relais deze gegevens vervolgens terug naar de agent. Laten we kijken hoe dit in de praktijk werkt.
Open uw Xcode-project en navigeer naar PlayerNode.swift. We moeten eerst zorgen dat het PlayerNode
klas voldoet aan de GKAgentDelegate
protocol.
class PlayerNode: SKShapeNode, GKAgentDelegate ...
Voeg vervolgens het volgende codeblok toe aan de PlayerNode
klasse.
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) if let agent2D = agent as? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))
We beginnen met het toevoegen van een eigenschap aan de PlayerNode
klasse, zodat we altijd een verwijzing hebben naar het agentobject van de huidige speler. Vervolgens implementeren we de twee methoden van de GKAgentDelegate
protocol. Door deze methoden te implementeren, zorgen we ervoor dat de stip van de speler die op het scherm wordt weergegeven, altijd overeenkomt met de wijzigingen die GameplayKit aanbrengt.
De agentWillUpdate (_ :)
methode wordt aangeroepen juist voordat GameplayKit door het gedrag en de doelen van die agent kijkt om te bepalen waar het moet worden verplaatst. Evenzo, de agentDidUpdate (_ :)
methode wordt meteen genoemd nadat GameplayKit dit proces heeft voltooid.
Onze implementatie van deze twee methoden zorgt ervoor dat het knooppunt dat we op het scherm zien de veranderingen weerspiegelt die GameplayKit maakt en dat GameplayKit de laatste positie van het knooppunt gebruikt bij het uitvoeren van zijn berekeningen.
Open vervolgens ContactNode.swift en vervang de inhoud van het bestand door de volgende implementatie:
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) if let agent2D = agent as? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))
Door de GKAgentDelegate
protocol in de ContactNode
klasse, we staan toe dat alle andere stippen in onze game up-to-date zijn met GameplayKit en onze speler-stip.
Het is nu tijd om het gedrag en de doelen in te stellen. Om dit te laten werken, moeten we voor drie dingen zorgen:
Ten eerste open GameScene.swift en, aan het einde van de didMoveToView (_ :)
methode, voeg de volgende twee regels code toe:
playerNode.entity.addComponent (playerNode.agent) playerNode.agent.delegate = playerNode
Met deze twee regels code voegen we de agent toe als een component en stelt de gemachtigde van de agent in als het knooppunt zelf.
Vervang vervolgens de implementatie van de initialSpawn
methode met de volgende implementatie:
func initialSpawn () voor point in self.spawnPoints let respawnFactor = arc4random ()% 3 // Genereert een waarde tussen 0 en 2 (inclusief) var node: SKShapeNode? = nil switch 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 ( ) standaard: onderbreken als entiteit = knooppunt? .valueForKey ("entiteit") als? GKEntity, let agent = node? .ValueForKey ("agent") as? GKAgent2D waarbij respawnFactor! = 0 entity.addComponent (agent) agent.delegate = node als? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (agent) laat gedrag = GKBehavior (doel: GKGoal (toSeekAgent: playerNode.agent), gewicht: 1.0 ) agent.behavior = behaviour agent.mass = 0.01 agent.maxSpeed = 50 agent.maxAcceleration = 1000 node! .position = point node! .strokeColor = UIColor.clearColor () node! .physicsBody! .contactTestBitMask = 1 self.addChild (knooppunt!)
De belangrijkste code die we hebben toegevoegd, bevindt zich in de als
verklaring die volgt op de schakelaar
uitspraak. Laten we deze code regel voor regel doorlopen:
agenten
. We zullen deze eigenschap toevoegen aan de GameScene
les in een moment.GKBehavior
object met een single GKGoal
om de agent van de huidige speler te targeten. De gewicht
parameter in deze initialisator wordt gebruikt om te bepalen welke doelen voorrang op andere moeten krijgen. Stel je bijvoorbeeld voor dat je een doel hebt om een bepaalde agent te targeten en een doel om van een andere agent af te wijken, maar je wilt dat het targetingdoel de voorkeur krijgt. In dit geval zou u het targetingdoel een gewicht van kunnen geven 1
en het verplaatsende doel een gewicht van 0.5
. Dit gedrag wordt vervolgens toegewezen aan de agent van het vijandelijke knooppunt.massa-
, maximale snelheid
, en maxAcceleration
eigenschappen van de agent. Deze hebben invloed op hoe snel de objecten kunnen bewegen en keren. Voel je vrij om met deze waarden te spelen en zie hoe het de beweging van de vijandelijke stippen beïnvloedt.Voeg vervolgens de volgende twee eigenschappen toe aan de GameScene
klasse:
var agents: [GKAgent2D] = [] var lastUpdateTime: CFTimeInterval = 0.0
De agenten
array wordt gebruikt om een verwijzing naar de vijandelijke agenten in de scène te houden. De lastUpdateTime
property wordt gebruikt om de tijd te berekenen die is verstreken sinds de scène voor het laatst is bijgewerkt.
Ten slotte, vervang de implementatie van de bijwerken(_:)
methode van de GameScene
klasse met de volgende implementatie:
override func update (currentTime: CFTimeInterval) / * Bel voor elk frame wordt weergegeven * / self.camera?.position = playerNode.position als self.lastUpdateTime == 0 lastUpdateTime = currentTime laat delta = currentTime - lastUpdateTime lastUpdateTime = currentTime playerNode.agent.updateWithDeltaTime (delta) voor agent in agents agent.updateWithDeltaTime (delta)
In de bijwerken(_:)
methode berekenen we de tijd die is verstreken sinds de laatste scène-update en werken de agenten vervolgens bij met die waarde.
Bouw je app en voer deze uit, en begin je rond te bewegen. Je zult zien dat de vijandelijke stippen langzaam naar je toe zullen bewegen.
Zoals je kunt zien, terwijl de vijandige stippen de huidige speler targeten, navigeren ze niet rond de witte barrières, maar proberen ze er doorheen te bewegen. Laten we de vijanden een beetje slimmer maken met pathfinding.
Met het GameplayKit-framework kun je complexe padvinden aan je spel toevoegen door physics bodies te combineren met gameplayKit-klassen en -methoden. Voor ons spel gaan we het zo instellen dat de vijandelijke stippen op de punt van de speler gericht zijn en tegelijkertijd rond obstakels navigeren.
Pathfinding in GameplayKit begint met het maken van een diagram van je scène. Deze grafiek is een verzameling afzonderlijke locaties, ook wel aangeduid als nodes, en verbindingen tussen deze locaties. Deze verbindingen bepalen hoe een bepaald object van de ene locatie naar de andere kan gaan. Een grafiek kan de beschikbare paden in uw scène op drie manieren modelleren:
GKObstacleGraph
klasse wordt gebruikt voor de grafiek, de GKPolygonObstacle
klasse voor obstakels, en de GKGraphNode2D
klasse voor knooppunten (locaties).GKGridGraph
klasse wordt gebruikt voor de grafiek en de GKGridGraphNode
klasse voor knooppunten.GKGraph
klasse wordt gebruikt voor de grafiek en de GKGraphNode
klasse voor knooppunten.Omdat we willen dat de punt van de speler in ons spel rond de witte barrières navigeert, gaan we de GKObstacleGraph
klas om een grafiek van onze scène te maken. Vervang om te beginnen de spawnPoints
eigendom in de GameScene
les met het volgende:
laat 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 graph: GKObstacleGraph!
De spawnPoints
array bevat enkele gewijzigde spawn-locaties ten behoeve van deze tutorial. Dit komt omdat GameplayKit momenteel alleen paden kan berekenen tussen objecten die relatief dicht bij elkaar staan.
Vanwege de grote standaardafstand tussen de stippen in dit spel, moeten een aantal nieuwe spawn-punten worden toegevoegd om padvervaging te illustreren. Merk op dat we ook a diagram
eigendom van het type GKObstacleGraph
om een verwijzing naar de grafiek te behouden die we zullen maken.
Voeg vervolgens de volgende twee regels code toe aan het begin van de didMoveToView (_ :)
methode:
laat obstakels = SKNode.obstaclesFromNodePhysicsLichamen (self.children) graph = GKObstacleGraph (obstakels: obstakels, bufferRadius: 0.0)
In de eerste regel creëren we een reeks obstakels van de fysische lichamen in de scène. Vervolgens maken we het grafiekobject met behulp van deze obstakels. De bufferRadius
parameter in deze initialisator kan worden gebruikt om objecten te dwingen niet binnen een bepaalde afstand van deze obstakels te komen. Deze regels moeten aan het begin van de didMoveToView (_ :)
methode, omdat de grafiek die we maken, nodig is tegen de tijd dat de initialSpawn
methode wordt genoemd.
Vervang tot slot de initialSpawn
methode met de volgende implementatie:
func initialSpawn () let endNode = GKGraphNode2D (punt: float2 (x: 2048.0, y: 2048.0)) self.graph.connectNodeUsingObstacles (endNode) voor punt in self.spawnPoints let respawnFactor = arc4random ()% 3 // Zal produceren een waarde tussen 0 en 2 (inclusief) var node: SKShapeNode? = nil switch 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 ( ) standaard: onderbreken als entiteit = knooppunt? .valueForKey ("entiteit") als? GKEntity, let agent = node? .ValueForKey ("agent") as? GKAgent2D waarbij respawnFactor! = 0 entity.addComponent (agent) agent.delegate = node als? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (agent) / * let behavion = GKBehavior (doel: GKGoal (toSeekAgent: playerNode.agent), weight : 1.0) agent.behavior = behaviour * / / *** BEGIN PATHFINDING *** / laat startNode = GKGraphNode2D (point: agent.position) self.graph.connectNodeUsingObstacles (startNode) laat pathNodes = self.graph.findPathFromNode (startNode, toNode: endNode) as! [GKGraphNode2D] if! PathNodes.isEmpty let path = GKPath (graphNodes: pathNodes, radius: 1.0) let followPath = GKGoal (toFollowPath: path, maxPredictionTime: 1.0, forward: true) let stayOnPath = GKGoal (toStayOnPath: path, maxPredictionTime: 1.0) laat gedrag = GKBehavior (doelen: [followPath, stayOnPath]) agent.behavior = behaviour self.graph.removeNodes ([startNode]) / *** END PATHFINDING *** / agent.mass = 0.01 agent.maxSpeed = 50 agent.maxAcceleration = 1000 node! .Position = puntknooppunt! .StrokeColor = UIColor.clearColor () node! .PhysicsBody! .ContactTestBitMask = 1 self.addChild (node!) Self.graph.removeNodes ([endNode])
We beginnen de methode door een GKGraphNode2D
object met de standaard spawn-coördinaten van de speler. Vervolgens verbinden we dit knooppunt met de grafiek zodat het kan worden gebruikt bij het vinden van paden.
Meeste van de initialSpawn
methode blijft ongewijzigd. Ik heb enkele opmerkingen toegevoegd om u te laten zien waar het padvindende gedeelte van de code zich in de eerste bevindt als
uitspraak. Laten we deze code stap voor stap doorlopen:
GKGraphNode2D
exemplaar en verbind dit met de grafiek.findPathFromNode (_: toNode :)
methode op onze grafiek.radius
parameter werkt vergelijkbaar met de bufferRadius
parameter van voor en definieert hoeveel een object zich van het gemaakte pad kan verwijderen.GKGoal
objecten, een voor het volgen van het pad en een ander voor het blijven op het pad. De maxPredictionTime
parameter stelt het doel in staat zo snel mogelijk te berekenen of iets het object zal onderbreken van het volgen / blijven op dat specifieke pad.Je zult ook opmerken dat we de knooppunten die we creëren verwijderen uit de grafiek zodra we klaar zijn met hen. Dit is een goede methode om te volgen, omdat het ervoor zorgt dat de knooppunten die u hebt gemaakt, later geen invloed hebben op andere routeverkenningen.
Bouw en run je app een laatste keer en je ziet twee puntjes spawnen heel dicht bij je en begin naar je toe te bewegen. Mogelijk moet je het spel meerdere keren uitvoeren als ze allebei spawnen als groene stippen.
Belangrijk!
In deze tutorial gebruikten we de functie Pathfinding van GameplayKit om vijandige stippen in staat te stellen om de spelerpunt rond obstakels te richten. Merk op dat dit alleen voor een praktisch voorbeeld van pathfinding was.
Voor een echte productiegame zou het het beste zijn om deze functionaliteit te implementeren door het targetdoel van de speler van eerder in deze tutorial te combineren met een obstakel-ontwijkend doel gemaakt met de init (toAvoidObstacles: maxPredictionTime :)
gemaksmethode, waarover u meer kunt lezen in de GKGoal
Klasse Referentie.
In deze zelfstudie heb ik je laten zien hoe je agenten, doelen en gedrag kunt gebruiken in games die een entiteit-componentstructuur hebben. Hoewel we in deze zelfstudie slechts drie doelen hebben gemaakt, zijn er veel meer voor u beschikbaar, waarover u meer kunt lezen in de GKGoal
Klasse Referentie.
Ik heb je ook laten zien hoe je een aantal geavanceerde routeplanning in je spel kunt implementeren door een grafiek, een reeks obstakels en doelen te maken om deze paden te volgen.
Zoals je ziet, is er een enorme hoeveelheid functionaliteit beschikbaar gemaakt via het GameplayKit-framework. In het derde en laatste deel van deze serie zal ik je leren over de random value-generators van GameplayKit en hoe je je eigen regelsysteem kunt maken om wat fuzzy logic in je spel te introduceren.
Laat zoals altijd uw opmerkingen en feedback hieronder achter.