Une introduction à GameplayKit Partie 2

Ceci est la deuxième partie de Une introduction à GameplayKit. Si vous n'avez pas encore lu la première partie, je vous recommande de lire ce tutoriel avant de continuer avec celui-ci..

introduction

Dans ce tutoriel, je vais vous expliquer deux autres fonctionnalités du framework GameplayKit dont vous pouvez tirer parti:

  • agents, objectifs et comportements
  • Trouver son chemin

En utilisant des agents, des objectifs et des comportements, nous allons intégrer une intelligence artificielle (AI) de base au jeu que nous avons commencé dans la première partie de cette série. L'IA permettra à nos points ennemis rouges et jaunes de cibler et de nous déplacer vers notre point joueur bleu. Nous allons également implémenter le pathfinding pour étendre cette intelligence artificielle afin de pouvoir contourner les obstacles..

Pour ce tutoriel, vous pouvez utiliser votre copie du projet terminé de la première partie de cette série ou télécharger une nouvelle copie du code source de GitHub..

1. Agents, objectifs et comportements

Dans GameplayKit, les agents, les objectifs et les comportements sont utilisés en combinaison pour définir la manière dont différents objets se déplacent les uns par rapport aux autres tout au long de votre scène. Pour un seul objet (ou SKShapeNode dans notre jeu), vous commencez par créer un agent, représenté par le GKAgent classe. Cependant, pour les jeux 2D, comme le nôtre, nous devons utiliser le béton GKAgent2D classe.

le GKAgent classe est une sous-classe de GKComponent. Cela signifie que votre jeu doit utiliser une structure à base d'entités et de composants, comme je vous l'ai montré dans le premier tutoriel de cette série..

Les agents représentent la position, la taille et la vitesse d'un objet. Vous ajoutez ensuite un comportement, représenté par le GKBehaviour classe, à cet agent. Enfin, vous créez un ensemble de buts, représenté par le GKGoal classe, et les ajouter à l'objet de comportement. Les objectifs peuvent être utilisés pour créer de nombreux éléments de jeu différents, par exemple:

  • se diriger vers un agent
  • s'éloigner d'un agent
  • regroupement rapproché avec d'autres agents
  • errant autour d'une position spécifique

Votre objet de comportement surveille et calcule tous les objectifs que vous lui ajoutez, puis relaie ces données à l'agent. Voyons comment cela fonctionne dans la pratique.

Ouvrez votre projet Xcode et accédez à PlayerNode.swift. Nous devons d’abord nous assurer que le PlayerNode classe conforme à la GKAgentDelegate protocole.

class PlayerNode: SKShapeNode, GKAgentDelegate …. 

Ensuite, ajoutez le bloc de code suivant à la PlayerNode classe.

var agent = GKAgent2D () // MARK: Délégué de l'agent en fonction de l'agentWillUpdate (agent: GKAgent) si laissez agent2D = agent en tant que? GKAgent2D agent2D.position = float2 (Float (position.x), Float (position.y)) func agentDidUpdate (agent: GKAgent) si laissez agent2D = agent comme? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))

Nous commençons par ajouter une propriété à la PlayerNode classe afin que nous ayons toujours une référence à l'objet agent du joueur actuel. Ensuite, nous implémentons les deux méthodes de GKAgentDelegate protocole. En implémentant ces méthodes, nous nous assurons que le point du joueur affiché à l'écran reflétera toujours les modifications apportées par GameplayKit..

le agentWillUpdate (_ :) Cette méthode est appelée juste avant que GameplayKit examine le comportement et les objectifs de cet agent pour déterminer où il doit être déplacé. De même, le agentDidUpdate (_ :) la méthode est appelée juste après que GameplayKit ait terminé ce processus.

Notre implémentation de ces deux méthodes garantit que le nœud que nous voyons à l'écran reflète les modifications apportées par GameplayKit et que GameplayKit utilise la dernière position du nœud lors de ses calculs..

