Une introduction à GameplayKit Partie 1

introduction

Outre toutes les nouvelles fonctionnalités et les nouveaux cadres de iOS 9 et OS X El Capitan, avec les sorties de cette année, Apple a également créé un tout nouveau cadre destiné aux développeurs de jeux, GameplayKit. Avec les API graphiques existantes (SpriteKit, SceneKit et Metal) facilitant la création de superbes jeux sur iOS et OS X, Apple vient de publier GameplayKit pour faciliter la création de jeux jouer bien. Ce nouveau framework contient de nombreuses classes et fonctionnalités pouvant être utilisées pour ajouter facilement une logique complexe à vos jeux..

Dans ce premier tutoriel, je vais vous enseigner deux aspects majeurs du framework GameplayKt:

  • entités et composants
  • machines d'état

Conditions préalables

Ce tutoriel nécessite que vous exécutiez Xcode 7 sur OS X Yosemite ou plus tard. Bien que cela ne soit pas obligatoire, il est recommandé d’utiliser un périphérique physique iOS 9 car vous obtiendrez de bien meilleures performances en testant le jeu basé sur SpriteKit utilisé dans ce tutoriel..

1. Commencer

Vous devrez d’abord télécharger le projet de démarrage de cette série de tutoriels à partir de GitHub. Ceci fait, ouvrez le projet dans Xcode et exécutez-le soit sur le simulateur iOS, soit sur votre appareil..

Vous verrez qu'il s'agit d'un jeu très basique dans lequel vous contrôlez un point bleu et naviguez sur la carte. Lorsque vous entrez en collision avec un point rouge, vous perdez deux points. Lorsque vous entrez en collision avec un point vert, vous gagnez un point. Lorsque vous entrez en collision avec un point jaune, votre propre point se fige pendant quelques secondes..

A ce stade, c'est un jeu très basique, mais tout au long de cette série de tutoriels, et avec l'aide de GameplayKit, nous allons ajouter encore plus de fonctionnalités et d'éléments de gameplay..

2. Entités et composants

Le premier aspect majeur du nouveau framework GameplayKit est un concept de structuration de code basé sur entités et Composants. Cela fonctionne en vous permettant, en tant que développeur, d'écrire du code commun utilisé par différents types d'objets dans votre jeu, tout en le maintenant bien organisé et facile à gérer. Le concept d'entités et de composants vise à éliminer l'approche commune basée sur l'héritage consistant à partager des fonctionnalités communes entre types d'objets. La manière la plus simple de comprendre ce concept consiste à utiliser quelques exemples. Imaginez le scénario suivant:

Vous construisez un jeu de tower defense avec trois types principaux de tours, Feu, La glace, et Guérir. Les trois types de tours partageraient des données communes, telles que la santé, la taille et la force. Vos tours de feu et de glace doivent pouvoir cibler les ennemis entrants sur lesquels tirer, alors que votre tour de guérir ne le fait pas. Tout ce que votre tour Heal doit faire est de réparer vos autres tours dans un certain rayon lorsqu'elles subissent des dégâts..

Avec ce modèle de jeu de base en tête, voyons comment organiser votre code à l’aide d’un héritage structure:

  • Un parent La tour classe contenant des données communes telles que la santé, la taille et la force.
  • Tour de feuTour de glace, et Tour de guérison les classes qui hériteraient de la La tour classe.
  • dans le Tour de guérison classe, vous avez la logique responsable de la guérison de vos autres tours dans un certain rayon.

Jusqu'à présent, cette structure est correcte, mais un problème se pose lorsque vous devez implémenter la capacité de ciblage des tours Fire and Ice. Est-ce que vous venez de copier et coller le même code dans vos deux Tour de feu et Tour de glace Des classes? Si vous devez apporter des modifications, vous devrez alors modifier votre code à plusieurs endroits, ce qui est fastidieux et source d'erreurs. De plus, que se passe-t-il si vous souhaitez ajouter un nouveau type de tour qui a également besoin de cette fonctionnalité de ciblage. Est-ce que vous copiez et collez-le une troisième fois?

