Eine Einführung in GameplayKit Teil 3

Dies ist der dritte Teil von Eine Einführung in GameplayKit. Wenn Sie den ersten und den zweiten Teil noch nicht durchgearbeitet haben, empfehle ich, zuerst diese Tutorials zu lesen, bevor Sie mit diesem fortfahren.

Einführung

In diesem dritten und letzten Tutorial werde ich Ihnen zwei weitere Funktionen beibringen, die Sie in Ihren eigenen Spielen verwenden können:

  • Zufallsgeneratoren
  • Regelsysteme

In diesem Lernprogramm verwenden wir zunächst einen der Zufallsgeneratoren von GameplayKit, um den anfänglichen Algorithmus für den Feindlaich zu optimieren. Wir implementieren dann ein grundlegendes Regelsystem in Kombination mit einer anderen Zufallsverteilung, um mit dem Respawn-Verhalten von Feinden umzugehen.

Für dieses Tutorial können Sie Ihre Kopie des abgeschlossenen Projekts aus dem zweiten Tutorial verwenden oder eine neue Kopie des Quellcodes von GitHub herunterladen.

1. Zufallsgeneratoren

Zufällige Werte können in GameplayKit mithilfe einer beliebigen Klasse generiert werden, die der entspricht GKRandom Protokoll. GameplayKit bietet fünf Klassen, die diesem Protokoll entsprechen. Diese Klassen enthalten drei zufällige Quellen und zwei zufällig Ausschüttungen. Der Hauptunterschied zwischen Zufallsquellen und Zufallsverteilungen besteht darin, dass Verteilungen eine Zufallsquelle verwenden, um Werte innerhalb eines bestimmten Bereichs zu erzeugen, und die Ausgabe des Zufallswerts auf verschiedene andere Arten beeinflussen kann.

Die oben genannten Klassen werden vom Framework bereitgestellt, damit Sie die richtige Balance zwischen Leistung und Zufälligkeit für Ihr Spiel finden können. Einige Algorithmen zur Erzeugung von Zufallswerten sind komplexer als andere und beeinflussen daher die Leistung.

Wenn Sie beispielsweise eine Zufallszahl benötigen, die für jeden Frame generiert wird (sechzig Mal pro Sekunde), ist es am besten, einen der schnelleren Algorithmen zu verwenden. Im Gegensatz dazu können Sie, wenn Sie nur selten einen Zufallswert generieren, einen komplexeren Algorithmus verwenden, um bessere Ergebnisse zu erzielen.

Die drei zufälligen Quellklassen, die vom GameplayKit-Framework bereitgestellt werden, sind GKARC4RandomSourceGKLinearCongruentialRandomSource, und GKMersenneTwisterRandomSource.

GKARC4RandomSource

Diese Klasse verwendet den ARC4-Algorithmus und ist für die meisten Zwecke geeignet. Dieser Algorithmus erzeugt eine Reihe von Zufallszahlen, die auf einem Samen basieren. Sie können ein initialisieren GKARC4RandomSource mit einem bestimmten Startwert, wenn Sie zufälliges Verhalten aus einem anderen Teil Ihres Spiels replizieren müssen. Der Samen einer vorhandenen Quelle kann von dessen Quelle abgerufen werden Samen schreibgeschützte Eigenschaft.

GKLinearCongruentialRandomSource

Diese Zufallsquellklasse verwendet den grundlegenden Algorithmus des linearen Kongruenzgenerators. Dieser Algorithmus ist effizienter und bietet bessere Ergebnisse als der ARC4-Algorithmus, generiert jedoch auch weniger zufällige Werte. Sie können eine abholen GKLinearCongruentialRandomSource Keim des Objekts und erstellen Sie eine neue Quelle damit wie ein GKARC4RandomSource Objekt.

GKMersenneTwisterRandomSource

Diese Klasse verwendet die Mersenne Twister Algorithmus und erzeugt die meisten zufälligen Ergebnisse, aber es ist auch am wenigsten effizient. Genau wie die beiden anderen zufälligen Quellklassen können Sie eine GKMersenneTwisterRandomSource Objekt-Seed und verwenden Sie es, um eine neue Quelle zu erstellen.

Die zwei zufälligen Verteilungsklassen in GameplayKit sind GKGaussianDistribution und GKShuffledDistribution.

GKGaussianDistribution

Dieser Verteilungstyp stellt sicher, dass die generierten Zufallswerte einer Gaußschen Verteilung folgen, die auch als Normalverteilung bezeichnet wird. Dies bedeutet, dass der Großteil der generierten Werte in der Mitte des von Ihnen angegebenen Bereichs liegt.

