Une introduction à GameplayKit Partie 3

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

introduction

Dans ce troisième et dernier tutoriel, je vais vous expliquer deux autres fonctionnalités que vous pouvez utiliser dans vos propres jeux:

  • générateurs de valeurs aléatoires
  • systèmes de règles

Dans ce didacticiel, nous allons d’abord utiliser l’un des générateurs de valeurs aléatoires de GameplayKit pour optimiser notre algorithme initial de génération d’ennemis. Nous allons ensuite mettre en œuvre un système de règles de base en combinaison avec une autre distribution aléatoire pour gérer le comportement de réapparition des ennemis.

Pour ce tutoriel, vous pouvez utiliser votre copie du projet terminé du deuxième tutoriel ou télécharger une nouvelle copie du code source à partir de GitHub..

1. Générateurs de valeur aléatoire

Des valeurs aléatoires peuvent être générées dans GameplayKit à l’aide de toute classe conforme à la GKRandom protocole. GameplayKit fournit cinq classes conformes à ce protocole. Ces classes contiennent trois variables aléatoires sources et deux au hasard les distributions. La principale différence entre les sources aléatoires et les distributions aléatoires est que les distributions utilisent une source aléatoire pour produire des valeurs dans une plage spécifique et peuvent manipuler la sortie des valeurs aléatoires de diverses autres manières..

Les classes susmentionnées sont fournies par le framework afin que vous puissiez trouver le bon équilibre entre performances et aléas pour votre jeu. Certains algorithmes générateurs de valeur aléatoires sont plus complexes que d’autres et ont donc un impact sur les performances..

Par exemple, si vous avez besoin d’un nombre aléatoire généré à chaque image (soixante fois par seconde), il est préférable d’utiliser l’un des algorithmes les plus rapides. En revanche, si vous générez rarement une valeur aléatoire, vous pouvez utiliser un algorithme plus complexe afin de produire de meilleurs résultats..

Les trois classes de sources aléatoires fournies par le framework GameplayKit sont GKARC4RandomSourceGKLinearCongruentialRandomSource, et GKMersenneTwisterRandomSource.

GKARC4RandomSource

Cette classe utilise l'algorithme ARC4 et convient à la plupart des applications. Cet algorithme fonctionne en produisant une série de nombres aléatoires basés sur une graine. Vous pouvez initialiser un GKARC4RandomSource avec une graine spécifique si vous devez reproduire un comportement aléatoire d’une autre partie de votre jeu. La graine d'une source existante peut être extraite de sa la graine propriété en lecture seule.

GKLinearCongruentialRandomSource

Cette classe de source aléatoire utilise l'algorithme de base du générateur congruentiel linéaire. Cet algorithme est plus efficace et plus performant que l’algorithme ARC4, mais il génère également des valeurs moins aléatoires. Vous pouvez aller chercher un GKLinearCongruentialRandomSource la graine de l'objet et créer une nouvelle source avec elle de la même manière qu'un GKARC4RandomSource objet.

GKMersenneTwisterRandomSource

Cette classe utilise le Mersenne Twister algorithme et génère les résultats les plus aléatoires, mais il est également le moins efficace. Tout comme les deux autres classes sources aléatoires, vous pouvez récupérer un GKMersenneTwisterRandomSource la graine de l'objet et l'utiliser pour créer une nouvelle source.

Les deux classes de distribution aléatoire dans GameplayKit sont GKGaussianDistribution et GKShuffledDistribution.

GKGaussianDistribution

Ce type de distribution garantit que les valeurs aléatoires générées suivent une distribution gaussienne, également appelée distribution normale. Cela signifie que la majorité des valeurs générées se situeront au milieu de la plage spécifiée..

Par exemple, si vous configurez un GKGaussianDistribution objet avec une valeur minimale de 1, une valeur maximale de dix, et un écart type de 1, environ 69% des résultats serait soit 4, 5, ou 6. Je vais expliquer cette distribution plus en détail lorsque nous en ajouterons un à notre jeu plus tard dans ce tutoriel..

GKShuffledDistribution

Cette classe peut être utilisée pour s'assurer que les valeurs aléatoires sont uniformément réparties sur la plage spécifiée. Par exemple, si vous générez des valeurs entre 1 et dix, et un 4 est généré, un autre 4 ne sera pas généré avant que tous les autres nombres entre 1 et dix ont également été générés.

Il est maintenant temps de mettre tout cela en pratique. Nous allons ajouter deux distributions aléatoires à notre jeu. Ouvrez votre projet dans Xcode et allez à GameScene.swift. La première distribution aléatoire que nous ajouterons est un GKGaussianDistribution. Plus tard, nous ajouterons également un GKShuffledDistribution. Ajoutez les deux propriétés suivantes à la GameScene classe.

var initialSpawnDistribution = GKGaussianDistribution (randomSource: GKARC4RandomSource (), vautValeur: 0, valeur la plus élevée: 2) var respawnDistribution = GKShuffledDistribution (randomSource: GKARC4RandomSource (), valeur la plus élevée: 2)