Ensuite, ouvrez ContactNode.swift et remplacez le contenu du fichier par l'implémentation suivante:

import UIKit import SpriteKit import GameplayKit classe ContactNode: SKShapeNode, GKAgentDelegate var agent = GKAgent2D () // MARK: Délégué de l'agent func agentWillUpdate (agent: GKAgent) si laissez agent2D = agent comme? GKAgent2D agent2D.position = float2 (Float (position.x), Float (position.y)) func agentDidUpdate (agent: GKAgent) si laissez agent2D = agent comme? GKAgent2D self.position = CGPoint (x: CGFloat (agent2D.position.x), y: CGFloat (agent2D.position.y))

En mettant en œuvre le GKAgentDelegate protocole dans le ContactNode classe, nous permettons à tous les autres points de notre jeu d’être à jour avec GameplayKit ainsi que notre lecteur.

Il est maintenant temps de définir les comportements et les objectifs. Pour que cela fonctionne, nous devons prendre en compte trois choses:

  • Ajouter l'agent du nœud du lecteur à son entité et définir son délégué.
  • Configurez les agents, les comportements et les objectifs pour tous nos points ennemis.
  • Mettre à jour tous ces agents au bon moment.

Tout d'abord, ouvrir GameScene.swift et à la fin de la didMoveToView (_ :) méthode, ajoutez les deux lignes de code suivantes:

playerNode.entity.addComponent (playerNode.agent) playerNode.agent.delegate = playerNode

Avec ces deux lignes de code, nous ajoutons l'agent en tant que composant et définissons le délégué de l'agent sur le noeud lui-même..

Ensuite, remplacez la mise en œuvre de la initialSpawn méthode avec l'implémentation suivante:

func initialSpawn () pour le point dans self.spawnPoints let respawnFactor = arc4random ()% 3 // Produira une valeur comprise entre 0 et 2 (inclus) noeud de variable: SKShapeNode? = nil switch respawnFactor case 0: noeud = PointsNode (circleOfRadius: 25) noeud! !physicsBody = SKPhysicsBody (circleOfRadius: 25) noeud! .fillColor = UIColor.greenColor () cas 1: noeud = RedEnemyNode (circleOfRadius: 75)!! .physicsBody = SKPhysicsBody (circleOfRadius: 75) noeud! !fillColor = UIColor.redColor () cas 2: noeud = YellowEnemyNode (circleOfRadius: 50) noeud! .physicsBody = SKPhysicsBody (circleOfRadius: 50) noeud! .physicsBody = SKPhysicsBody (circleOfRadius: 50) noeud! ) default: break si let entity = node? .valueForKey ("entity") as? GKEntity, laissez agent = node? .ValueForKey ("agent") comme? GKAgent2D où respawnFactor! = 0 entity.addComponent (agent) agent.delegate = noeud comme? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (agent) let behavior = GKBehavior (objectif: GKGo (toSeekAgent: playerNode.agent), poids: 1,0 ) agent.behavior = comportement agent.mass = 0.01 agent.maxSpeed ​​= 50 agent.maxAcceleration = 1000 noeud! .position = noeud de point! .strokeColor = noeud UIColor.clearColor ()! .physicsBody! .contactTestBitMask = 1 self.addChild (noeud!)

