Comment créer un jeu SpriteKit dans Swift 3 (Partie 3)
Publié: 2022-03-10Vous êtes-vous déjà demandé ce qu'il fallait pour créer un jeu SpriteKit ? Les boutons semblent-ils être une tâche plus importante qu'ils ne devraient l'être ? Vous êtes-vous déjà demandé comment conserver les paramètres dans un jeu ? La création de jeux n'a jamais été aussi facile sur iOS depuis l'introduction de SpriteKit. Dans la troisième partie de cette série en trois parties, nous terminerons notre jeu RainCat et terminerons notre introduction à SpriteKit.
Si vous avez raté la leçon précédente, vous pouvez vous rattraper en récupérant le code sur GitHub. N'oubliez pas que ce tutoriel nécessite Xcode 8 et Swift 3.
Lectures complémentaires sur SmashingMag : Lien
- Gamification et UX : là où les utilisateurs gagnent ou perdent
- Design UX ludique : créer un meilleur jeu
- Combiner la conception UX et la psychologie pour changer le comportement des utilisateurs

C'est la troisième leçon de notre voyage RainCat. Dans la leçon précédente, nous avons passé une longue journée à travers des animations simples, des comportements de chat, des effets sonores rapides et une musique de fond.
Aujourd'hui, nous allons nous concentrer sur les points suivants :
- affichage tête haute (HUD) pour la notation ;
- menu principal — avec des boutons ;
- options pour couper les sons ;
- option d'abandon du jeu.
Encore plus d'atouts
Les ressources de la leçon finale sont disponibles sur GitHub. Faites à nouveau glisser les images dans Assets.xcassets
, comme nous l'avons fait dans les leçons précédentes.
La tête haute!
Nous avons besoin d'un moyen de compter les points. Pour ce faire, nous pouvons créer un affichage tête haute (HUD). Ce sera assez simple; ce sera un SKNode
qui contiendra le score et un bouton pour quitter le jeu. Pour l'instant, nous nous concentrerons uniquement sur le score. La police que nous utiliserons est Pixel Digivolve, que vous pouvez obtenir sur Dafont.com. Comme pour l'utilisation d'images ou de sons qui ne vous appartiennent pas, lisez la licence de la police avant de l'utiliser. Celui-ci indique qu'il est gratuit pour un usage personnel, mais si vous aimez vraiment la police, vous pouvez faire un don à l'auteur à partir de la page. Vous ne pouvez pas toujours tout faire vous-même, alors redonner à ceux qui vous ont aidé en cours de route est agréable.
Ensuite, nous devons ajouter la police personnalisée au projet. Ce processus peut être délicat la première fois.
Téléchargez et déplacez la police dans le dossier du projet, sous un dossier "Polices". Nous avons fait cela plusieurs fois dans les leçons précédentes, nous allons donc parcourir ce processus un peu plus rapidement. Ajoutez un groupe nommé Fonts
au projet et ajoutez le fichier Pixel digivolve.otf
.
Vient maintenant la partie délicate. Si vous manquez cette partie, vous ne pourrez probablement pas utiliser la police. Nous devons l'ajouter à notre fichier Info.plist
. Ce fichier se trouve dans le volet gauche de Xcode. Cliquez dessus et vous verrez la liste des propriétés (ou plist
). Faites un clic droit sur la liste et cliquez sur "Ajouter une ligne".

plist
.Lorsque la nouvelle ligne apparaît, entrez ce qui suit :
Fonts provided by application
Ensuite, sous Item 0
, nous devons ajouter le nom de notre police. Le plist
devrait ressembler à ceci :

plist
avec succès ! La police doit être prête à être utilisée ! Nous devrions faire un test rapide pour nous assurer qu'il fonctionne comme prévu. Déplacez-vous vers GameScene.swift
et dans sceneDidLoad
ajoutez le code suivant en haut de la fonction :
let label = SKLabelNode(fontNamed: "PixelDigivolve") label.text = "Hello World!" label.position = CGPoint(x: size.width / 2, y: size.height / 2) label.zPosition = 1000 addChild(label)
Est-ce que ça marche?