Dans cet extrait, nous créons deux distributions avec une valeur minimale de 0 et une valeur maximale de 2. Pour le GKGaussianDistribution, la moyenne et l'écart sont automatiquement calculés selon les équations suivantes:

  • moyenne = (maximum - minimum) / 2
  • écart = (maximum - minimum) / 6

La moyenne d'une distribution gaussienne est son point médian et l'écart est utilisé pour calculer quel pourcentage de valeurs doit se situer dans une certaine plage par rapport à la moyenne. Le pourcentage de valeurs dans une certaine plage est:

  • 68,27% à 1 écart de la moyenne
  • 95% à moins de 2 déviations de la moyenne
  • 100% dans les 3 écarts par rapport à la moyenne

Cela signifie qu'environ 69% des valeurs générées doivent être égales à 1. Cela se traduira par davantage de points rouges proportionnellement aux points verts et jaunes. Pour que cela fonctionne, nous devons mettre à jour le initialSpawn méthode.

dans le pour boucle, remplace la ligne suivante:

let respawnFactor = arc4random ()% 3 // produira une valeur comprise entre 0 et 2 (inclus)

avec ce qui suit:

let respawnFactor = self.initialSpawnDistribution.nextInt ()

le nextInt méthode peut être appelée sur n’importe quel objet conforme à la GKRandom protocole et renverra une valeur aléatoire en fonction de la source et, le cas échéant, de la distribution que vous utilisez.

Générez et exécutez votre application, puis déplacez-vous sur la carte. Vous devriez voir beaucoup plus de points rouges par rapport aux points verts et jaunes.

La deuxième distribution aléatoire que nous utiliserons dans le jeu entrera en jeu lors du traitement du comportement de réapparition basé sur un système de règles.

2. Systèmes de règles

Les systèmes de règles GameplayKit sont utilisés pour mieux organiser la logique conditionnelle au sein de votre jeu et également pour introduire la logique floue. En introduisant une logique floue, vous pouvez obliger les entités de votre jeu à prendre des décisions en fonction de différentes règles et variables, telles que l'état de santé du joueur, le nombre d'ennemis actuels et la distance qui le sépare. Cela peut être très avantageux par rapport au simple si et commutateur des déclarations.

Systèmes de règles, représentés par le GKRuleSystem classe, ont trois parties clés pour eux:

  • Ordre du jour. C'est l'ensemble de règles qui ont été ajoutées au système de règles. Par défaut, ces règles sont évaluées dans l'ordre dans lequel elles ont été ajoutées au système de règles. Vous pouvez changer le saillance propriété de n'importe quelle règle pour spécifier quand vous voulez qu'elle soit évaluée.
  • Informations d'état. le Etat propriété d'un GKRuleSystem objet est un dictionnaire auquel vous pouvez ajouter toutes les données, y compris les types d'objet personnalisé. Ces données peuvent ensuite être utilisées par les règles du système de règles lors du renvoi du résultat..
  • Faits. Les faits dans un système de règles représentent les conclusions tirées de l'évaluation des règles. Un fait peut également être représenté par n'importe quel type d'objet dans votre jeu. Chaque fait a aussi un correspondant grade d'adhésion, qui est une valeur entre 0.0 et 1,0. Cette classe de membre représente l'inclusion ou la présence du fait dans le système de règles.

Les règles elles-mêmes, représentées par le GKRule classe, ont deux composantes principales:

  • Prédicat. Cette partie de la règle renvoie une valeur booléenne indiquant si les exigences de la règle ont été remplies. Un prédicat de règle peut être créé en utilisant un NSPredicate objet ou, comme nous le ferons dans ce tutoriel, un bloc de code.
  • action. Quand le prédicat de la règle retourne vrai, c'est l'action est exécutée. Cette action est un bloc de code dans lequel vous pouvez exécuter toute logique si les exigences de la règle ont été satisfaites. C’est là que vous affirmez (ajoutez) ou retirez (supprimez) des faits dans le système de règles parent.

Voyons comment tout cela fonctionne dans la pratique. Pour notre système de règles, nous allons créer trois règles qui prennent en compte:

  • la distance entre le point d'apparition et le joueur. Si cette valeur est relativement petite, nous rendrons le jeu plus susceptible de générer des ennemis rouges..
  • le nombre de nœuds actuel de la scène. Si cela est trop élevé, nous ne voulons plus de points ajoutés à la scène.
  • si un point est déjà présent au point d'apparition. S'il n'y en a pas, alors nous voulons procéder pour générer un point ici.

Tout d'abord, ajoutez la propriété suivante à la GameScene classe:

var ruleSystem = GKRuleSystem ()

Ensuite, ajoutez l’extrait de code suivant à la didMoveToView (_ :) méthode:

