Apprendre à connaître Swift avec Snake



Bonjour à tous! En prévision du lancement du cours «Développeur iOS. Cours de base », nous avons organisé une autre leçon ouverte . Ce webinaire est conçu pour les personnes qui ont de l'expérience dans le développement dans toutes les langues et plates-formes, mais qui souhaitent apprendre le langage Swift et maîtriser le développement pour iOS. Dans la leçon, nous avons examiné en détail la syntaxe et les constructions clés du langage Swift , familiarisé avec les principaux outils de développement.



Les participants au webinaire ont appris:

  • quelle est la langue Swift, quelles sont ses caractéristiques;
  • Comment l'environnement de développement Xcode vous aide à démarrer
  • comment créer un jeu simple pour iOS.

Le webinaire a été dirigé par Alexei Sobolevsky , développeur iOS chez Yandex.

Faites-le vous-même Snake


Pour le travail, nous avons utilisé l' environnement de développement intégré Xcode . Il s'agit d'un environnement pratique, gratuit et fonctionnel créé par Apple.

Au tout début, nous avons créé un nouveau projet et sélectionné l'ensemble de base des fichiers «Game»:



Sans plus tarder, ils ont appelé le projet «Snake». Tous les paramètres ont été laissés par défaut, garantissant que SpriteKit était dans la ligne Game Technology.

Détails de la création du projet.

Après avoir effectué les actions ci-dessus, une liste de fichiers créés automatiquement pour notre projet sera affichée dans la partie gauche de la fenêtre. L'un des fichiers les plus importants est AppDelegate.swift , qui aide le système à communiquer avec notre code lorsque surviennent des événements importants pour l'application (lancement, push, clic sur le lien, etc.). Code de ce fichier:

// // AppDelegate.swift // SnakeGame // // Created by Alexey Sobolevsky on 15/09/2019. // Copyright 2019 Alexey Sobolevsky. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } } 

Les fichiers tout aussi importants sont GameScene.swift et GameViewController.swift . La classe GameScene crée la scène et le GameViewController est responsable d'un écran de l'application que nous voyons (un écran - un GameViewController). Bien sûr, cette règle n'est pas toujours prise en charge, mais en général elle fonctionne. Puisque notre application est assez simple, nous n'aurons qu'un seul GameViewController. Commençons par lui.

Écrire un GameViewController


Nous supprimerons le code par défaut. Le contrôleur de vue dispose de plusieurs méthodes qui fonctionnent en fonction de l'état de l'écran. Par exemple, viewDidLoad() déclenché lorsque tous les éléments d'écran ont déjà été chargés et que l'écran est sur le point d'apparaître sur le smartphone. Puisque nous avons un jeu, nous devons placer la scène du jeu dans notre contrôleur de vue (c'est là que le serpent se déroulera et que tous les autres événements du jeu se produiront).

Créez une scène:

 let scene = GameScene(size: view.bounds.size) 

let est une constante et un mot-clé. Swift utilise également le mot clé var, qui est requis pour définir une variable. En utilisant var , nous pouvons changer la valeur des variables plusieurs fois pendant l'exécution du programme. En utilisant let, nous ne pouvons pas changer la valeur des variables après l'initialisation.

Maintenant, nous devons nous assurer que la vue dans laquelle nous allons placer la scène créée correspond au type souhaité. Pour ce faire, utilisez la construction guard - c'est la même chose que if , seulement dans l'autre sens (sinon):

 guard let skView = view as? SKView else { return } 

Après nous être assurés que l'élément screen correspond au type souhaité, nous y ajoutons notre scène:

 skView.presentScene(scene) 

Vous devez également afficher le nombre d'images par seconde (FPS):

 skView.showsFPS = true 

Affichez ensuite le nombre d'éléments de tous types sur la scène:

  skView.showsNodeCount = true 

Et faisons apparaître les éléments à l'écran quel que soit leur ordre dans la hiérarchie des éléments:

 skView.ignoresSiblingOrder = true 