Zum Beispiel, wenn Sie a einrichten GKGaussianDistribution Objekt mit einem Mindestwert von 1, ein maximaler Wert von 10, und eine Standardabweichung von 1, CA 69% der Ergebnisse wäre entweder 4, 5, oder 6. Ich werde diese Verteilung ausführlicher erklären, wenn wir später in diesem Tutorial eine neue hinzufügen.

GKShuffledDistribution

Diese Klasse kann verwendet werden, um sicherzustellen, dass Zufallswerte gleichmäßig über den angegebenen Bereich verteilt sind. Zum Beispiel, wenn Sie Werte zwischen generieren 1 und 10, und ein 4 wird eine andere generiert 4 wird erst generiert, wenn alle anderen Zahlen zwischen 1 und 10 wurden auch generiert.

Es ist jetzt an der Zeit, all dies in die Praxis umzusetzen. Wir werden unserem Spiel zwei zufällige Distributionen hinzufügen. Öffnen Sie Ihr Projekt in Xcode und gehen Sie zu GameScene.swift. Die erste zufällige Verteilung, die wir hinzufügen, ist a GKGaussianDistribution. Später fügen wir auch eine GKShuffledDistribution. Fügen Sie die folgenden zwei Eigenschaften hinzu GameScene Klasse.

