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..
Dans ce tutoriel, je vais vous expliquer deux autres fonctionnalités du framework GameplayKit dont vous pouvez tirer parti:
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..
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:
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:
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:
agents
. Nous allons ajouter cette propriété à la GameScene
classe dans un instant.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..Masse
, vitesse 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.
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:
GKObstacleGraph
la classe est utilisée pour le graphe, le GKPolygonObstacle
classe pour les obstacles, et la GKGraphNode2D
classe pour les nœuds (emplacements).GKGridGraph
la classe est utilisée pour le graphique et la GKGridGraphNode
classe pour les noeuds.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:
GKGraphNode2D
par exemple et connectez cela au graphique.findPathFromNode (_: toNode :)
méthode sur notre graphique.rayon
paramètre fonctionne comme le bufferRadius
paramètre d'avant et définit combien un objet peut s'éloigner du chemin créé.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.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.
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.