Et n'oubliez pas que notre scène doit être étirée sur toute la largeur de l'écran:

 scene.scaleMode = .resizeFill 

Voici le code final du fichier GameViewController.swift :

 import UIKit import SpriteKit import GameplayKit class GameViewController: UIViewController { override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() setup() } private func setup() { guard let skView = view as? SKView else { return } let scene = GameScene(size: view.bounds.size) skView.showsFPS = true skView.showsNodeCount = true skView.ignoresSiblingOrder = true scene.scaleMode = .resizeFill skView.presentScene(scene) } } 

Détails de la création du fichier GameViewController.swift.

Donc, nous avons créé la scène, mais elle est vide, donc si nous exécutons l'émulateur maintenant, nous ne verrons qu'un écran noir.

Écrire une GameScene


Comme la dernière fois, nous supprimons la plupart du code, puis effectuons les réglages nécessaires pour la scène . Il a également ses propres méthodes. Par exemple, depuis que nous avons ajouté notre scène au ViewController, nous avons besoin de la méthode didMove() :

 override func didMove(to view: SKView) { setup(in: view) } 

De plus, lorsque le jeu démarre, la méthode Update() est appelée pour chaque image:

 override func update(_ currentTime: TimeInterval) { snake?.move() } 

Et nous avons également besoin de quelques gestionnaires pour toucher l'écran:

 override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touchedNode = findTouchedNode(with: touches) else { return } 

 override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touchedNode = findTouchedNode(with: touches) else { return } 

Comme vous le savez, Swift est célèbre pour la présence de sucre syntaxique . Sucre syntaxique - ce sont des aspects techniques qui simplifient la vie du développeur, accélèrent l'écriture du code. Tout cela aide beaucoup à mettre en place la scène, ce que nous allons faire maintenant. Tout d'abord, définissez la couleur:

 backgroundColor = SKColor.white 

Étant donné que le serpent fonctionne dans un avion, nous n'avons pas besoin de physique, et vous pouvez le désactiver pour que le serpent ne tombe pas à cause de la gravité. De plus, nous n'avons pas besoin que le jeu tourne, etc.:

 physicsWorld.gravity = .zero physicsWorld.contactDelegate = self physicsBody = SKPhysicsBody(edgeLoopFrom: frame) physicsBody?.allowsRotation = false physicsBody?.categoryBitMask = CollisionCategories.edgeBody physicsBody?.collisionBitMask = CollisionCategories.snake | CollisionCategories.snakeHead view.showsPhysics = true 

Créez maintenant les boutons:

 let counterClockwiseButton = ControlsFactory.makeButton(at: CGPoint(x: scene.frame.minX + 30, y: scene.frame.minY + 50), name: .counterClockwiseButtonName) addChild(counterClockwiseButton) let clockwiseButton = ControlsFactory.makeButton(at: CGPoint(x: scene.frame.maxX - 90, y: scene.frame.minY + 50), name: .clockwiseButtonName) addChild(clockwiseButton) 

Lorsque vous avez écrit un morceau de code, vous devez vous demander si le code peut être amélioré ou refactorisé afin qu'il puisse être réutilisé à l'avenir. Regardez, nous avons essentiellement deux boutons à l'écran, pour la création desquels le même code est utilisé. Ainsi, ce code peut être retiré dans une fonction distincte. Pour ce faire, créez une nouvelle classe et, en conséquence, le fichier ControlsFactory.swift avec le code suivant:

 import SpriteKit final class ControlsFactory { static func makeButton(at position: CGPoint, name: String) -> SKShapeNode { let button = SKShapeNode() button.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 45, height: 45)).cgPath button.position = position button.fillColor = .gray button.strokeColor = UIColor.lightGray.withAlphaComponent(0.7) button.lineWidth = 10 button.name = name return button } } 

