Eine Einführung in GameplayKit Teil 2

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.

Einführung

In diesem Tutorial werde ich Ihnen zwei weitere Funktionen des GameplayKit-Frameworks zeigen, die Sie nutzen können:

  • Agenten, Ziele und Verhaltensweisen
  • Wegfindung

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.

1. Agenten, Ziele und Verhalten

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:

  • auf einen Agenten zugehen
  • sich von einem Agenten entfernen
  • enge Gruppierung mit anderen Agenten
  • um eine bestimmte Position wandern

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:

  • Fügen Sie den Agenten des Spielerknotens zu seiner Entität hinzu und legen Sie seinen Delegierten fest.
  • Konfigurieren Sie Agenten, Verhalten und Ziele für alle unsere feindlichen Punkte.
  • Aktualisieren Sie alle diese Agenten zum richtigen Zeitpunkt.

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:

  • Zuerst fügen wir den Agenten als Komponente zur Entität hinzu und konfigurieren seinen Delegierten.
  • Als Nächstes weisen wir die Position des Agenten zu und fügen den Agenten einem gespeicherten Array hinzu, Agenten. Wir werden diese Eigenschaft dem hinzufügen GameScene Klasse in einem Moment.
  • Wir erstellen dann eine 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.
  • Zuletzt konfigurieren wir die MasseHö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.

2. Wegfindung

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:

  • Ein durchgehender Raum mit Hindernissen: Dieses Diagrammmodell ermöglicht glatte Wege um Hindernisse von einem Ort zum anderen. Für dieses Modell die GKObstacleGraph Klasse wird für den Graphen verwendet GKPolygonObstacle Klasse für Hindernisse und die GKGraphNode2D Klasse für Knoten (Standorte).
  • Ein einfaches 2D-Gitter: In diesem Fall können gültige Positionen nur solche mit ganzzahligen Koordinaten sein. Dieses Diagrammmodell ist hilfreich, wenn Ihre Szene ein eindeutiges Rasterlayout hat und Sie keine glatten Pfade benötigen. Bei Verwendung dieses Modells können sich Objekte nur horizontal oder vertikal in eine Richtung bewegen. Für dieses Modell die GKGridGraph Klasse wird für die Grafik und die GKGridGraphNode Klasse für Knoten.
  • Eine Sammlung von Standorten und den Verbindungen zwischen ihnen: Dies ist das allgemeinste Diagrammmodell und wird für Fälle empfohlen, in denen sich Objekte zwischen verschiedenen Räumen bewegen, deren spezifische Position innerhalb dieses Bereichs jedoch für das Gameplay nicht wesentlich ist. Für dieses Modell die 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:

  • Wir schaffen eine andere GKGraphNode2D Instanz und verbinden Sie diese mit der Grafik.
  • Wir erstellen eine Reihe von Knoten, die einen Pfad bilden, indem Sie die findPathFromNode (_: toNode :) Methode in unserer Grafik.
  • Wenn eine Reihe von Pfadknoten erfolgreich erstellt wurde, erstellen wir daraus einen Pfad. Das Radius Der Parameter arbeitet ähnlich wie der bufferRadius Parameter von vor und definiert, wie weit sich ein Objekt vom erstellten Pfad entfernen kann.
  • Wir schaffen zwei 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.
  • Schließlich erstellen wir mit diesen beiden Zielen ein neues Verhalten und weisen dieses dem Agenten zu.

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.

Fazit

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.