Le meilleur moyen semble être de mettre cette logique de ciblage dans le parent La tour classe. Cela vous permettrait d’avoir un seul exemplaire du code qui ne doit être modifié qu’à un seul endroit. Ajouter ce code ici, cependant, rendrait la La tour la classe est beaucoup plus grande et plus compliquée qu'elle ne le devrait quand toutes ses sous-classes n'ont pas besoin de cette fonctionnalité. Si vous souhaitez également ajouter davantage de fonctionnalités partagées entre vos types de tour, votre La tour classe deviendrait progressivement plus grande, ce qui rendrait difficile de travailler avec.

Comme vous pouvez le constater, s’il est possible de créer un modèle de jeu basé sur héritage, il peut très rapidement et facilement devenir inorganisé et difficile à gérer.

Voyons maintenant comment ce même modèle de jeu pourrait être structuré en utilisant entités et Composants:

  • Nous créerions Tour de feuTour de glace, et Tour de guérison entités. Plusieurs entités pourraient être créées pour tout type de tour que vous souhaitez ajouter ultérieurement.
  • Nous créerions aussi un Tour de base composant qui contiendrait la santé, la taille, la force, etc..
  • Pour gérer la guérison de vos tours dans un certain rayon, nous ajoutons un Guérison composant.
  • UNE Le ciblage le composant contiendrait le code nécessaire pour cibler les ennemis entrants.

En utilisant GameplayKit et cette structure, vous auriez alors un type d'entité unique pour chaque type de tour de votre jeu. Pour chaque entité individuelle, vous pouvez ajouter les composants souhaités. Par exemple:

  • Votre Tour de feu et Tour de glace les entités auraient chacune un Tour de base et Le ciblage composant lié à celui-ci.
  • Votre Tour de guérison entité aurait à la fois un Tour de base et un Guérison composant.

Comme vous pouvez le constater, en utilisant une structure basée sur les entités et les composants, votre modèle de jeu est désormais beaucoup plus simple et polyvalent. Votre logique de ciblage n'a besoin d'être écrite qu'une seule fois et ne crée que des liens vers les entités dont elle a besoin. De même, les données de base de votre tour peuvent toujours être facilement partagées entre toutes vos tours sans augmenter toutes vos autres fonctionnalités communes..

Un autre atout de cette structure basée sur les entités et les composants est que les composants peuvent être ajoutés et supprimés des entités à tout moment. Par exemple, si vous souhaitez désactiver vos tours de guet dans certaines conditions, vous pouvez simplement supprimer le Guérison composant de votre entité jusqu'à ce que les bonnes conditions soient remplies. De même, si vous souhaitez que l’une de vos tours à incendie acquière une capacité de guérison temporaire, vous pouvez simplement ajouter une Guérison composante de votre Tour de feu entité pour une durée déterminée.

Maintenant que vous êtes à l'aise avec le concept de structure de modèle de jeu basée sur des entités et des composants, créons-en un dans notre propre jeu. Dans Xcode Inspecteur de fichier, trouvez le Entités dossier dans votre projet. Pour plus de commodité, il existe déjà trois classes d'entités pour vous, mais vous allez maintenant créer une nouvelle entité à partir de zéro..

Choisir Fichier> Nouveau> Fichier… ou appuyez sur Commande-N pour créer une nouvelle classe. Assurez-vous de sélectionner le Cacao Touch Class modèle de la iOS> Source section. Nommez la classe Joueur et en faire une sous-classe de GKEntity.

Vous verrez que dès l'ouverture de votre nouveau fichier, Xcode affichera une erreur. Pour résoudre ce problème, ajoutez l’instruction d’importation suivante sous le fichier existant. importer UIKit déclaration:

importer GameplayKit

Revenir à PlayerNode.swift et ajoutez la propriété suivante à la PlayerNode classe:

var entity = Player ()