Pour dessiner une pomme au hasard que notre serpent «mangera», créez la classe Apple et le fichier Apple.swift :

 import SpriteKit final class Apple: SKShapeNode { let diameter: CGFloat = 10 convenience init(at point: CGPoint) { self.init() path = UIBezierPath(ovalIn: CGRect(x: -diameter/2, y: -diameter/2, width: diameter, height: diameter)).cgPath fillColor = .red strokeColor = UIColor.red.withAlphaComponent(0.7) lineWidth = 5 position = point physicsBody = SKPhysicsBody(circleOfRadius: diameter / 2, center: .zero) physicsBody?.categoryBitMask = CollisionCategories.apple } } 

Et nous décrivons notre pomme avec la fonction createApple() dans GameScene.swift :

 private func createApple() { let padding: UInt32 = 15 let randX = CGFloat(arc4random_uniform(UInt32(gameFrameRect.maxX) - padding) + padding) let randY = CGFloat(arc4random_uniform(UInt32(gameFrameRect.maxY) - padding) + padding) let apple = Apple(at: CGPoint(x: randX, y: randY).relative(to: gameFrameRect)) gameFrameView.addChild(apple) } 

Eh bien, le tour est venu pour le serpent. Il sera composé de deux parties: le corps ( SnakeBodyPart.swift ) et la tête ( SnakeHead.swift ).