let playerDistanceRule = GKRule (blockPredicate: (system: GKRuleSystem) -> Bool dans if let value = system.state ["spawnPoint"] en tant que? NSValue let point = valeur.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)) si totalDistance <= 200  return true  else  return false   else  return false  )  (system: GKRuleSystem) -> Nul dans system.assertFact ("spawnEnemy") let nodeCountRule = GKRule (blockPredicate: (système: GKRuleSystem) -> Bool dans if self.children.count <= 50  return true  else  return false  )  (system: GKRuleSystem) -> Nul dans system.assertFact ("shouldSpawn", grade: 0.5) let nodePresentRule = GKRule (blockPredicate: (system: GKRuleSystem) -> Bool dans if let let = system.state ["spawnPoint"] comme? NSValue where self. nodesAtPoint (value.CGPointValue ()). count == 0 return true else return false) ((system: GKRuleSystem) -> Nul dans let grade = system.gradeForFact ("shouldSpawn") system.assertFact (" shouldSpawn ", note: (note + 0.5)) self.ruleSystem.addRulesFromArray ([playerDistanceRule, nodeCountRule, nodePresentRule])

Avec ce code, nous créons trois GKRule objets et les ajouter au système de règles. Les règles affirment un fait particulier dans leur bloc d’action. Si vous ne fournissez pas de valeur de note et appelez simplement le assertFact (_ :) méthode, comme nous le faisons avec playerDistanceRule, le fait est donné une note par défaut de 1,0.

Vous remarquerez que pour le nodeCountRule nous affirmons seulement le "devrait Spawn" fait avec une note de 0.5. le nodePresentRule puis affirme ce même fait et ajoute une valeur de grade de 0.5. Ceci est fait pour que lorsque nous vérifions le fait plus tard, une valeur de note de 1,0 signifie que les deux règles ont été satisfaites.

Vous verrez également que les deux playerDistanceRule et nodePresentRule accéder au "spawnPoint" valeur du système de règles Etat dictionnaire. Nous attribuerons cette valeur avant d'évaluer le système de règles..

Enfin, recherchez et remplacez le renaître méthode dans le GameScene classe avec l'implémentation suivante:

func respawn () let endNode = GKGraphNode2D (point: float2 (x: 2048.0, y: 2048.0)) self.graph.connectNodeUsingObstacles (endNode) pour le point dans self.spawnPoints self.ruleSystem.reset () self.ruleSystem.state ["spawnPoint"] = NSValue (CGPoint: point) self.ruleSystem.evaluate () si self.ruleSystem.gradeForFact ("shouldSpawn") == 1.0 var respawnFactor = self.respawnDistribution.nextInt () si (.) si self.ruleSystem.gradeForFact ("spawnEnemy") == 1.0 respawnFactor = self.initialSpawnDistribution.nextInt () noeud var: 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 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]) agent.mass = 0.01 agent.maxSpeed ​​= 50 agent.maxAcceleration = 1000 noeud !. position = noeud du point! .strokeColor = noeud UIColor.clearColor ()! .physicsBody! .contactTestBitMask = 1 self.addChild (noeud!) self.graph.removeNodes ([endNode])

Cette méthode sera appelée une fois par seconde et est très similaire à la initialSpawn méthode. Il existe un certain nombre de différences importantes dans la pour boucle si.

  • Nous réinitialisons d’abord le système de règles en appelant son réinitialiser méthode. Cela doit être fait lorsqu'un système de règles est évalué de manière séquentielle. Cela supprime tous les faits affirmés et les données associées afin de garantir qu'il ne reste aucune information de la précédente évaluation susceptible d’interférer avec la prochaine..
  • Nous assignons ensuite le point d'apparition au système de règles Etat dictionnaire. Nous utilisons un NSValue objet, parce que le CGPoint le type de données n'est pas conforme à celui de Swift AnyObject protocole et ne peut pas être attribué à cette NSMutableDictionary propriété.
  • Nous évaluons le système de règles en appelant son évaluer méthode.
  • Nous récupérons ensuite le niveau d'adhésion du système de règles pour le "devrait Spawn" fait. Si cela est égal à 1, nous continuons avec respawning le point.
  • Enfin, nous vérifions la note du système de règles pour "spawnEnemy" fait et, si égal à 1, utiliser le générateur aléatoire normalement distribué pour créer notre spawnFactor.

Le reste de la renaître la méthode est la même que la initialSpawn méthode. Construisez et lancez votre jeu une dernière fois. Même sans vous déplacer, vous verrez de nouveaux points apparaître lorsque les conditions nécessaires seront remplies.

Conclusion

Dans cette série sur GameplayKit, vous avez beaucoup appris. Résumons brièvement ce que nous avons couvert.

  • Entités et Composants
  • Machines d'état
  • Agents, objectifs et comportements
  • Trouver son chemin
  • Générateurs de valeurs aléatoires
  • Systèmes de règles

GameplayKit est un ajout important à iOS 9 et à OS X El Capitan. Il élimine une grande partie des complexités du développement de jeux. J'espère que cette série vous a motivé à expérimenter davantage le framework et à découvrir ce dont il est capable.

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