Le code le plus important que nous avons ajouté est situé dans le si déclaration qui suit la commutateur déclaration. Passons en revue ce code ligne par ligne:

  • Nous ajoutons d'abord l'agent à l'entité en tant que composant et configurons son délégué.
  • Ensuite, nous affectons la position de l'agent et ajoutons l'agent à un tableau stocké., agents. Nous allons ajouter cette propriété à la GameScene classe dans un instant.
  • Nous créons ensuite un GKBehavior objet avec un seul GKGoal pour cibler l'agent du joueur actuel. le poids paramètre dans cet initialiseur est utilisé pour déterminer quels objectifs doivent avoir la priorité sur les autres. Par exemple, imaginons que vous ayez pour objectif de cibler un agent particulier et que vous souhaitiez vous éloigner d'un autre agent, mais que vous souhaitiez que l'objectif de ciblage prenne la préférence. Dans ce cas, vous pouvez attribuer à l'objectif de ciblage un poids de 1 et l'objectif d'éloignement un poids de 0.5. Ce comportement est ensuite attribué à l'agent du noeud ennemi..
  • Enfin, nous configurons le Massevitesse maximale, et maxAcceleration propriétés de l'agent. Celles-ci affectent la vitesse à laquelle les objets peuvent bouger et tourner. N'hésitez pas à jouer avec ces valeurs et à voir comment elles affectent le mouvement des points ennemis.

Ensuite, ajoutez les deux propriétés suivantes à la GameScene classe:

agents var: [GKAgent2D] = [] var lastUpdateTime: CFTimeInterval = 0.0

le agents array sera utilisé pour garder une référence aux agents ennemis dans la scène. le lastUpdateTime la propriété sera utilisée pour calculer le temps écoulé depuis la dernière mise à jour de la scène.

Enfin, remplacer la mise en œuvre de la mettre à jour(_:) méthode du GameScene classe avec l'implémentation suivante:

override func update (currentTime: CFTimeInterval) / * Appelé avant le rendu de chaque image * / self.camera?.position = playerNode.position if self.lastUpdateTime == 0 lastUpdateTime = currentTime let delta = currentTime - lastUpdateTime lastUpdateTime = currentTime playerNode.agent.updateWithDeltaTime (delta) pour l'agent dans les agents agent.updateWithDeltaTime (delta)

dans le mettre à jour(_:) méthode, nous calculons le temps écoulé depuis la dernière mise à jour de la scène, puis mettons à jour les agents avec cette valeur.

Générez et exécutez votre application et commencez à vous déplacer dans la scène. Vous verrez que les points ennemis commenceront lentement à se déplacer vers vous..

Comme vous pouvez le constater, bien que les points ennemis ciblent le joueur actuel, ils ne naviguent pas autour des barrières blanches, mais tentent plutôt de les traverser. Rendons les ennemis un peu plus malins avec Pathfinding.

2. Pathfinding

Avec le framework GameplayKit, vous pouvez ajouter des chemins complexes à votre jeu en combinant des corps physiques avec des classes et des méthodes GameplayKit. Pour notre jeu, nous allons le mettre en place de manière à ce que les points ennemis ciblent le point du joueur tout en contournant les obstacles..

Pathfinding dans GameplayKit commence par créer un graphique de votre scène. Ce graphique est un ensemble de localisations individuelles, également appelées noeuds, et les connexions entre ces endroits. Ces connexions définissent comment un objet particulier peut se déplacer d'un endroit à un autre. Un graphique peut modéliser les chemins disponibles dans votre scène de trois manières différentes:

  • Un espace continu contenant des obstacles: Ce modèle de graphique permet des chemins lisses autour des obstacles d'un endroit à un autre. Pour ce modèle, le GKObstacleGraph la classe est utilisée pour le graphe, le GKPolygonObstacle classe pour les obstacles, et la GKGraphNode2D classe pour les nœuds (emplacements).
  • Une simple grille 2D: Dans ce cas, les emplacements valides ne peuvent être que ceux avec des coordonnées entières. Ce modèle de graphique est utile lorsque votre scène a une disposition de grille distincte et que vous n’avez pas besoin de tracés lisses. Lors de l'utilisation de ce modèle, les objets ne peuvent se déplacer horizontalement ou verticalement dans une seule direction à la fois. Pour ce modèle, le GKGridGraph la classe est utilisée pour le graphique et la GKGridGraphNode classe pour les noeuds.
  • Une collection de lieux et les liens entre eux: Il s'agit du modèle de graphique le plus générique. Il est recommandé dans les cas où des objets se déplacent entre des espaces distincts, mais leur emplacement spécifique dans cet espace n'est pas essentiel au gameplay. Pour ce modèle, le GKGraph la classe est utilisée pour le graphique et la GKGraphNode classe pour les noeuds.