Code SnakeBodyPart.swift :

 import SpriteKit class SnakeBodyPart: SKShapeNode { init(at point: CGPoint, diameter: CGFloat = 10.0) { super.init() path = UIBezierPath(ovalIn: CGRect(x: -diameter/2, y: -diameter/2, width: diameter, height: diameter)).cgPath fillColor = .green strokeColor = UIColor.green.withAlphaComponent(0.7) lineWidth = 5 position = point physicsBody = SKPhysicsBody(circleOfRadius: diameter - 4, center: .zero) physicsBody?.isDynamic = true physicsBody?.categoryBitMask = CollisionCategories.snake physicsBody?.contactTestBitMask = CollisionCategories.edgeBody | CollisionCategories.apple } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Code SnakeHead.swift :

 import SpriteKit final class SnakeHead: SnakeBodyPart { init(at point: CGPoint) { super.init(at: point, diameter: 20) physicsBody?.categoryBitMask = CollisionCategories.snakeHead physicsBody?.contactTestBitMask = CollisionCategories.edgeBody | CollisionCategories.apple } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Cependant, nous ne vous ennuierons pas avec une description de chaque ligne, car les détails de la création du fichier GameScene.swift et des autres classes sont bien affichés dans la vidéo. Nous vous proposons uniquement de voir le code final de GameScene.swift :

 import SpriteKit import GameplayKit class GameScene: SKScene { var gameFrameRect: CGRect = .zero var gameFrameView: SKShapeNode! var startButton: SKLabelNode! var stopButton: SKLabelNode! var snake: Snake? override func didMove(to view: SKView) { setup(in: view) } override func update(_ currentTime: TimeInterval) { snake?.move() } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touchedNode = findTouchedNode(with: touches) else { return } if let shapeNode = touchedNode as? SKShapeNode, touchedNode.name == .counterClockwiseButtonName || touchedNode.name == .clockwiseButtonName { shapeNode.fillColor = .green if touchedNode.name == .counterClockwiseButtonName { snake?.moveCounterClockwise() } else if touchedNode.name == .clockwiseButtonName { snake?.moveClockwise() } } else if touchedNode.name == .startButtonName { start() } else if touchedNode.name == .stopButtonName { stop() } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touchedNode = findTouchedNode(with: touches) else { return } if let shapeNode = touchedNode as? SKShapeNode, touchedNode.name == .counterClockwiseButtonName || touchedNode.name == .clockwiseButtonName { shapeNode.fillColor = .gray } } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touchedNode = findTouchedNode(with: touches) else { return } if let shapeNode = touchedNode as? SKShapeNode, touchedNode.name == .counterClockwiseButtonName || touchedNode.name == .clockwiseButtonName { shapeNode.fillColor = .gray } } // MARK: - private func start() { guard let scene = scene else { return } snake = Snake(at: CGPoint(x: scene.frame.midX, y: scene.frame.midY)) gameFrameView.addChild(snake!) createApple() startButton.isHidden = true stopButton.isHidden = false } private func stop() { snake = nil gameFrameView.removeAllChildren() startButton.isHidden = false stopButton.isHidden = true } private func setup(in view: SKView) { backgroundColor = SKColor.white physicsWorld.gravity = .zero physicsWorld.contactDelegate = self physicsBody = SKPhysicsBody(edgeLoopFrom: frame) physicsBody?.allowsRotation = false physicsBody?.categoryBitMask = CollisionCategories.edgeBody physicsBody?.collisionBitMask = CollisionCategories.snake | CollisionCategories.snakeHead view.showsPhysics = true let margin: CGFloat = 20 let gameFrame = frame.inset(by: view.safeAreaInsets) gameFrameRect = CGRect(x: margin, y: margin + view.safeAreaInsets.top + 55, width: gameFrame.width - margin * 2, height: gameFrame.height - margin * 2 - 55) drawGameFrame() guard let scene = view.scene else { return } let counterClockwiseButton = ControlsFactory.makeButton(at: CGPoint(x: scene.frame.minX + 30, y: scene.frame.minY + 50), name: .counterClockwiseButtonName) addChild(counterClockwiseButton) let clockwiseButton = ControlsFactory.makeButton(at: CGPoint(x: scene.frame.maxX - 90, y: scene.frame.minY + 50), name: .clockwiseButtonName) addChild(clockwiseButton) startButton = SKLabelNode(text: "START") startButton.position = CGPoint(x: scene.frame.midX, y: 55) startButton.fontSize = 40 startButton.fontColor = .green startButton.name = .startButtonName addChild(startButton) stopButton = SKLabelNode(text: "STOP") stopButton.position = CGPoint(x: scene.frame.midX, y: 55) stopButton.fontSize = 40 stopButton.fontColor = .red stopButton.name = .stopButtonName stopButton.isHidden = true addChild(stopButton) } final func drawGameFrame() { gameFrameView = SKShapeNode(rect: gameFrameRect) gameFrameView.fillColor = .lightGray gameFrameView.lineWidth = 2 gameFrameView.strokeColor = .green addChild(gameFrameView) } private func findTouchedNode(with touches: Set<UITouch>) -> SKNode? { return touches.map { [unowned self] touch in touch.location(in: self) } .map { atPoint($0) } .first } private func createApple() { let padding: UInt32 = 15 let randX = CGFloat(arc4random_uniform(UInt32(gameFrameRect.maxX) - padding) + padding) let randY = CGFloat(arc4random_uniform(UInt32(gameFrameRect.maxY) - padding) + padding) let apple = Apple(at: CGPoint(x: randX, y: randY).relative(to: gameFrameRect)) gameFrameView.addChild(apple) } } // MARK: - SKPhysicsContactDelegate extension GameScene: SKPhysicsContactDelegate { func didBegin(_ contact: SKPhysicsContact) { var contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask contactMask ^= CollisionCategories.snakeHead switch contactMask { case CollisionCategories.apple: let apple = contact.bodyA.node is Apple ? contact.bodyA.node : contact.bodyB.node snake?.addBodyPart() apple?.removeFromParent() createApple() case CollisionCategories.edgeBody: stop() break default: break } } } private extension String { static let counterClockwiseButtonName = "counterClockwiseButton" static let clockwiseButtonName = "clockwiseButton" static let startButtonName = "startButton" static let stopButtonName = "stopButton" } 

Le résultat fut le jeu Snake le plus simple:



Il nous a fallu environ une heure et demie pour écrire le jeu. Si vous souhaitez acquérir des compétences en programmation dans Swift, répétez toutes les étapes vous-même. Au fait, ici, vous aurez un accès complet à tous les fichiers de code qui ont été utilisés dans ce projet.

Source: https://habr.com/ru/post/fr469537/


All Articles