Ensuite, accédez au Composants dossier dans votre projet Xcode et créez une nouvelle classe comme vous le faisiez auparavant. Cette fois, nommez la classe Composant clignotant et en faire une sous-classe de GKComponent comme indiqué ci-dessous.

Le composant que vous venez de créer va gérer le clignotement visuel de notre point bleu lorsqu'il est touché par un point rouge et qu'il est dans son état invulnérable. Remplacer le contenu de FlashingComponent.swift avec ce qui suit:

import UIKit import SpriteKit import GameplayKit class FlashingComponent: GKComponent var nodeToFlash: SKNode! func startFlashing () let fadeAction = SKAction.sequence ([SKAction.fadeOutWithDuration (0,75), SKAction.fadeInWithDuration (0,75)]) removeActionForKey ("flash")

La mise en œuvre conserve simplement une référence à un SKNode objet et répète des actions en fondu enchaîné et en sortie tant que le composant est actif.

Revenir à GameScene.swift et ajoutez le code suivant quelque part dans le didMoveToView (_ :) méthode:

// Ajout de composant let flash = FlashingComponent () flash.nodeToFlash = playerNode flash.startFlashing () playerNode.entity.addComponent (flash)

Nous créons un Composant clignotant objet et configurez-le pour qu'il clignote sur le point du joueur. La dernière ligne ajoute ensuite le composant à l’entité pour la maintenir active et en cours d’exécution..

Construisez et exécutez votre application. Vous allez maintenant voir que votre point bleu s’efface progressivement.

Avant de continuer, supprimez le code que vous venez d’ajouter à partir du didMoveToView (_ :) méthode. Plus tard, vous rajouterez ce code, mais seulement lorsque votre point bleu entrera dans son état invulnérable..

3. Machines d'état

Dans GameplayKit, les machines d’état vous permettent d’identifier et d’exécuter facilement des tâches en fonction de la situation actuelle. Etat d'un objet particulier. À partir de l'exemple précédent de défense de tour, certains états possibles pour chaque tour pourraient inclure actif, désactivé, et Détruit. L'un des principaux avantages des machines à états est que vous pouvez spécifier les états vers lesquels un autre état peut se déplacer. Avec les trois exemples d'états mentionnés ci-dessus, à l'aide d'une machine à états, vous pouvez configurer la machine à états de sorte que:

  • une tour peut devenir désactivé quand actif et vice versa
  • une tour peut devenir Détruit quand soit actif ou désactivé
  • une tour peut ne pas devenir actif ou désactivé une fois qu'il a été Détruit

Dans le jeu de ce tutoriel, nous allons le garder très simple et n’avoir qu'un Ordinaire et invulnérable Etat.

Dans votre projet Machine d'état dossier, créez deux nouvelles classes. Nomme les État normal et État invulnérable respectivement, les deux constituant une sous-classe du GKState classe.

Remplacer le contenu de NormalState.swift avec ce qui suit:

import UIKit import SpriteKit import GameplayKit classe NormalState: GKState noeud de var: PlayerNode init (avecNode: PlayerNode) node = withNode écrasement func isValidNextState (stateClass: AnyClass) -> Bool commutateur stateClass case est InvulnerableState.Type: return true default : return false écrasera func didEnterWithPreviousState (previousState: GKState?) si let _ = previousState as? InvulnerableState node.entity.removeComponentForClass (FlashingComponent) node.runAction (SKAction.fadeInWithDuration (0.5))

le État normal La classe contient les éléments suivants:

  • Il implémente un simple initialiseur pour garder une référence au noeud du joueur actuel.
  • Il a une mise en œuvre pour le isValidNextState (_ :) méthode. L'implémentation de cette méthode renvoie une valeur booléenne, indiquant si la classe d'état actuelle peut ou non être déplacée vers la classe d'état fournie par le paramètre de méthode..
  • La classe comprend également une implémentation pour le didEnterWithPreviousState (_ :) méthode de rappel. Dans l'implémentation de la méthode, nous vérifions si l'état précédent était le État invulnérable Etat et, si vrai, supprimer le composant clignotant de l'entité du lecteur.

