Dies ist der zweite Teil von Eine Einführung in GameplayKit. Wenn Sie den ersten Teil noch nicht durchgearbeitet haben, empfehle ich, zuerst dieses Tutorial zu lesen, bevor Sie mit diesem fortfahren.
In diesem Tutorial werde ich Ihnen zwei weitere Funktionen des GameplayKit-Frameworks zeigen, die Sie nutzen können:
Indem wir Agenten, Ziele und Verhalten verwenden, werden wir einige grundlegende künstliche Intelligenz (KI) in das Spiel einbauen, das wir im ersten Teil dieser Serie begonnen haben. Die KI ermöglicht es unseren roten und gelben gegnerischen Punkten, auf unser blaues Spielerfeld zu zielen. Wir werden auch Pathfinding implementieren, um diese KI zu erweitern und Hindernisse zu umgehen.
Für dieses Tutorial können Sie Ihre Kopie des abgeschlossenen Projekts aus dem ersten Teil dieser Serie verwenden oder eine neue Kopie des Quellcodes von GitHub herunterladen.
In GameplayKit werden Agenten, Ziele und Verhalten in Kombination miteinander verwendet, um zu definieren, wie sich verschiedene Objekte in Ihrer Szene in Relation zueinander bewegen. Für ein einzelnes Objekt (oder SKShapeNode
In unserem Spiel beginnen Sie mit der Erstellung eines Agent, vertreten durch die GKAgent
Klasse. Für 2D-Spiele wie unseres müssen wir jedoch den Beton verwenden GKAgent2D
Klasse.
Das GKAgent
Klasse ist eine Unterklasse von GKComponent
. Das bedeutet, dass Ihr Spiel eine entitäts- und komponentenbasierte Struktur verwenden muss, wie ich Ihnen im ersten Tutorial dieser Serie gezeigt habe.
Agenten repräsentieren die Position, Größe und Geschwindigkeit eines Objekts. Sie fügen dann eine Verhalten, vertreten durch die GKBehaviour
Klasse, zu diesem Agenten. Zum Schluss erstellen Sie eine Gruppe von Tore, vertreten durch die GKGoal
Klasse und fügen Sie sie dem Verhaltensobjekt hinzu. Mithilfe von Zielen können viele verschiedene Spielelemente erstellt werden, beispielsweise:
Ihr Verhaltensobjekt überwacht und berechnet alle Ziele, die Sie ihm hinzufügen, und gibt diese Daten an den Agenten zurück. Mal sehen, wie das in der Praxis funktioniert.
Öffnen Sie Ihr Xcode-Projekt und navigieren Sie zu PlayerNode.swift. Wir müssen zuerst sicherstellen, dass PlayerNode
Klasse entspricht der GKAgentDelegate
Protokoll.
Klasse PlayerNode: SKShapeNode, GKAgentDelegate …
Fügen Sie als Nächstes den folgenden Codeblock hinzu PlayerNode
Klasse.
var agent = GKAgent2D () // MARK: Agent Delegat func agentWillUpdate (agent: GKAgent) wenn agent2D = agent as ist? GKAgent2D agent2D.position = float2 (Float (position.x), Float (position.y)) func agentDidUpdate (agent: GKAgent) wenn let agent2D = agent as? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))
Wir beginnen mit dem Hinzufügen einer Eigenschaft zum PlayerNode
Klasse, so dass wir immer einen Verweis auf das Agentenobjekt des aktuellen Spielers haben. Als nächstes implementieren wir die beiden Methoden von GKAgentDelegate
Protokoll. Durch die Implementierung dieser Methoden stellen wir sicher, dass der auf dem Bildschirm angezeigte Spielerpunkt immer die Änderungen widerspiegelt, die GameplayKit vornimmt.
Das agentWillUpdate (_ :)
Die Methode wird aufgerufen, bevor GameplayKit das Verhalten und die Ziele dieses Agenten durchläuft, um festzustellen, wohin sich der Agent bewegen soll. Ebenso die agentDidUpdate (_ :)
Die Methode wird direkt aufgerufen, nachdem GameplayKit diesen Vorgang abgeschlossen hat.
Durch die Implementierung dieser beiden Methoden wird sichergestellt, dass der Knoten, den wir auf dem Bildschirm sehen, die Änderungen widerspiegelt, die GameplayKit vornimmt, und dass GameplayKit bei der Berechnung die letzte Position des Knotens verwendet.
Als nächstes öffnen ContactNode.swift und ersetzen Sie den Inhalt der Datei durch die folgende Implementierung:
import UIKit importieren SpriteKit importieren GameplayKit-Klasse ContactNode: SKShapeNode, GKAgentDelegate var agent = GKAgent2D () // MARK: Agent Delegat func agentWillUpdate (Agent: GKAgent) wenn let agent2D = agent as? GKAgent2D agent2D.position = float2 (Float (position.x), Float (position.y)) func agentDidUpdate (agent: GKAgent) wenn let agent2D = agent as? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))
Durch die Implementierung der GKAgentDelegate
Protokoll in der ContactNode
In dieser Klasse erlauben wir, dass alle anderen Punkte in unserem Spiel mit GameplayKit und unserem Spielerpunkt auf dem neuesten Stand sind.
Es ist jetzt an der Zeit, die Verhaltensweisen und Ziele festzulegen. Damit dies funktioniert, müssen wir uns um drei Dinge kümmern:
Zunächst offen GameScene.swift und am Ende des didMoveToView (_ :)
fügen Sie die folgenden zwei Codezeilen hinzu:
playerNode.entity.addComponent (playerNode.agent) playerNode.agent.delegate = playerNode
Mit diesen beiden Codezeilen fügen wir den Agenten als Komponente hinzu und setzen den Stellvertreter des Agenten als Knoten selbst.
Als nächstes ersetzen Sie die Implementierung von Anfangsspawn
Methode mit folgender Implementierung:
func initialSpawn () für Punkt in self.spawnPoints let respawnFactor = arc4random ()% 3 // Erzeugt einen Wert zwischen 0 und 2 (einschließlich) var-Knoten: SKShapeNode? = nil switch respawnFactor Fall 0: Knoten = PointsNode (CircleOfRadius: 25) Knoten! .physicsBody = SKPhysicsBody (CircleOfRadius: 25) Knoten! .fillColor = UIColor.greenColor () Fall 1: Knoten = RedEnemyNode (75) .physicsBody = SKPhysicsBody (circleOfRadius: 75) Knoten! .fillColor = UIColor.redColor (), Fall 2: Knoten = YellowEnemyNode (circleOfRadius: 50) Knoten! .physicsBody = SKPhysicsBody = SKPhysicsBody (50). ) default: break wenn Entität = Knoten? .valueForKey ("Entität") als? GKEntity, lassen Sie agent = node? .ValueForKey ("agent") als? GKAgent2D wo respawnFactor! = 0 entity.addComponent (agent) agent.delegate = Knoten wie? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (agent) let Verhalten = GKBehavior (Ziel: GKGoal (toSeekAgent: playerNode.agent), Gewichtung: 1.0 ) agent.behavior = Verhalten agent.mass = 0.01 agent.maxSpeed = 50 agent.maxAcceleration = 1000 Knoten! .position = Punktknoten! .strokeColor = UIColor.clearColor () Knoten! .physicsBody! .contactTestBitMask = 1 self.addChild (Knoten!)
Der wichtigste Code, den wir hinzugefügt haben, befindet sich im ob
Aussage, die dem folgt Schalter
Aussage. Gehen wir diesen Code Zeile für Zeile durch:
Agenten
. Wir werden diese Eigenschaft dem hinzufügen GameScene
Klasse in einem Moment.GKBehavior
Objekt mit einem einzigen GKGoal
Ziel des Agenten des aktuellen Spielers. Das Gewicht
Mit diesem Parameter wird festgelegt, welche Ziele Vorrang vor anderen haben sollen. Stellen Sie sich zum Beispiel vor, Sie haben ein Ziel, einen bestimmten Agenten anzugreifen, und ein anderes Ziel, sich von einem anderen Agenten zu entfernen, das Ziel sollte jedoch bevorzugt werden. In diesem Fall können Sie dem Zielziel eine Gewichtung von geben 1
und das entfernte Ziel ein Gewicht von 0,5
. Dieses Verhalten wird dann dem Agenten des gegnerischen Knotens zugewiesen.Masse
, Höchstgeschwindigkeit
, und maxAcceleration
Eigenschaften des Agenten. Diese beeinflussen, wie schnell sich Objekte bewegen und wenden können. Fühlen Sie sich frei, mit diesen Werten herumzuspielen und zu sehen, wie sie die Bewegung der gegnerischen Punkte beeinflussen.Fügen Sie als Nächstes die folgenden zwei Eigenschaften hinzu GameScene
Klasse:
var-Agenten: [GKAgent2D] = [] var lastUpdateTime: CFTimeInterval = 0,0
Das Agenten
Array wird verwendet, um einen Verweis auf die feindlichen Agenten in der Szene zu behalten. Das lastUpdateTime
Diese Eigenschaft wird verwendet, um die Zeit zu berechnen, die seit der letzten Aktualisierung der Szene vergangen ist.
Ersetzen Sie abschließend die Implementierung des aktualisieren(_:)
Methode der GameScene
Klasse mit der folgenden Implementierung:
func update überschreiben (currentTime: CFTimeInterval) / * Wird aufgerufen, bevor jeder Frame gerendert wird * / self.camera?.position = playerNode.position, wenn self.lastUpdateTime == 0 lastUpdateTime = currentTime ist playerNode.agent.updateWithDeltaTime (Delta) für Agenten in Agenten agent.updateWithDeltaTime (Delta)
In dem aktualisieren(_:)
Methode berechnen wir die Zeit, die seit der letzten Szenenaktualisierung vergangen ist, und aktualisieren die Agenten mit diesem Wert.
Erstellen und starten Sie Ihre App und bewegen Sie sich in der Szene. Sie werden sehen, dass sich die gegnerischen Punkte langsam auf Sie zu bewegen.
Wie Sie sehen, navigieren die gegnerischen Punkte zwar nicht auf den aktuellen Spieler, bewegen sich jedoch nicht um die weißen Barrieren herum, sondern versuchen, sich durch sie hindurch zu bewegen. Lassen Sie uns die Feinde mit Pfadfindung etwas intelligenter machen.
Mit dem GameplayKit-Framework können Sie Ihrem Spiel komplexe Pfadfindung hinzufügen, indem Sie physische Körper mit GameplayKit-Klassen und -Methoden kombinieren. Für unser Spiel werden wir es so einrichten, dass die gegnerischen Punkte den Spielerpunkt anvisieren und gleichzeitig um Hindernisse navigieren.
Pathfinding in GameplayKit beginnt mit dem Erstellen eines Graph von deiner Szene. Diese Grafik ist eine Sammlung einzelner Standorte, auch bezeichnet als Knoten, und Verbindungen zwischen diesen Orten. Diese Verbindungen definieren, wie ein bestimmtes Objekt von einem Ort zu einem anderen verschoben werden kann. Ein Diagramm kann die verfügbaren Pfade in Ihrer Szene auf drei Arten modellieren:
GKObstacleGraph
Klasse wird für den Graphen verwendet GKPolygonObstacle
Klasse für Hindernisse und die GKGraphNode2D
Klasse für Knoten (Standorte).GKGridGraph
Klasse wird für die Grafik und die GKGridGraphNode
Klasse für Knoten.GKGraph
Klasse wird für die Grafik und die GKGraphNode
Klasse für Knoten.Da wir möchten, dass der Spielerpunkt in unserem Spiel um die weißen Barrieren herum navigiert, verwenden wir den GKObstacleGraph
Klasse, um eine Grafik unserer Szene zu erstellen. Um zu beginnen, ersetzen Sie das SpawnPoints
Eigentum in der GameScene
Klasse mit folgendem:
Lassen Sie 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!
Das SpawnPoints
Array enthält einige geänderte Spawn-Standorte für die Zwecke dieses Tutorials. Dies liegt daran, dass GameplayKit derzeit nur Pfade zwischen Objekten berechnen kann, die relativ nahe beieinander liegen.
Aufgrund des großen Standardabstands zwischen den Punkten in diesem Spiel müssen einige neue Spawnpunkte hinzugefügt werden, um die Pfadfindung zu veranschaulichen. Beachten Sie, dass wir auch eine Graph
Eigenschaft des Typs GKObstacleGraph
Um einen Verweis auf das Diagramm zu behalten, erstellen wir.
Als nächstes fügen Sie die folgenden zwei Codezeilen am Anfang von ein didMoveToView (_ :)
Methode:
Lassen Sie Hindernisse = SKNode.obstaclesFromNodePhysicsBodies (self.children) graph = GKObstacleGraph (Hindernisse: Hindernisse, bufferRadius: 0.0)
In der ersten Zeile erstellen wir eine Reihe von Hindernissen aus den physischen Körpern der Szene. Dann erstellen wir das Diagrammobjekt anhand dieser Hindernisse. Das bufferRadius
Der Parameter in diesem Initialisierer kann verwendet werden, um Objekte dazu zu bringen, sich nicht in einer bestimmten Entfernung von diesen Hindernissen zu befinden. Diese Zeilen müssen zu Beginn des hinzugefügt werden didMoveToView (_ :)
Methode, weil der Graph, den wir erstellen, zu der Zeit benötigt wird Anfangsspawn
Methode wird aufgerufen.
Ersetzen Sie schließlich die Anfangsspawn
Methode mit folgender Implementierung:
func initialSpawn () let endNode = GKGraphNode2D (Punkt: float2 (x: 2048.0, y: 2048.0)) self.graph.connectNodeUsingacles (endNode) für Punkt in self.spawnPoints lasse respawnFactor = arc4random ()% 3 // produzieren ein Wert zwischen 0 und 2 (einschließlich) var-Knoten: SKShapeNode? = nil switch respawnFactor Fall 0: Knoten = PointsNode (CircleOfRadius: 25) Knoten! .physicsBody = SKPhysicsBody (CircleOfRadius: 25) Knoten! .fillColor = UIColor.greenColor () Fall 1: Knoten = RedEnemyNode (75) .physicsBody = SKPhysicsBody (circleOfRadius: 75) Knoten! .fillColor = UIColor.redColor (), Fall 2: Knoten = YellowEnemyNode (circleOfRadius: 50) Knoten! .physicsBody = SKPhysicsBody = SKPhysicsBody (50). ) default: break wenn Entität = Knoten? .valueForKey ("Entität") als? GKEntity, lassen Sie agent = node? .ValueForKey ("agent") als? GKAgent2D wo respawnFactor! = 0 entity.addComponent (agent) agent.delegate = Knoten wie? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (Agent) / * let behaviour = GKBehavior (Ziel: GKGoal (toSeekAgent: playerNode.agent), weight : 1.0) agent.behavior = Verhalten * / / *** BEGIN PATHFINDING *** / let startNode = GKGraphNode2D (Punkt: agent.position) self.graph.connectNodeUsingacles (startNode) let pathNodes = self.graph.findPathFromNode (startNode, toNode: endNode) as! [GKGraphNode2D] if! PathNodes.isEmpty let path = GKPath (graphNodes: pathNodes, radius: 1.0) setze followPath = GKGoal (toFollowPath: path, maxPredictionTime: 1.0, forward: true) let stayOnPath =AutAutPath: 1.0) let behaviour = GKBehavior (Ziele: [followPath, stayOnPath]) agent.behavior = behaviour self.graph.removeNodes ([startNode]) / *** END PATHFINDING *** / agent.mass = 0.01 agent.maxSpeed = 50 agent.maxAcceleration = 1000 Knoten! .Position = Punktknoten! .StrokeColor = UIColor.clearColor () - Knoten! .PhysicsBody! .ContactTestBitMask = 1 self.addChild (node!) Self.graph.removeNodes ([endNode])
Wir beginnen die Methode mit der Erstellung eines GKGraphNode2D
Objekt mit den Standard-Player-Spawn-Koordinaten. Als Nächstes verbinden wir diesen Knoten mit dem Graphen, damit er beim Auffinden von Pfaden verwendet werden kann.
Die meisten von den Anfangsspawn
Methode bleibt unverändert. Ich habe einige Kommentare hinzugefügt, um Ihnen zu zeigen, wo sich der Pfadfindeteil des Codes im ersten befindet ob
Aussage. Gehen wir diesen Code Schritt für Schritt durch:
GKGraphNode2D
Instanz und verbinden Sie diese mit der Grafik.findPathFromNode (_: toNode :)
Methode in unserer Grafik.Radius
Der Parameter arbeitet ähnlich wie der bufferRadius
Parameter von vor und definiert, wie weit sich ein Objekt vom erstellten Pfad entfernen kann.GKGoal
Objekte, eines für das Befolgen des Pfads und eines für das Bleiben auf dem Pfad. Das maxPredictionTime
Mit diesem Parameter kann das Ziel so früh wie möglich berechnen, ob das Objekt durch irgendetwas davon abgehalten wird, diesem bestimmten Pfad zu folgen.Sie werden auch bemerken, dass wir die Knoten, die wir erstellen, aus dem Diagramm entfernen, sobald wir mit ihnen fertig sind. Dies ist eine bewährte Vorgehensweise, um sicherzustellen, dass die von Ihnen erstellten Knoten später keine anderen Pfadfindungsberechnungen beeinflussen.
Erstellen und starten Sie Ihre App ein letztes Mal. Sie werden zwei Punkte sehen, die sich ganz in Ihrer Nähe befinden, und sich auf Sie zubewegen. Möglicherweise müssen Sie das Spiel mehrmals ausführen, wenn beide als grüne Punkte erscheinen.
Wichtig!
In diesem Lernprogramm haben wir die Pathfinding-Funktion von GameplayKit verwendet, um es Feindpunkten zu ermöglichen, den Spieler auf Hindernisse zu richten. Beachten Sie, dass dies nur für ein praktisches Beispiel für die Pfadfindung war.
Für ein reales Produktionsspiel ist es am besten, diese Funktionalität zu implementieren, indem Sie das Ziel des Spielers aus einem früheren Abschnitt dieses Tutorials mit einem mit dem Ziel erstellten Hindernisvermeidungsziel kombinieren init (toAvoidObstacles: maxPredictionTime :)
Convenience - Methode, über die Sie in der GKGoal
Klassenreferenz.
In diesem Tutorial habe ich Ihnen gezeigt, wie Sie Agenten, Ziele und Verhalten in Spielen einsetzen können, die eine Entitätskomponentenstruktur aufweisen. Während wir in diesem Tutorial nur drei Ziele festgelegt haben, stehen Ihnen noch viele weitere zur Verfügung, über die Sie im Abschnitt Weitere Informationen lesen können GKGoal
Klassenreferenz.
Ich habe Ihnen auch gezeigt, wie Sie eine fortgeschrittene Pfadfindung in Ihrem Spiel implementieren können, indem Sie ein Diagramm, eine Reihe von Hindernissen und Ziele erstellen, die diesen Pfaden folgen.
Wie Sie sehen, stehen Ihnen im Rahmen des GameplayKit-Frameworks zahlreiche Funktionen zur Verfügung. Im dritten und letzten Teil dieser Serie werde ich Sie über die Zufallswertgeneratoren von GameplayKit und über das Erstellen eines eigenen Regelsystems informieren, um eine Fuzzy-Logik in Ihr Spiel einzuführen.
Bitte hinterlassen Sie wie immer Ihre Kommentare und Rückmeldungen.