Parce que nous voulons que le joueur dans notre jeu contourne les barrières blanches, nous allons utiliser la GKObstacleGraph classe pour créer un graphique de notre scène. Pour commencer, remplacez le spawnPoints propriété dans le GameScene classe avec les éléments suivants:

let 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!

le spawnPoints array contient des emplacements de spawn modifiés aux fins de ce tutoriel. En effet, actuellement, GameplayKit ne peut calculer que des chemins entre des objets relativement proches les uns des autres..

En raison de la grande distance par défaut entre les points dans ce jeu, deux nouveaux points de spawn doivent être ajoutés pour illustrer le parcours. Notez que nous déclarons aussi un graphique propriété de type GKObstacleGraph pour garder une référence au graphique que nous allons créer.

Ensuite, ajoutez les deux lignes de code suivantes au début de la didMoveToView (_ :) méthode:

let obstacles = SKNode.obstaclesFromNodePhysicsBodies (self.children) graph = GKObstacleGraph (obstacles: obstacles, bufferRadius: 0.0)

En première ligne, nous créons un tableau d'obstacles à partir des corps physiques de la scène. Nous créons ensuite l'objet graphique en utilisant ces obstacles. le bufferRadius Le paramètre de cet initialiseur peut être utilisé pour forcer les objets à ne pas entrer à une certaine distance de ces obstacles. Ces lignes doivent être ajoutées au début de la didMoveToView (_ :) méthode, car le graphique que nous créons est nécessaire au moment où le initialSpawn la méthode s'appelle.

Enfin, remplacez le initialSpawn méthode avec l'implémentation suivante:

func initialSpawn () let endNode = GKGraphNode2D (point: float2 (x: 2048.0, y: 2048.0)) self.graph.connectNodeUsingObstacles (endNode) pour le point dans self.spawnPoints (laisser respawnFactor = arc4random ()% 3 // produira une valeur comprise entre 0 et 2 (inclus) var noeud: SKShapeNode? = nil switch respawnFactor case 0: noeud = PointsNode (circleOfRadius: 25) noeud! !physicsBody = SKPhysicsBody (circleOfRadius: 25) noeud! .fillColor = UIColor.greenColor () cas 1: noeud = RedEnemyNode (circleOfRadius: 75)!! .physicsBody = SKPhysicsBody (circleOfRadius: 75) noeud! !fillColor = UIColor.redColor () cas 2: noeud = YellowEnemyNode (circleOfRadius: 50) noeud! .physicsBody = SKPhysicsBody (circleOfRadius: 50) noeud! .physicsBody = SKPhysicsBody (circleOfRadius: 50) noeud! ) default: break si let entity = node? .valueForKey ("entity") as? GKEntity, laissez agent = node? .ValueForKey ("agent") comme? GKAgent2D où respawnFactor! = 0 entity.addComponent (agent) agent.delegate = noeud comme? ContactNode agent.position = float2 (x: Float (point.x), y: Float (point.y)) agents.append (agent) / * let behavior = GKBehavior (objectif: GKGoal (toSeekAgent: playerNode.agent), poids : 1.0) agent.behavior = comportement * / / *** BEGIN PATHFINDING *** / let startNode = GKGraphNode2D (point: agent.position) self.graph.connectNodeUsingObstacles (startNode) let pathNodes = self.graph.findPathFromNode (startNode, toNode: endNode) en tant que! [GKGraphNode2D] if! PathNodes.isEmpty let path = GKPath (graphNodes: pathNodes, rayon: 1.0) let followPath = GKGoal (toFollowPath: path, maxPredictionTime: 1.0, forward: true) let stayOnPath = GKGoal (toStayOmPall): 1.0) let behavior = GKBehavior (objectifs: [followPath, stayOnPath]) agent.behavior = comportement self.graph.removeNodes ([startNode]) / *** FIN PATHFINDING *** / agent.mass = 0.01 agent.maxSpeed ​​= 50 agent.maxAcceleration = 1000 noeud! .Position = noeud de point! .StrokeColor = noeud UIColor.clearColor ()! .PhysicsBody! .ContactTestBitMask = 1 self.addChild (noeud!) Self.graph.removeNodes ([endNode]) 

Nous commençons la méthode en créant un GKGraphNode2D objet avec les coordonnées d'apparition du joueur par défaut. Ensuite, nous connectons ce noeud au graphe pour qu’il puisse être utilisé lors de la recherche de chemins..

La plupart initialSpawn la méthode reste inchangée. J'ai ajouté quelques commentaires pour vous montrer où se trouve la partie pathfinding du code dans le premier si déclaration. Passons en revue ce code étape par étape:

  • Nous créons un autre GKGraphNode2D par exemple et connectez cela au graphique.
  • Nous créons une série de nœuds qui constituent un chemin en appelant le findPathFromNode (_: toNode :) méthode sur notre graphique.
  • Si une série de nœuds de chemin a été créée avec succès, nous créons un chemin à partir de ceux-ci. le rayon paramètre fonctionne comme le bufferRadius paramètre d'avant et définit combien un objet peut s'éloigner du chemin créé.
  • Nous créons deux GKGoal objets, un pour suivre le chemin et un autre pour rester sur le chemin. le maxPredictionTime paramètre permet à l'objectif de calculer du mieux possible à l'avance si quelque chose va interrompre la poursuite / le maintien de l'objet sur ce chemin particulier.
  • Enfin, nous créons un nouveau comportement avec ces deux objectifs et l'attribuons à l'agent..

Vous remarquerez également que nous supprimons les nœuds créés du graphique une fois que nous en avons terminé. Ceci est une bonne pratique à suivre car elle garantit que les nœuds que vous avez créés n'interfèrent pas ultérieurement avec d'autres calculs de recherche de chemin..

Construisez et exécutez votre application une dernière fois et vous verrez deux points apparaître tout près de vous et commencer à se déplacer vers vous. Vous devrez peut-être exécuter le jeu plusieurs fois s'ils apparaissent tous les deux sous forme de points verts..

Important!

Dans ce didacticiel, nous avons utilisé la fonctionnalité de recherche de parcours de GameplayKit pour permettre aux points ennemis de cibler le point du joueur autour des obstacles. Notez que ceci était juste pour un exemple pratique de pathfinding.

Pour un jeu de production réel, il serait préférable d'implémenter cette fonctionnalité en combinant l'objectif de ciblage des joueurs de ce didacticiel avec un objectif permettant d'éviter les obstacles créé avec l'outil init (toAvoidObstacles: maxPredictionTime :) méthode de commodité, que vous pouvez lire plus dans le GKGoal Référence de classe.

Conclusion

Dans ce didacticiel, je vous ai montré comment utiliser les agents, les objectifs et les comportements dans les jeux comportant une structure de composant entité. Bien que nous n’ayions créé que trois objectifs dans ce didacticiel, vous en trouverez de nombreux autres sur lesquels vous pourrez en savoir plus à propos de GKGoal Référence de classe.

Je vous ai également montré comment implémenter des parcours avancés dans votre jeu en créant un graphique, un ensemble d'obstacles et des objectifs pour suivre ces chemins..

Comme vous pouvez le constater, de nombreuses fonctionnalités sont mises à votre disposition via le framework GameplayKit. Dans la troisième et dernière partie de cette série, je vais vous expliquer les générateurs de valeurs aléatoires de GameplayKit et comment créer votre propre système de règles pour introduire une logique floue dans votre jeu..

Comme toujours, assurez-vous de laisser vos commentaires ci-dessous.