Ouvert InvulnerableState.swift et remplacez son contenu par ce qui suit:

import UIKit import Classe GameplayKit InvulnerableState: GKState noeud de var: PlayerNode init (withNode: PlayerNode) noeud = withNode écrasement func isValidNextState (stateClass: AnyClass) -> Bool switch stateClass case = NormalState.Type: return false écraser func didEnterWithPreviousState (previousState: GKState?) si let _ = previousState as? NormalState // Ajout de composant let flash = FlashingComponent () flash.nodeToFlash = noeud flash.startFlashing () node.entity.addComponent (flash)

le État invulnérable la classe est très similaire à la État normal classe. La principale différence est que lorsque vous entrez dans cet état, vous ajoutez le composant clignotant à l'entité du lecteur plutôt que de le supprimer..

Maintenant que vos classes d'état sont complètes et ouvertes PlayerNode.swift à nouveau et ajouter les lignes suivantes à la PlayerNode classe:

var stateMachine: GKStateMachine! func enterNormalState () self.stateMachine.enterState (NormalState)

Cet extrait de code ajoute une nouvelle propriété à la PlayerNode classe et implémente une méthode de commodité pour revenir à l'état normal.

Ouvert GameScene.swift et à la fin de la didMoveToView (_ :) méthode, ajoutez les deux lignes suivantes:

playerNode.stateMachine = GKStateMachine (états: [Etat normal (avec Noeud: joueurNode), InvulnerableState (avec Noeud: joueurNode)]) playerNode.stateMachine.enterState (NormalState)

Dans ces deux lignes de code, nous créons un nouveau GKStateMachine avec les deux états et lui dire d'entrer dans le État normal.

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

func handleContactWithNode (contact: ContactNode) si le contact est PointsNode NSNotificationCenter.defaultCenter (). postNotificationName ("updateScore", objet: self, userInfo: ["score": 1]) sinon si le contact est RedEnemyNode && playerNode.stateMachine. état actuel! est NormalState NSNotificationCenter.defaultCenter (). postNotificationName ("updateScore", objet: self, userInfo: ["score": -2]) playerNode.stateMachine.enterState (InvulnerableState) playerNode.performSelector ("enterNormalState", avec withObject: nil, afterDelay: 5.0) else si le contact est YellowEnemyNode && playerNode.stateMachine.currentState! est NormalState self.playerNode.enabled = false contact.removeFromParent ()

Lorsque le point bleu du joueur entre en collision avec un point ennemi rouge, le joueur entre dans le État invulnérable état pendant cinq secondes, puis revenez à la État normal Etat. Nous vérifions également l’état actuel du joueur et n’exécutons une logique liée à l’ennemi que si elle est la même. État normal Etat.

Générez et exécutez votre application une dernière fois et déplacez-vous sur la carte jusqu'à ce que vous trouviez un point rouge. Lorsque vous entrez en collision avec le point rouge, vous verrez que votre point bleu entre dans son état invulnérable et clignote pendant cinq secondes..

Conclusion

Dans ce tutoriel, je vous ai présenté deux des principaux aspects du framework GameplayKit., entités et Composants, et machines d'état. Je vous ai montré comment utiliser des entités et des composants pour structurer votre modèle de jeu et tout organiser. L'utilisation de composants est un moyen très simple de partager des fonctionnalités entre les objets de vos jeux..

Je vous ai également montré les bases des machines à états, y compris comment spécifier les états vers lesquels un état particulier peut être transféré, ainsi que l'exécution du code lorsqu'un état particulier est entré.

Restez à l'écoute pour la deuxième partie de cette série où nous allons faire passer ce jeu à un autre niveau en ajoutant une intelligence artificielle, mieux connue sous le nom d'intelligence artificielle. L'IA permettra aux points ennemis de cibler le joueur et de trouver le meilleur chemin pour le rejoindre..

Comme toujours, si vous avez des commentaires ou des questions, laissez-les dans les commentaires ci-dessous.