var initialSpawnDistribution = GKGaussianDistribution (randomSource: GKARC4RandomSource (), niedrigster Wert: 0, höchster Wert: 2) var respawnDistribution = GKShuffledDistribution (randomSource: GKARC4RandomSource (), niedrigsterWert: 0,

In diesem Snippet erstellen wir zwei Distributionen mit einem Mindestwert von 0 und einen maximalen Wert von 2. Für die GKGaussianDistribution, Mittelwert und Abweichung werden automatisch nach folgenden Gleichungen berechnet:

  • Mittelwert = (Maximum - Minimum) / 2
  • Abweichung = (Maximum - Minimum) / 6

Der Mittelwert einer Gaußschen Verteilung ist der Mittelpunkt und die Abweichung wird verwendet, um zu berechnen, wie viel Prozent der Werte innerhalb eines bestimmten Bereichs vom Mittelwert liegen sollten. Der Prozentsatz der Werte innerhalb eines bestimmten Bereichs beträgt:

  • 68,27% innerhalb einer Abweichung vom Mittelwert
  • 95% innerhalb von 2 Abweichungen vom Mittelwert
  • 100% innerhalb von 3 Abweichungen vom Mittelwert

Dies bedeutet, dass ungefähr 69% der generierten Werte gleich 1 sein sollten. Dies führt zu mehr roten Punkten im Verhältnis zu grünen und gelben Punkten. Damit dies funktioniert, müssen wir das aktualisieren Anfangsspawn Methode.

In dem zum Schleife, ersetzen Sie die folgende Zeile:

let respawnFactor = arc4random ()% 3 // Erzeugt einen Wert zwischen 0 und 2 (einschließlich)

mit den folgenden:

Lassen Sie respawnFactor = self.initialSpawnDistribution.nextInt ()

Das nextInt Die Methode kann für jedes Objekt aufgerufen werden, das dem entspricht GKRandom Protokoll und gibt einen zufälligen Wert basierend auf der Quelle und ggf. der verwendeten Verteilung zurück.

Erstellen und starten Sie Ihre App und bewegen Sie sich auf der Karte. Sie sollten viel mehr rote Punkte im Vergleich zu grünen und gelben Punkten sehen.

Die zweite Zufallsverteilung, die wir im Spiel verwenden werden, wird beim Umgang mit dem auf Regeln basierenden Respawn-Verhalten zum Tragen kommen.

2. Regelsysteme

GameplayKit-Regelsysteme werden verwendet, um die bedingte Logik in Ihrem Spiel besser zu organisieren und eine Fuzzy-Logik einzuführen. Durch die Einführung der Fuzzy-Logik können Sie Entitäten innerhalb Ihres Spiels dazu bringen, Entscheidungen auf der Grundlage verschiedener Regeln und Variablen zu treffen, z. B. der Spielergesundheit, der aktuellen Feindzahl und der Entfernung zum Feind. Dies kann im Vergleich zu einfach sehr vorteilhaft sein ob und Schalter Aussagen.

Regelsysteme, vertreten durch GKRuleSystem Klasse, haben drei Schlüsselteile:

  • Agenda. Dies ist der Satz von Regeln, die dem Regelsystem hinzugefügt wurden. Standardmäßig werden diese Regeln in der Reihenfolge ausgewertet, in der sie dem Regelsystem hinzugefügt werden. Sie können das ändern salience Eigenschaft einer Regel, die angegeben werden soll, wann sie ausgewertet werden soll.
  • Zustandsinformationen. Das Zustand Eigentum von a GKRuleSystem object ist ein Wörterbuch, zu dem Sie beliebige Daten hinzufügen können, einschließlich benutzerdefinierter Objekttypen. Diese Daten können dann von den Regeln des Regelsystems verwendet werden, wenn das Ergebnis zurückgegeben wird.
  • Fakten. Fakten innerhalb eines Regelsystems stellen die Schlussfolgerungen dar, die aus der Bewertung von Regeln gezogen werden. Eine Tatsache kann auch durch einen beliebigen Objekttyp in Ihrem Spiel dargestellt werden. Jede Tatsache hat auch eine Entsprechung Mitgliedsnote, Welches ist ein Wert zwischen 0,0 und 1,0. Diese Mitgliedschaftsnote stellt die Einbeziehung oder Anwesenheit der Tatsache innerhalb des Regelsystems dar.

Regeln selbst, vertreten durch die GKRule Klasse haben zwei Hauptkomponenten:

  • Prädikat. Dieser Teil der Regel gibt einen booleschen Wert zurück, der angibt, ob die Anforderungen der Regel erfüllt wurden. Das Prädikat einer Regel kann mithilfe von erstellt werden NSPredicate object oder, wie in diesem Tutorial beschrieben, einen Codeblock.
  • Aktion. Wenn das Prädikat der Regel zurückkehrt wahr, Ihre Aktion wird ausgeführt. Diese Aktion ist ein Codeblock, in dem Sie eine beliebige Logik ausführen können, wenn die Anforderungen der Regel erfüllt sind. Hier können Sie im Allgemeinen Tatsachen innerhalb des übergeordneten Regelsystems geltend machen (hinzufügen) oder zurückziehen (entfernen).

Mal sehen, wie das alles in der Praxis funktioniert. Für unser Regelsystem werden wir drei Regeln erstellen, die Folgendes berücksichtigen:

  • die Entfernung vom Spawnpunkt zum Spieler. Wenn dieser Wert relativ klein ist, wird das Spiel wahrscheinlich rote Gegner hervorbringen.
  • die aktuelle Knotenanzahl der Szene. Wenn dies zu hoch ist, möchten wir nicht, dass weitere Punkte zur Szene hinzugefügt werden.
  • ob ein Punkt bereits am Spawnpunkt vorhanden ist oder nicht. Wenn dies nicht der Fall ist, wollen wir hier einen Punkt erzeugen.

Fügen Sie zunächst die folgende Eigenschaft zum hinzu GameScene Klasse:

var ruleSystem = GKRuleSystem ()

Fügen Sie als Nächstes das folgende Codeausschnitt hinzu didMoveToView (_ :) Methode:

let playerDistanceRule = GKRule (blockPredicate: (system: GKRuleSystem) -> Bool in, wenn value = system.state ["spawnPoint"] als? NSValue let point = value.CGPointValue () let xDistance = abs (point.x - self.playerNode.position.x) let yDistance = abs (point.y - self.playerNode.position.y) let totalDistance = sqrt ((xDistance * xDistance) + (yDistance * yDistance)) wenn totalDistance <= 200  return true  else  return false   else  return false  )  (system: GKRuleSystem) -> Void in system.assertFact ("spawnEnemy") Lasse nodeCountRule = GKRule (blockPredicate: (system: GKRuleSystem) -> Bool in if self.children.count <= 50  return true  else  return false  )  (system: GKRuleSystem) -> Void in system.assertFact ("shouldSpawn", Bewertung: 0,5) nodePresentRule = GKRule (blockPredicate: (system: GKRuleSystem) -> Bool in if let value = system.state ["spawnPoint"] als? NSValue wo selbst. nodeAtPoint (value.CGPointValue ()). count == 0 return true else return false) ) (System: GKRuleSystem) -> Nicht gültig in grade Klasse = system.gradeForFact ("shouldSpawn") system.assertFact (" shouldSpawn ", Benotung: (Note + 0,5)) self.ruleSystem.addRulesFromArray ([playerDistanceRule, nodeCountRule, nodePresentRule])

Mit diesem Code erstellen wir drei GKRule Objekte und fügen Sie sie dem Regelsystem hinzu. Die Regeln behaupten eine bestimmte Tatsache in ihrem Aktionsblock. Wenn Sie keinen Notenwert angeben, rufen Sie einfach die assertFact (_ :) Methode, wie wir es mit der playerDistanceRule, der Tatsache wird eine Standardnote von 1,0.