SKLabelNode
. Oh non! Le label "Hello World" est de retour.Si cela fonctionne, alors vous avez tout fait correctement. Si non, alors quelque chose ne va pas. Code With Chris a un guide de dépannage plus approfondi, mais notez qu'il s'agit d'une ancienne version de Swift, vous devrez donc apporter des modifications mineures pour l'amener à Swift 3.
Maintenant que nous pouvons charger des polices personnalisées, nous pouvons commencer sur notre HUD. Supprimez l'étiquette "Hello World", car nous ne l'avons utilisée que pour nous assurer que notre police se charge. Le HUD sera un SKNode
, agissant comme un conteneur pour nos éléments HUD. C'est le même processus que nous avons suivi lors de la création du nœud d'arrière-plan dans la première leçon.
Créez le fichier HudNode.swift
en utilisant les méthodes habituelles et saisissez le code suivant :
import SpriteKit class HudNode : SKNode { private let scoreKey = "RAINCAT_HIGHSCORE" private let scoreNode = SKLabelNode(fontNamed: "PixelDigivolve") private(set) var score : Int = 0 private var highScore : Int = 0 private var showingHighScore = false /// Set up HUD here. public func setup(size: CGSize) { let defaults = UserDefaults.standard highScore = defaults.integer(forKey: scoreKey) scoreNode.text = "\(score)" scoreNode.fontSize = 70 scoreNode.position = CGPoint(x: size.width / 2, y: size.height - 100) scoreNode.zPosition = 1 addChild(scoreNode) } /// Add point. /// - Increments the score. /// - Saves to user defaults. /// - If a high score is achieved, then enlarge the scoreNode and update the color. public func addPoint() { score += 1 updateScoreboard() if score > highScore { let defaults = UserDefaults.standard defaults.set(score, forKey: scoreKey) if !showingHighScore { showingHighScore = true scoreNode.run(SKAction.scale(to: 1.5, duration: 0.25)) scoreNode.fontColor = SKColor(red:0.99, green:0.92, blue:0.55, alpha:1.0) } } } /// Reset points. /// - Sets score to zero. /// - Updates score label. /// - Resets color and size to default values. public func resetPoints() { score = 0 updateScoreboard() if showingHighScore { showingHighScore = false scoreNode.run(SKAction.scale(to: 1.0, duration: 0.25)) scoreNode.fontColor = SKColor.white } } /// Updates the score label to show the current score. private func updateScoreboard() { scoreNode.text = "\(score)" } }
Avant de faire quoi que ce soit d'autre, ouvrez Constants.swift
et ajoutez la ligne suivante au bas du fichier - nous l'utiliserons pour récupérer et conserver le meilleur score :
let ScoreKey = "RAINCAT_HIGHSCORE"
Dans le code, nous avons cinq variables qui se rapportent au tableau de bord. La première variable est le véritable SKLabelNode
, que nous utilisons pour présenter l'étiquette. Vient ensuite notre variable pour contenir le score actuel ; puis la variable qui détient le meilleur score. La dernière variable est un booléen qui nous indique si nous présentons actuellement le meilleur score (nous l'utilisons pour déterminer si nous devons exécuter une SKAction
pour augmenter l'échelle du tableau de bord et le coloriser au jaune du sol).
La première fonction, setup(size:)
, est là juste pour tout configurer. Nous avons configuré le SKLabelNode
de la même manière que nous l'avons fait précédemment. La classe SKNode
n'a pas de propriétés de taille par défaut, nous devons donc créer un moyen de définir une taille pour positionner notre étiquette scoreNode
. Nous récupérons également le meilleur score actuel de UserDefaults
. Il s'agit d'un moyen rapide et facile d'enregistrer de petits morceaux de données, mais ce n'est pas sécurisé. Parce que nous ne nous inquiétons pas de la sécurité pour cet exemple, UserDefaults
est parfaitement bien.
Dans notre addPoint()
, nous incrémentons la variable de score
actuelle et vérifions si l'utilisateur a obtenu un score élevé. S'ils ont un score élevé, nous enregistrons ce score dans UserDefaults
et vérifions si nous affichons actuellement le meilleur score. Si l'utilisateur a atteint un score élevé, nous pouvons animer la taille et la couleur de scoreNode
.
Dans la fonction resetPoints()
, nous définissons le score actuel sur 0
. Nous devons ensuite vérifier si nous affichions le meilleur score et réinitialiser la taille et la couleur aux valeurs par défaut si nécessaire.
Enfin, nous avons une petite fonction nommée updateScoreboard
. Il s'agit d'une fonction interne permettant de définir le score sur le texte de scoreNode
. Ceci est appelé à la fois dans addPoint()
et resetPoints()
.
Branchement du HUD
Nous devons tester si notre HUD fonctionne correctement. Passez à GameScene.swift
et ajoutez la ligne suivante sous la variable foodNode
en haut du fichier :
private let hudNode = HudNode()
Ajoutez les deux lignes suivantes dans la fonction sceneDidLoad()
, vers le haut :
hudNode.setup(size: size) addChild(hudNode)
Ensuite, dans la fonction spawnCat()
, réinitialisez les points au cas où le chat serait tombé de l'écran. Ajoutez la ligne suivante après avoir ajouté l'image-objet du chat à la scène :
hudNode.resetPoints()
Ensuite, dans la handleCatCollision(contact:)
, nous devons réinitialiser à nouveau le score lorsque le chat est frappé par la pluie. Dans l'instruction switch
à la fin de la fonction — lorsque l'autre corps est une RainDropCategory
— ajoutez la ligne suivante :
hudNode.resetPoints()
Enfin, nous devons indiquer au tableau de bord quand l'utilisateur a gagné des points. À la fin du fichier dans handleFoodHit(contact:)
, trouvez les lignes suivantes jusqu'ici :
//TODO increment points print("fed cat")
Et remplacez-les par ceci :
hudNode.addPoint()
Voila !

Vous devriez voir le HUD en action. Courez et récupérez de la nourriture. La première fois que vous collectez de la nourriture, vous devriez voir le score devenir jaune et grossir. Lorsque vous voyez cela se produire, laissez le chat se faire frapper. Si le score se réinitialise, vous saurez que vous êtes sur la bonne voie !

La scène suivante
C'est vrai, nous passons à une autre scène ! En fait, une fois terminé, ce sera le premier écran de notre application. Avant de faire quoi que ce soit d'autre, ouvrez Constants.swift
et ajoutez la ligne suivante au bas du fichier — nous l'utiliserons pour récupérer et conserver le meilleur score :
let ScoreKey = "RAINCAT_HIGHSCORE"
Créez la nouvelle scène, placez-la dans le dossier "Scènes" et appelez-la MenuScene.swift
. Saisissez le code suivant dans le fichier MenuScene.swift
:
import SpriteKit class MenuScene : SKScene { let startButtonTexture = SKTexture(imageNamed: "button_start") let startButtonPressedTexture = SKTexture(imageNamed: "button_start_pressed") let soundButtonTexture = SKTexture(imageNamed: "speaker_on") let soundButtonTextureOff = SKTexture(imageNamed: "speaker_off") let logoSprite = SKSpriteNode(imageNamed: "logo") var startButton : SKSpriteNode! = nil var soundButton : SKSpriteNode! = nil let highScoreNode = SKLabelNode(fontNamed: "PixelDigivolve") var selectedButton : SKSpriteNode? override func sceneDidLoad() { backgroundColor = SKColor(red:0.30, green:0.81, blue:0.89, alpha:1.0) //Set up logo - sprite initialized earlier logoSprite.position = CGPoint(x: size.width / 2, y: size.height / 2 + 100) addChild(logoSprite) //Set up start button startButton = SKSpriteNode(texture: startButtonTexture) startButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - startButton.size.height / 2) addChild(startButton) let edgeMargin : CGFloat = 25 //Set up sound button soundButton = SKSpriteNode(texture: soundButtonTexture) soundButton.position = CGPoint(x: size.width - soundButton.size.width / 2 - edgeMargin, y: soundButton.size.height / 2 + edgeMargin) addChild(soundButton) //Set up high-score node let defaults = UserDefaults.standard let highScore = defaults.integer(forKey: ScoreKey) highScoreNode.text = "\(highScore)" highScoreNode.fontSize = 90 highScoreNode.verticalAlignmentMode = .top highScoreNode.position = CGPoint(x: size.width / 2, y: startButton.position.y - startButton.size.height / 2 - 50) highScoreNode.zPosition = 1 addChild(highScoreNode) } }
Parce que cette scène est relativement simple, nous ne créerons pas de classes spéciales. Notre scène sera composée de deux boutons. Ceux-ci pourraient être (et mériteraient peut-être d'être) leur propre classe de SKSpriteNodes
, mais comme ils sont suffisamment différents, nous n'aurons pas besoin de créer de nouvelles classes pour eux. Il s'agit d'un conseil important lorsque vous créez votre propre jeu : vous devez être en mesure de déterminer où vous arrêter et de refactoriser le code lorsque les choses deviennent complexes. Une fois que vous avez ajouté plus de trois ou quatre boutons à un jeu, il est peut-être temps d'arrêter et de refactoriser le code du bouton de menu dans sa propre classe.
Le code ci-dessus ne fait rien de spécial ; il définit les positions de quatre sprites. Nous définissons également la couleur d'arrière-plan de la scène, de sorte que tout l'arrière-plan ait la valeur correcte. Un bon outil pour générer des codes de couleur à partir de chaînes HEX pour Xcode est UI Color. Le code ci-dessus définit également les textures pour nos états de bouton. Le bouton pour démarrer le jeu a un état normal et un état enfoncé, tandis que le bouton du son est une bascule. Pour simplifier les choses pour la bascule, nous allons changer la valeur alpha du bouton de son lors de la pression de l'utilisateur. Nous extrayons et définissons également le SKLabelNode
à score élevé.
Notre MenuScene
a l'air plutôt bien. Maintenant, nous devons montrer la scène lorsque l'application se charge. Accédez à GameViewController.swift
et recherchez la ligne suivante :
let sceneNode = GameScene(size: view.frame.size)
Remplacez-le par ceci :
let sceneNode = MenuScene(size: view.frame.size)
Ce petit changement chargera MenuScene
par défaut, au lieu de GameScene
.

États des boutons
Les boutons peuvent être délicats dans SpriteKit. De nombreuses options tierces sont disponibles (j'en ai même créé une moi-même), mais en théorie, vous n'avez besoin de connaître que les trois méthodes tactiles :
-
touchesBegan(_ touches: with event:)
-
touchesMoved(_ touches: with event:)
-
touchesEnded(_ touches: with event:)
Nous avons brièvement couvert cela lors de la mise à jour du parapluie, mais nous devons maintenant savoir ce qui suit : quel bouton a été touché, si l'utilisateur a relâché son appui ou cliqué sur ce bouton, et si l'utilisateur le touche toujours. C'est là que notre variable selectedButton
entre en jeu. Lorsqu'un toucher commence, nous pouvons capturer le bouton sur lequel l'utilisateur a commencé à cliquer avec cette variable. S'ils traînent en dehors du bouton, nous pouvons gérer cela et lui donner la texture appropriée. Lorsqu'ils relâchent le toucher, nous pouvons alors voir s'ils touchent toujours l'intérieur du bouton. Si tel est le cas, nous pouvons lui appliquer l'action associée. Ajoutez les lignes suivantes au bas de MenuScene.swift
:
override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton != nil { handleStartButtonHover(isHovering: false) handleSoundButtonHover(isHovering: false) } // Check which button was clicked (if any) if startButton.contains(touch.location(in: self)) { selectedButton = startButton handleStartButtonHover(isHovering: true) } else if soundButton.contains(touch.location(in: self)) { selectedButton = soundButton handleSoundButtonHover(isHovering: true) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { // Check which button was clicked (if any) if selectedButton == startButton { handleStartButtonHover(isHovering: (startButton.contains(touch.location(in: self)))) } else if selectedButton == soundButton { handleSoundButtonHover(isHovering: (soundButton.contains(touch.location(in: self)))) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton == startButton { // Start button clicked handleStartButtonHover(isHovering: false) if (startButton.contains(touch.location(in: self))) { handleStartButtonClick() } } else if selectedButton == soundButton { // Sound button clicked handleSoundButtonHover(isHovering: false) if (soundButton.contains(touch.location(in: self))) { handleSoundButtonClick() } } } selectedButton = nil } /// Handles start button hover behavior func handleStartButtonHover(isHovering : Bool) { if isHovering { startButton.texture = startButtonPressedTexture } else { startButton.texture = startButtonTexture } } /// Handles sound button hover behavior func handleSoundButtonHover(isHovering : Bool) { if isHovering { soundButton.alpha = 0.5 } else { soundButton.alpha = 1.0 } } /// Stubbed out start button on click method func handleStartButtonClick() { print("start clicked") } /// Stubbed out sound button on click method func handleSoundButtonClick() { print("sound clicked") }
Il s'agit d'une simple manipulation des boutons pour nos deux boutons. Dans touchesBegan(_ touches: with events:)
, nous commençons par vérifier si nous avons des boutons actuellement sélectionnés. Si nous le faisons, nous devons réinitialiser l'état du bouton sur non enfoncé. Ensuite, nous devons vérifier si un bouton est enfoncé. Si vous appuyez sur l'un d'eux, il affichera l'état en surbrillance du bouton. Ensuite, nous définissons selectedButton
sur le bouton à utiliser dans les deux autres méthodes.

Dans touchesMoved(_ touches: with events:)
, nous vérifions quel bouton a été touché à l'origine. Ensuite, nous vérifions si le toucher actuel est toujours dans les limites de selectedButton
, et nous mettons à jour l'état en surbrillance à partir de là. L'état en surbrillance du startButton
change la texture en la texture de l'état pressé, où l'état en surbrillance du soundButton
a la valeur alpha du sprite définie sur 50 %.
Enfin, dans touchesEnded(_ touches: with event:)
, nous vérifions à nouveau quel bouton est sélectionné, le cas échéant, puis si le toucher est toujours dans les limites du bouton. Si tous les cas sont satisfaits, nous appelons handleStartButtonClick()
ou handleSoundButtonClick()
pour le bon bouton.
Un temps pour l'action
Maintenant que nous avons le comportement de base des boutons, nous avons besoin d'un événement à déclencher lorsqu'ils sont cliqués. Le bouton le plus facile à implémenter est startButton
. Au clic, il suffit de présenter la GameScene
. Mettez à jour handleStartButtonClick()
dans la fonction MenuScene.swift
avec le code suivant :
func handleStartButtonClick() { let transition = SKTransition.reveal(with: .down, duration: 0.75) let gameScene = GameScene(size: size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene, transition: transition) }
Si vous exécutez l'application maintenant et appuyez sur le bouton, le jeu commencera !
Nous devons maintenant implémenter la bascule muette. Nous avons déjà un gestionnaire de son, mais nous devons être en mesure de lui dire si la mise en sourdine est activée ou désactivée. Dans Constants.swift
, nous devons ajouter une clé pour persister lorsque la mise en sourdine est activée. Ajoutez la ligne suivante :
let MuteKey = "RAINCAT_MUTED"
Nous allons l'utiliser pour enregistrer une valeur booléenne dans UserDefaults
. Maintenant que c'est configuré, nous pouvons passer à SoundManager.swift
. C'est ici que nous vérifierons et UserDefaults
pour voir si la mise en sourdine est activée ou désactivée. En haut du fichier, sous la variable trackPosition
, ajoutez la ligne suivante :
private(set) var isMuted = false
C'est la variable que le menu principal (et tout ce qui jouera du son) vérifie pour déterminer si le son est autorisé. Nous l'initialisons en tant que false
, mais nous devons maintenant vérifier UserDefaults
pour voir ce que l'utilisateur veut. Remplacez la fonction init()
par ce qui suit :
private override init() { //This is private, so you can only have one Sound Manager ever. trackPosition = Int(arc4random_uniform(UInt32(SoundManager.tracks.count))) let defaults = UserDefaults.standard isMuted = defaults.bool(forKey: MuteKey) }
Maintenant que nous avons une valeur par défaut pour isMuted
, nous devons pouvoir la modifier. Ajoutez le code suivant au bas de SoundManager.swift
:
func toggleMute() -> Bool { isMuted = !isMuted let defaults = UserDefaults.standard defaults.set(isMuted, forKey: MuteKey) defaults.synchronize() if isMuted { audioPlayer?.stop() } else { startPlaying() } return isMuted }
Cette méthode va basculer notre variable muette, ainsi que mettre à jour UserDefaults
. Si la nouvelle valeur n'est pas mise en sourdine, la lecture de la musique commencera ; si la nouvelle valeur est muette, la lecture ne commencera pas. Sinon, nous arrêterons la lecture de la piste en cours. Après cela, nous devons modifier l'instruction if
dans startPlaying()
.
Trouvez la ligne suivante :
if audioPlayer == nil || audioPlayer?.isPlaying == false {
Et remplacez-le par ceci :
if !isMuted && (audioPlayer == nil || audioPlayer?.isPlaying == false) {
Maintenant, si la mise en sourdine est désactivée et que le lecteur audio n'est pas réglé ou que le lecteur audio actuel ne joue plus, nous jouerons la piste suivante.
À partir de là, nous pouvons revenir dans MenuScene.swift
pour terminer notre bouton de sourdine. Remplacez handleSoundbuttonClick()
par le code suivant :
func handleSoundButtonClick() { if SoundManager.sharedInstance.toggleMute() { //Is muted soundButton.texture = soundButtonTextureOff } else { //Is not muted soundButton.texture = soundButtonTexture } }
Cela bascule le son dans SoundManager
, vérifie le résultat, puis définit de manière appropriée la texture pour montrer à l'utilisateur si le son est coupé ou non. On a presque fini! Il nous suffit de définir la texture initiale du bouton au lancement. Dans sceneDidLoad()
, recherchez la ligne suivante :
soundButton = SKSpriteNode(texture: soundButtonTexture)
Et remplacez-le par ceci :
soundButton = SKSpriteNode(texture: SoundManager.sharedInstance.isMuted ? soundButtonTextureOff : soundButtonTexture)
L'exemple ci-dessus utilise un opérateur ternaire pour définir la texture correcte.
Maintenant que la musique est connectée, nous pouvons passer à CatSprite.swift
pour désactiver le chat qui miaule lorsque la mise en sourdine est activée. Dans hitByRain()
, nous pouvons ajouter l'instruction if
suivante après avoir supprimé l'action walking :
if SoundManager.sharedInstance.isMuted { return }
Cette instruction indiquera si l'utilisateur a désactivé l'application. Pour cette raison, nous ignorerons complètement nos effets sonores currentRainHits
, maxRainHits
et miaulement.
Après tout cela, il est maintenant temps d'essayer notre bouton de sourdine. Exécutez l'application et vérifiez si elle joue et coupe les sons de manière appropriée. Coupez le son, fermez l'application et rouvrez-la. Assurez-vous que le paramètre muet persiste. Notez que si vous désactivez et réexécutez simplement l'application à partir de Xcode, vous n'avez peut-être pas laissé suffisamment de temps à UserDefaults
pour s'enregistrer. Jouez au jeu et assurez-vous que le chat ne miaule jamais lorsque vous êtes en sourdine.
Quitter le jeu
Maintenant que nous avons le premier type de bouton pour le menu principal, nous pouvons nous lancer dans des affaires délicates en ajoutant le bouton Quitter à notre scène de jeu. Certaines interactions intéressantes peuvent venir avec notre style de jeu ; actuellement, le parapluie se déplacera là où l'utilisateur touche ou déplace son toucher. De toute évidence, le parapluie se déplaçant vers le bouton Quitter lorsque l'utilisateur tente de quitter le jeu est une expérience utilisateur assez médiocre, nous allons donc essayer d'empêcher que cela ne se produise.
Le bouton Quitter que nous implémentons imitera le bouton Démarrer le jeu que nous avons ajouté précédemment, une grande partie du processus restant le même. Le changement sera dans la façon dont nous gérons les touches. Obtenez vos actifs quit_button
et quit_button_pressed
dans le fichier Assets.xcassets
et ajoutez le code suivant au fichier HudNode.swift
:
private var quitButton : SKSpriteNode! private let quitButtonTexture = SKTexture(imageNamed: "quit_button") private let quitButtonPressedTexture = SKTexture(imageNamed: "quit_button_pressed")
Cela gérera notre référence quitButton
, ainsi que les textures que nous définirons pour les états des boutons. Pour nous assurer que nous ne mettons pas à jour par inadvertance le parapluie en essayant de quitter, nous avons besoin d'une variable qui indique au HUD (et à la scène du jeu) que nous interagissons avec le bouton de sortie et non avec le parapluie. Ajoutez le code suivant sous la showingHighScore
booléenne montrantHighScore :
private(set) var quitButtonPressed = false
Encore une fois, il s'agit d'une variable que seul le HudNode
peut définir mais que d'autres classes peuvent vérifier. Maintenant que nos variables sont configurées, nous pouvons ajouter le bouton au HUD. Ajoutez le code suivant à la fonction setup(size:)
:
quitButton = SKSpriteNode(texture: quitButtonTexture) let margin : CGFloat = 15 quitButton.position = CGPoint(x: size.width - quitButton.size.width - margin, y: size.height - quitButton.size.height - margin) quitButton.zPosition = 1000 addChild(quitButton)
Le code ci-dessus définira le bouton Quitter avec la texture de notre état non pressé. Nous définissons également la position dans le coin supérieur droit et définissons la zPosition
sur un nombre élevé afin de le forcer à toujours dessiner en haut. Si vous lancez le jeu maintenant, il apparaîtra dans GameScene
, mais il ne sera pas encore cliquable.

Maintenant que le bouton a été positionné, nous devons pouvoir interagir avec lui. À l'heure actuelle, le seul endroit où nous avons une interaction dans GameScene
est lorsque nous interagissons avec umbrellaSprite
. Dans notre exemple, le HUD aura la priorité sur le parapluie, afin que les utilisateurs n'aient pas à déplacer le parapluie pour sortir. Nous pouvons créer les mêmes fonctions dans HudNode.swift
pour imiter la fonctionnalité tactile dans GameScene.swift
. Ajoutez le code suivant à HudNode.swift
:
func touchBeganAtPoint(point: CGPoint) { let containsPoint = quitButton.contains(point) if quitButtonPressed && !containsPoint { //Cancel the last click quitButtonPressed = false quitButton.texture = quitButtonTexture } else if containsPoint { quitButton.texture = quitButtonPressedTexture quitButtonPressed = true } } func touchMovedToPoint(point: CGPoint) { if quitButtonPressed { if quitButton.contains(point) { quitButton.texture = quitButtonPressedTexture } else { quitButton.texture = quitButtonTexture } } } func touchEndedAtPoint(point: CGPoint) { if quitButton.contains(point) { //TODO tell the gamescene to quit the game } quitButton.texture = quitButtonTexture }
Le code ci-dessus ressemble beaucoup au code que nous avons créé pour MenuScene
. La différence est qu'il n'y a qu'un seul bouton à suivre, nous pouvons donc tout gérer dans ces méthodes tactiles. De plus, comme nous connaîtrons l'emplacement du toucher dans GameScene
, nous pouvons simplement vérifier si notre bouton contient le point tactile.
Passez à GameScene.swift
et remplacez les touchesBegan(_ touches with event:)
et touchesMoved(_ touches: with event:)
par le code suivant :
override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchBeganAtPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchMovedToPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchEndedAtPoint(point: point) } }
Ici, chaque méthode gère tout à peu près de la même manière. Nous informons le HUD que l'utilisateur a interagi avec la scène. Ensuite, nous vérifions si le bouton Quitter capture actuellement les touches. Si ce n'est pas le cas, nous déplaçons le parapluie. Nous avons également ajouté la touchesEnded(_ touches: with event:)
pour gérer la fin du clic pour le bouton Quitter, mais nous ne l'utilisons toujours pas pour umbrellaSprite
.
Maintenant que nous avons un bouton, nous avons besoin d'un moyen pour qu'il affecte GameScene
. Ajoutez la ligne suivante en haut de HudeNode.swift
:
var quitButtonAction : (() -> ())?
Il s'agit d'une fermeture générique qui n'a ni entrée ni sortie. Nous allons définir cela avec du code dans le fichier GameScene.swift
et l'appeler lorsque nous cliquons sur le bouton dans HudNode.swift
. Ensuite, nous pouvons remplacer le TODO
dans le code que nous avons créé précédemment dans la touchEndedAtPoint(point:)
par ceci :
if quitButton.contains(point) && quitButtonAction != nil { quitButtonAction!() }
Maintenant, si nous définissons la fermeture quitButtonAction
, elle sera appelée à partir de ce point.
Pour configurer la fermeture quitButtonAction
, nous devons passer à GameScene.swift
. Dans sceneDidLoad()
, nous pouvons remplacer notre configuration HUD par le code suivant :
hudNode.setup(size: size) hudNode.quitButtonAction = { let transition = SKTransition.reveal(with: .up, duration: 0.75) let gameScene = MenuScene(size: self.size) gameScene.scaleMode = self.scaleMode self.view?.presentScene(gameScene, transition: transition) self.hudNode.quitButtonAction = nil } addChild(hudNode)
Exécutez l'application, appuyez sur lecture, puis appuyez sur quitter. Si vous êtes de retour au menu principal, votre bouton Quitter fonctionne comme prévu. Dans la fermeture que nous avons créée, nous avons initialisé une transition vers le MenuScene
. Et nous définissons cette fermeture sur le nœud HUD
pour qu'elle s'exécute lorsque le bouton Quitter est cliqué. Une autre ligne importante ici est lorsque nous définissons le quitButtonAction
sur nil
. La raison en est qu'un cycle de rétention se produit. La scène contient une référence au HUD où le HUD contient une référence à la scène. Étant donné qu'il existe une référence aux deux objets, aucun ne sera supprimé au moment de la récupération de place. Dans ce cas, chaque fois que nous entrons et quittons GameScene
, une autre instance de celle-ci sera créée et jamais publiée. C'est mauvais pour les performances et l'application finira par manquer de mémoire. Il existe plusieurs façons d'éviter cela, mais dans notre cas, nous pouvons simplement supprimer la référence à GameScene
du HUD, et la scène et le HUD seront terminés une fois que nous reviendrons au MenuScene
. Krakendev a une explication plus approfondie des types de référence et comment éviter ces cycles.
Maintenant, passez à GameViewController.swift
et supprimez ou commentez les trois lignes de code suivantes :
view.showsPhysics = true view.showsFPS = true view.showsNodeCount = true
Avec les données de débogage à l'écart, le jeu a l'air vraiment bien ! Félicitations : nous sommes actuellement en version bêta ! Découvrez le code final d'aujourd'hui sur GitHub.
Dernières pensées
Il s'agit de la dernière leçon d'un didacticiel en trois parties, et si vous êtes arrivé jusqu'ici, vous venez de faire beaucoup de travail sur votre jeu. Dans ce didacticiel, vous êtes passé d'une scène qui ne contenait absolument rien à un jeu terminé. Félicitations! Dans la première leçon, nous avons ajouté le sol, les gouttes de pluie, l'arrière-plan et les sprites de parapluie. Nous avons également joué avec la physique et fait en sorte que nos gouttes de pluie ne s'accumulent pas. Nous avons commencé avec la détection de collision et avons travaillé sur l'élimination des nœuds afin de ne pas manquer de mémoire. Nous avons également ajouté une certaine interaction avec l'utilisateur en permettant au parapluie de se déplacer vers l'endroit où l'utilisateur touche l'écran.
Dans la deuxième leçon, nous avons ajouté le chat et la nourriture, ainsi que des méthodes de reproduction personnalisées pour chacun d'eux. Nous avons mis à jour notre détection de collision pour permettre les sprites de chat et de nourriture. Nous avons également travaillé sur le mouvement du chat. Le chat a acquis un but : manger chaque morceau de nourriture disponible. Nous avons ajouté une animation simple pour le chat et ajouté des interactions personnalisées entre le chat et la pluie. Enfin, nous avons ajouté des effets sonores et de la musique pour donner l'impression que le jeu est complet.
Dans cette dernière leçon, nous avons créé un affichage tête haute pour contenir notre étiquette de score, ainsi que notre bouton Quitter. Nous avons géré les actions sur les nœuds et permis à l'utilisateur de quitter avec un rappel du nœud HUD. Nous avons également ajouté une autre scène dans laquelle l'utilisateur peut se lancer et revenir après avoir cliqué sur le bouton Quitter. Nous avons géré le processus de démarrage du jeu et de contrôle du son dans le jeu.
Où aller en partant d'ici
Nous avons mis beaucoup de temps pour en arriver là, mais il reste encore beaucoup de travail à faire dans ce jeu. RainCat poursuit son développement et est disponible dans l'App Store. Vous trouverez ci-dessous une liste de souhaits et de besoins à ajouter. Certains éléments ont été ajoutés, tandis que d'autres sont toujours en attente :
- Ajoutez des icônes et un écran de démarrage.
- Finalisez le menu principal (simplifié pour le tutoriel).
- Corrigez les bugs, y compris les gouttes de pluie voyous et le frai multiple de la nourriture.
- Refactoriser et optimiser le code.
- Modifiez la palette de couleurs du jeu en fonction du score.
- Mettez à jour la difficulté en fonction du score.
- Animez le chat lorsque la nourriture est juste au-dessus.
- Intégrez Game Center.
- Donnez du crédit (y compris le crédit approprié pour les morceaux de musique).
Gardez une trace sur GitHub car ces modifications seront apportées à l'avenir. Si vous avez des questions sur le code, n'hésitez pas à nous envoyer un message à [email protected] et nous pourrons en discuter. Si certains sujets reçoivent suffisamment d'attention, nous pouvons peut-être écrire un autre article sur le sujet.
Merci!
Je tiens à remercier toutes les personnes qui ont contribué au processus de création du jeu et au développement des articles qui l'accompagnent.
- Cathryn Rowe Pour l'art initial, la conception et l'édition, et pour la publication des articles dans notre garage.
- Morgan Wheaton Pour la conception finale du menu et les palettes de couleurs (qui auront fière allure une fois que j'aurai implémenté ces fonctionnalités - restez à l'écoute).
- Nikki Clark Pour les superbes en-têtes et intercalaires dans les articles et pour l'aide à l'édition des articles.
- Laura Levisay Pour tous les GIF géniaux dans les articles et pour m'avoir envoyé des GIF de chats mignons pour un soutien moral.
- Tom Hudson Pour l'aide à la rédaction des articles et sans qui cette série n'aurait pas été réalisée du tout.
- Lani DeGuire Pour l'aide avec l'édition des articles, ce qui était une tonne de travail.
- Jeff Moon Pour l'aide à la rédaction de la troisième leçon et du ping-pong. Beaucoup de ping-pong.
- Tom Nelson Pour avoir aidé à s'assurer que le didacticiel fonctionne comme il se doit.
Sérieusement, il a fallu une tonne de personnes pour tout préparer pour cet article et le publier dans le magasin.
Merci également à tous ceux qui liront cette phrase.