Dans ce didacticiel, cinquième et dernier volet de la série SpriteKit From Scratch, nous examinons certaines techniques avancées que vous pouvez utiliser pour optimiser vos jeux basés sur SpriteKit afin d'améliorer les performances et l'expérience utilisateur..
Ce didacticiel nécessite que vous exécutiez Xcode 7.3 ou une version ultérieure, qui inclut Swift 2.2 et les SDK iOS 9.3, tvOS 9.2 et OS X 10.11.4. Pour suivre, vous pouvez utiliser le projet que vous avez créé dans le précédent tutoriel ou télécharger une nouvelle copie à partir de GitHub..
Les graphiques utilisés pour le jeu dans cette série sont disponibles sur GraphicRiver. GraphicRiver est une excellente source pour trouver des illustrations et des graphiques pour vos jeux..
Afin d’optimiser l’utilisation de la mémoire de votre jeu, SpriteKit fournit les fonctionnalités des atlas de texture sous la forme de SKTextureAtlas
classe. Ces atlas combinent efficacement les textures que vous spécifiez en une seule et grande texture qui utilise moins de mémoire que les textures individuelles..
Heureusement, Xcode peut très facilement créer des atlas de texture. Cette opération est effectuée dans les mêmes catalogues d'actifs que ceux utilisés pour d'autres images et ressources dans vos jeux. Ouvrez votre projet et accédez à la Assets.xcassets catalogue d'actifs. Au bas de la barre latérale gauche, cliquez sur le bouton + bouton et sélectionnez le Nouvel Atlas Sprite option.
Par conséquent, un nouveau dossier est ajouté au catalogue d'actifs. Cliquez une fois sur le dossier pour le sélectionner et cliquez à nouveau pour le renommer. Nomme le Obstacles. Ensuite, faites glisser le Obstacle 1 et Obstacle 2 ressources dans ce dossier. Vous pouvez également supprimer le blanc Lutin actif généré par Xcode si vous le souhaitez, mais cela n’est pas obligatoire. Une fois terminé, votre élargi Obstacles atlas de texture devrait ressembler à ceci:
Il est maintenant temps d'utiliser l'atlas de texture dans le code. Ouvrir MainScene.swift et ajoutez la propriété suivante à la MainScene
classe. Nous initialisons un atlas de texture en utilisant le nom que nous avons entré dans notre catalogue d’actifs..
let obstaclesAtlas = SKTextureAtlas (nommé: "Obstacles")
Bien que cela ne soit pas nécessaire, vous pouvez précharger les données d'un atlas de texture avant de les utiliser. Cela permet à votre jeu d’éliminer tout retard qui pourrait se produire lors du chargement de l’atlas de texture et lors de la récupération de la première texture. Le préchargement d'un atlas de texture est effectué avec une seule méthode et vous pouvez également exécuter un bloc de code personnalisé une fois le chargement terminé..
dans le MainScene
classe, ajoutez le code suivant à la fin de la didMoveToView (_ :)
méthode:
override func didMoveToView (vue: SKView) … obstaclesAtlas.preloadWithCompletionHandler // Faites quelque chose une fois que l’atlas de texture a été chargé
Pour récupérer une texture d’un atlas de texture, vous utilisez le textureNamed (_ :)
méthode avec le nom que vous avez spécifié dans le catalogue d'actifs en tant que paramètre. Mettons à jour le spawnObstacle (_ :)
méthode dans le MainScene
classe à utiliser l'atlas de texture que nous avons créé il y a un instant. Nous récupérons la texture de l'atlas de texture et l'utilisons pour créer un nœud de sprite.
func spawnObstacle (timer: NSTimer) si player.hidden timer.invalidate () return let spriteGenerator = GKShuffledDistribution (lowerValue: 1, mostValue: 2) let texture = obstaclesAtlas.textureNamed ("Obstacle \ (spriteGenerator)") let obstacle = SKSpriteNode (texture: texture) obstacle.xEchelle = 0.3 obstacle.yEchelle = 0,3 laissez physicsBody = SKPhysicsBody (circleOfRadius: 15) physicsBody.contactTestBitMask = 0x00000001 physicsBody.pinned = true physicsBody.allowsRody = false .width / 2.0, difference = CGFloat (85.0) var x: CGFloat = 0 let laneGenerator = GKShuffledDistribution (valeur minimale: 1, valeur maximale: 3) switch laneGenerator.nextInt () (cas 1: x = centre - cas 2: x = cas central 3: x = centre + différence valeur par défaut: fatalError ("Nombre hors de [1, 3] généré") obstacle.position = CGPoint (x: x, y: (player.position.y + 800)) addChild ( obstacle) obstacle.lightingBitMask = 0xFFFFFFFF obstacle.shadowCastBitMask = 0xFFFF FFFF
Notez que si votre jeu tire parti des ressources à la demande (ODR), vous pouvez facilement spécifier une ou plusieurs balises pour chaque atlas de texture. Une fois que vous avez accédé avec succès aux balises de ressources correctes avec les API ODR, vous pouvez utiliser votre atlas de texture comme nous l’avions fait auparavant. spawnObstacle (_ :)
méthode. Vous pouvez en savoir plus sur les ressources à la demande dans un autre de mes tutoriels.
SpriteKit vous offre également la possibilité de sauvegarder et de charger facilement des scènes vers et depuis un stockage persistant. Cela permet aux joueurs de quitter votre jeu, de le relancer plus tard et d’être toujours au même niveau qu’auparavant dans votre jeu..
La sauvegarde et le chargement de votre jeu sont gérés par le NSCoding
protocole, que le SKScene
classe déjà conforme à. La mise en œuvre par SpriteKit des méthodes requises par ce protocole permet automatiquement de sauvegarder et de charger très facilement tous les détails de votre scène. Si vous le souhaitez, vous pouvez également remplacer ces méthodes pour enregistrer des données personnalisées avec votre scène..
Parce que notre jeu est très basique, nous allons utiliser un simple Bool
valeur pour indiquer si la voiture s'est écrasée. Cela vous montre comment enregistrer et charger des données personnalisées liées à une scène. Ajoutez les deux méthodes suivantes de NSCoding
protocole au MainScene
classe.
// MARK: - Protocole NSCoding requis init? (Codeur aDecoder: NSCoder) super.init (codeur: aDecoder) laissez carHasCrashed = aDecoder.decodeBoolForKey ("carCrashed") print ("car crashé: \ (carHasCrashed)") substitut func encodeWithCoder (aCoder: NSCoder) super.encodeWithCoder (aCoder) laisse carHasCrashed = player.hidden aCoder.encodeBool (carHasCrashed, pourKey: "carCrashed")
Si vous ne connaissez pas le NSCoding
protocole, le encodeWithCoder (_ :)
méthode gère la sauvegarde de votre scène et l'initialiseur avec un seul NSCoder
paramètre gère le chargement.
Ensuite, ajoutez la méthode suivante à la MainScene
classe. le saveScene ()
méthode crée un NSData
représentation de la scène, en utilisant le NSKeyedArchiver
classe. Pour garder les choses simples, nous stockons les données dans NSUserDefaults
.
func saveScene () let sceneData = NSKeyedArchiver.archivedDataWithRootObject (auto) NSUserDefaults.standardUserDefaults (). setObject (sceneData, forKey: "currentScene")
Ensuite, remplacez la mise en œuvre de didBeginContactMethod (_ :)
dans le MainScene
classe avec les éléments suivants:
func didBeginContact (contact: SKPhysicsContact) if contact.bodyA.node == player || contact.bodyB.node == player if let explosionPath = NSBundle.mainBundle (). pathForResource ("Explosion", ofType: "sks"), let smokePath = NSBundle.mainBundle (). pathForResource ("Smoke", deType: " sks "), laissez explosion = NSKeyedUnarchiver.unarchiveObjectWithFile (explosionPath) as? SKEmitterNode, let smoke = NSKeyedUnarchiver.unarchiveObjectWithFile (smokePath) as? SKEmitterNode player.removeAllActions () player.hidden = true player.physicsBody? .CategoryBitMask = 0 caméra? .RemoveAllActions () explosion.position = player.position smoke.position = player.position addChild (smoke) addChild (explosion) saveScene ( )
La première modification apportée à cette méthode est l'édition du noeud du joueur. catégorieBitMask
plutôt que de le retirer de la scène entièrement. Cela garantit que lors du rechargement de la scène, le nœud du lecteur est toujours là, même s'il n'est pas visible, mais que les collisions en double ne sont pas détectées. L’autre changement apporté appelle la saveScene ()
méthode que nous avons définie précédemment une fois que la logique d'explosion personnalisée a été exécutée.
Enfin, ouvrez ViewController.swift et remplacer le viewDidLoad ()
méthode avec l'implémentation suivante:
remplacer func viewDidLoad () super.viewDidLoad () laissez skView = SKView (frame: view.frame) var scène: MainScene? si laissez savedSceneData = NSUserDefaults.standardUserDefaults (). objectForKey ("currentScene") comme? NSData, laissez savedScene = NSKeyedUnarchiver.unarchiveObjectWithData (savedSceneData) en tant que? MainScene scene = savedScene else si let url = NSBundle.mainBundle (). URLForResource ("MainScene", avec Extension: "sks"), laissez newSceneData = NSData (contentsOfURL: url), laissez newScene = NSKeyedUnarchiver.unarchiveObjectWithData (nouveau). ? MainScene scene = newScene skView.presentScene (scène) view.insertSubview (skView, atIndex: 0) let left = LeftLane (player: scene! .Player) let middle = MiddleLane (joueur: scene! .Player) let right = RightLane (player: scene! .player) stateMachine = LaneStateMachine (indique: [gauche, milieu, droite]) stateMachine? .enterState (MiddleLane)
Lors du chargement de la scène, nous vérifions d’abord s'il existe des données sauvegardées dans le fichier standard. NSUserDefaults
. Si tel est le cas, nous récupérons ces données et recréons le MainScene
objet en utilisant le NSKeyedUnarchiver
classe. Sinon, nous obtenons l'URL du fichier de scène que nous avons créé dans Xcode et chargeons les données à partir de celui-ci de manière similaire..
Exécutez votre application et rencontrez un obstacle avec votre voiture. A ce stade, vous ne voyez pas de différence. Exécutez votre application à nouveau, cependant, et vous devriez voir que votre scène a été restaurée exactement telle qu'elle était lorsque vous venez d'écraser la voiture..
Avant chaque rendu de votre jeu, SpriteKit exécute une série de processus dans un ordre particulier. Ce groupe de processus est appelé le boucle d'animation. Ces processus représentent les actions, les propriétés physiques et les contraintes que vous avez ajoutées à votre scène..
Si, pour une raison quelconque, vous devez exécuter du code personnalisé entre l’un de ces processus, vous pouvez remplacer certaines méthodes spécifiques dans votre ordinateur. SKScene
sous-classe ou spécifiez un délégué conforme à la SKSceneDelegate
protocole. Notez que si vous affectez un délégué à votre scène, les implémentations de la classe des méthodes suivantes ne sont pas appelées..
Les processus de la boucle d'animation sont les suivants:
La scène appelle sa mettre à jour(_:)
méthode. Cette méthode a un seul NSTimeInterval
paramètre, qui vous donne l'heure système actuelle. Cet intervalle de temps peut être utile car il vous permet de calculer le temps de rendu de votre image précédente..
Si la valeur est supérieure à 1 / 60ème de seconde, votre jeu ne fonctionne pas à la cadence de 60 images par seconde (FPS) telle que visée par SpriteKit. Cela signifie que vous devrez peut-être modifier certains aspects de votre scène (particules, nombre de nœuds, par exemple) pour réduire sa complexité..
La scène exécute et calcule les actions que vous avez ajoutées à vos nœuds et les positionne en conséquence..
La scène appelle sa didEvaluateActions ()
méthode. C'est là que vous pouvez exécuter n'importe quelle logique personnalisée avant que SpriteKit ne continue avec la boucle d'animation.
La scène effectue ses simulations physiques et modifie votre scène en conséquence.
La scène appelle sa didSimulatePhysics ()
méthode, que vous pouvez remplacer avec le didEvaluateActions ()
méthode.
La scène applique les contraintes que vous avez ajoutées à vos nœuds..
La scène appelle sa didApplyConstraints ()
méthode, qui est disponible pour vous de remplacer.
La scène appelle sa didFinishUpdate ()
méthode, que vous pouvez également remplacer. C’est la dernière méthode à laquelle vous pouvez changer votre scène avant que son apparence pour cette image ne soit finalisée..
Enfin, la scène restitue son contenu et met à jour son contenu. SKView
en conséquence.
Il est important de noter que, si vous utilisez un SKSceneDelegate
objet plutôt qu’une sous-classe personnalisée, chaque méthode gagne un paramètre supplémentaire et change légèrement de nom. Le paramètre supplémentaire est un SKScene
objet, qui vous permet de déterminer la scène par rapport à laquelle la méthode est exécutée. Les méthodes définies par le SKSceneDelegate
protocole sont nommés comme suit:
mise à jour (_: forScene :)
didEvaluateActionsForScene (_ :)
didSimulatePhysicsForScene (_ :)
didApplyConstraintsForScene (_ :)
didFinishUpdateForScene (_ :)
Même si vous n'utilisez pas ces méthodes pour apporter des modifications à votre scène, elles peuvent néanmoins s'avérer très utiles pour le débogage. Si votre jeu est toujours à la traîne et que la fréquence d'images diminue à un moment donné de votre jeu, vous pouvez remplacer n'importe quelle combinaison des méthodes ci-dessus et rechercher l'intervalle de temps entre chacune de celles appelées. Cela vous permet de déterminer avec précision si ce sont vos actions, votre physique, vos contraintes ou vos graphiques qui sont trop complexes pour que votre jeu fonctionne à 60 FPS..
Lors du rendu de votre scène, SpriteKit, par défaut, parcourt les nœuds de votre scène. les enfants
tableau et les dessine sur l'écran dans le même ordre que dans le tableau. Ce processus est également répété et mis en boucle pour tout nœud enfant qu'un nœud particulier pourrait avoir..
Enumérer individuellement les nœuds enfants signifie que SpriteKit exécute un appel de dessin pour chaque nœud. Alors que pour les scènes simples, cette méthode de rendu n'a pas d'incidence significative sur les performances, à mesure que votre scène gagne plus de nœuds, ce processus devient très inefficace..
Pour rendre le rendu plus efficace, vous pouvez organiser les nœuds de votre scène en couches distinctes. Cela se fait à travers le zPosition
propriété du SKNode
classe. Plus haut un noeud zPosition
C'est le "plus proche" de l'écran, ce qui signifie qu'il est rendu au-dessus des autres nœuds de votre scène. De même, le nœud avec le plus bas zPosition
dans une scène apparaît très à l'arrière et peut être chevauché par n'importe quel autre noeud.
Après avoir organisé les nœuds en couches, vous pouvez définir une SKView
objets ignoreSiblingOrder
propriété à vrai
. Cela se traduit par SpriteKit en utilisant le zPosition
valeurs pour rendre une scène plutôt que l'ordre de la les enfants
tableau. Ce processus est beaucoup plus efficace que tous les nœuds avec le même zPosition
sont regroupés en un seul appel de tirage au lieu d'en avoir un pour chaque nœud.
Il est important de noter que le zPosition
La valeur d'un nœud peut être négative si nécessaire. Les noeuds de votre scène sont toujours restitués par ordre croissant. zPosition
.
Les deux SKAction
et SKConstraint
les classes contiennent un grand nombre de règles que vous pouvez ajouter à une scène pour créer des animations. Faisant partie du framework SpriteKit, ils sont optimisés au maximum et s’intègrent parfaitement dans la boucle d’animation de SpriteKit..
Le large éventail d’actions et de contraintes qui vous sont fournies vous permet de réaliser presque toutes les animations possibles. Pour ces raisons, il est recommandé de toujours utiliser des actions et des contraintes dans vos scènes pour créer des animations plutôt que d'exécuter une logique personnalisée ailleurs dans votre code..
Dans certains cas, notamment si vous devez animer un groupe de nœuds assez important, les champs de force physique peuvent également être en mesure de produire le résultat souhaité. Les champs de force sont encore plus efficaces car ils sont calculés parallèlement au reste des simulations physiques de SpriteKit..
Vos scènes peuvent être optimisées encore davantage en utilisant uniquement les masques de bits appropriés pour les nœuds de votre scène. En plus d’être cruciales pour la détection des collisions physiques, les masques de bits déterminent également l’effet des simulations physiques et de l’éclairage habituels sur les nœuds d’une scène..
Pour toute paire de nœuds dans une scène, qu'ils se rencontrent ou non, les contrôles SpriteKit surveillent leur relation les uns par rapport aux autres. Cela signifie que, si les masques par défaut avec tous les bits sont laissés, SpriteKit garde une trace de la position de chaque nœud dans votre scène par rapport à tous les autres nœuds. Vous pouvez grandement simplifier les simulations physiques de SpriteKit en définissant des masques de bits appropriés de sorte que seules les relations entre les nœuds susceptibles de se heurter soient suivies..
De même, une lumière dans SpriteKit n’affecte un nœud que si la logique ET
les masques de bits de leur catégorie sont une valeur non nulle. En modifiant ces catégories, de sorte que seuls les nœuds les plus importants de votre scène soient affectés par une lumière particulière, vous réduisez considérablement la complexité d'une scène..
Vous devez maintenant savoir comment optimiser davantage vos jeux SpriteKit en utilisant des techniques plus avancées, telles que les atlases de texture, le dessin par lots et les masques de bits optimisés. Vous devez également être à l'aise avec la sauvegarde et le chargement de scènes pour donner à vos joueurs une meilleure expérience globale..
Tout au long de cette série, nous avons examiné de nombreuses fonctionnalités du framework SpriteKit dans iOS, tvOS et OS X. Il existe des sujets encore plus avancés qui dépassent le cadre de cette série, tels que les shaders OpenGL ES et Metal personnalisés. comme champs et joints de physique.
Si vous souhaitez en savoir plus sur ces sujets, je vous recommande de commencer par la référence du framework SpriteKit et de vous renseigner sur les classes pertinentes..
Comme toujours, assurez-vous de laisser vos commentaires dans les commentaires ci-dessous.