Sie werden das für das bemerken nodeCountRule wir behaupten nur das "shouldSpawn" Tatsache mit einer Note von 0,5. Das nodePresentRule dann behauptet diese gleiche Tatsache und addiert einen Notenwert von 0,5. Dies geschieht so, dass, wenn wir die Tatsache später prüfen, ein Notenwert von 1,0 bedeutet, dass beide Regeln erfüllt wurden.

Sie werden auch sehen, dass beide playerDistanceRule und nodePresentRule Greife auf ... zu "Spawnpunkt" Wert des Regelsystems Zustand Wörterbuch. Wir werden diesen Wert vor der Auswertung des Regelsystems zuweisen.

Finden Sie schließlich das Respawn Methode in der GameScene Klasse mit der folgenden Implementierung:

func respawn () let endNode = GKGraphNode2D (Punkt: float2 (x: 2048.0, y: 2048.0)) self.graph.connectNodeUsingacles (endNode) für einen Punkt in self.spawnPoints self.ruleSystem.reset () self.ruleSystem.state ["spawnPoint"] = NSValue (CGPoint: point) self.ruleSystem.evaluate () if self.ruleSystem.gradeForFact ("shouldSpawn") == 1.0 var respawnFactor = self.respawnDistribution.nextInt () if self.ruleSystem.gradeForFact ("spawnEnemy") == 1.0 respawnFactor = self.initialSpawnDistribution.nextInt () 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 startNode = GKGraphNode2D (point: agent.position) self.graph.connectNodeUsingacles (startNode) let pathNodes = self.graph.findPathFromNode (startNode, toNode: endNode) als! [GKGraphNode2D] if! PathNodes.isEmpty let path = GKPath (graphNodes: pathNodes, radius: 1.0) let followPath = GKGoal (toFollowPath: path, maxPredictionTime: 1.0, forward: true) let stayOnPath =atKanal (toStayOnPath): 1.0) let Verhalten = GKBehavior (Ziele: [followPath, stayOnPath]) agent.behavior = Verhalten self.graph.removeNodes ([startNode]) 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])

Diese Methode wird einmal pro Sekunde aufgerufen und ähnelt der Anfangsspawn Methode. Es gibt eine Reihe wichtiger Unterschiede in der zum Schleife aber.

  • Wir setzen das Regelsystem zuerst zurück, indem wir es aufrufen zurücksetzen Methode. Dies muss geschehen, wenn ein Regelsystem sequentiell ausgewertet wird. Dadurch werden alle behaupteten Fakten und zugehörigen Daten entfernt, um sicherzustellen, dass keine Informationen aus der vorherigen Auswertung übrig bleiben, die die nächste stören könnten.
  • Wir weisen den Spawnpunkt dann dem Regelsystem zu Zustand Wörterbuch. Wir benutzen ein NSValue Objekt, weil die CGPoint Datentyp entspricht nicht dem von Swift AnyObject Protokoll und kann diesem nicht zugeordnet werden NSMutableDictionary Eigentum.
  • Wir bewerten das Regelsystem, indem wir es aufrufen bewerten Methode.
  • Anschließend rufen wir die Mitgliedschaftsstufe des Regelsystems für das System ab "shouldSpawn" Tatsache. Wenn das gleich ist 1, wir setzen den Punkt fort.
  • Zum Schluss überprüfen wir die Note des Regelsystems für "spawnEnemy" Tatsache und, wenn gleich 1, Verwenden Sie den normalverteilten Zufallsgenerator, um unseren zu erstellen SpawnFactor.

Der Rest des Respawn Methode ist die gleiche wie die Anfangsspawn Methode. Bauen Sie Ihr Spiel ein letztes Mal auf und führen Sie es aus. Auch ohne sich zu bewegen, werden neue Punkte angezeigt, wenn die erforderlichen Bedingungen erfüllt sind.

Fazit

In dieser Serie von GameplayKit haben Sie viel gelernt. Fassen wir kurz zusammen, was wir behandelt haben.

  • Entitäten und Komponenten
  • Zustandsmaschinen
  • Agenten, Ziele und Verhalten
  • Wegfindung
  • Zufallsgeneratoren
  • Regelsysteme

GameplayKit ist eine wichtige Ergänzung zu iOS 9 und OS X El Capitan. Es beseitigt viele Komplexitäten der Spielentwicklung. Ich hoffe, dass diese Serie Sie dazu motiviert hat, mehr mit dem Rahmen zu experimentieren und herauszufinden, wozu er in der Lage ist.

Bitte hinterlassen Sie wie immer Ihre Kommentare und Rückmeldungen.