Conociendo a Swift con Snake



Hola a todos! En previsión del lanzamiento del curso "iOS-developer. Curso básico ” organizamos otra lección abierta . Este seminario web está diseñado para personas que tienen experiencia en el desarrollo en cualquier idioma y plataforma, pero desean aprender el lenguaje Swift y dominar el desarrollo para iOS. En la lección, examinamos en detalle la sintaxis y las construcciones clave del lenguaje Swift , nos familiarizamos con las principales herramientas de desarrollo.



Los participantes del seminario web aprendieron:

  • cuál es el lenguaje Swift, cuáles son sus características;
  • Cómo el entorno de desarrollo de Xcode te ayuda a comenzar
  • Cómo crear un juego simple para iOS.

El seminario web fue realizado por Alexei Sobolevsky , un desarrollador de iOS en Yandex.

Hazlo tu mismo Snake


Para el trabajo, utilizamos el entorno de desarrollo integrado Xcode . Este es un entorno conveniente, gratuito y funcional creado por Apple.

Al principio, creamos un nuevo proyecto y seleccionamos el conjunto básico de archivos de "Juego":



Sin más preámbulos, llamaron al proyecto "Serpiente". Todas las configuraciones se dejaron de forma predeterminada, asegurándose de que SpriteKit estuviera en la línea Game Technology.

Detalles de la creación del proyecto.

Después de realizar las acciones anteriores, se mostrará una lista de archivos creados automáticamente para nuestro proyecto en la parte izquierda de la ventana. Uno de los archivos más importantes es AppDelegate.swift , que ayuda al sistema a comunicarse con nuestro código cuando hay eventos importantes para la aplicación (inicio, inserción, clic en el enlace, etc.). Código de este archivo:

// // 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 } } 

Archivos igualmente importantes son GameScene.swift y GameViewController.swift . La clase GameScene crea la escena, y GameViewController es responsable de una pantalla de la aplicación que vemos (una pantalla, una GameViewController). Por supuesto, esta regla no siempre es compatible, pero en general funciona. Como nuestra aplicación es bastante simple, solo tendremos un GameViewController. Comencemos con él.

Escribir un GameViewController


Eliminaremos el código predeterminado. El controlador de vista tiene varios métodos que funcionan según el estado de la pantalla. Por ejemplo, viewDidLoad() activa cuando todos los elementos de la pantalla ya se han cargado y la pantalla está a punto de aparecer en el teléfono inteligente. Como tenemos un juego, necesitamos colocar la escena del juego en nuestro controlador de vista (aquí es donde se ejecutará la serpiente y ocurrirán todos los demás eventos del juego).

Crea una escena:

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

let es una constante y una palabra clave. Swift también usa la palabra clave var, que es necesaria para definir una variable. Usando var , podemos cambiar el valor de las variables muchas veces mientras se ejecuta el programa. Usando let, no podemos cambiar el valor de las variables después de la inicialización.

Ahora debemos asegurarnos de que la vista en la que colocaremos la escena creada corresponda al tipo deseado. Para hacer esto, use la construcción de guardia : esto es lo mismo que if , solo al revés (si no):

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

Después de asegurarnos de que el elemento de pantalla coincida con el tipo deseado, le agregamos nuestra escena:

 skView.presentScene(scene) 

También debe mostrar la cantidad de fotogramas por segundo (FPS):

 skView.showsFPS = true 

Luego muestre el número de elementos de todos los tipos en la escena:

  skView.showsNodeCount = true 

Y hagamos que los elementos aparezcan en la pantalla independientemente de su orden en la jerarquía de elementos:

 skView.ignoresSiblingOrder = true 

Y no olvide que nuestra escena debe extenderse a todo el ancho de la pantalla:

 scene.scaleMode = .resizeFill 

Aquí está el código final para el archivo 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) } } 

Detalles de la creación del archivo GameViewController.swift.

Entonces, creamos la escena, pero está vacía, así que si ejecutamos el emulador ahora, solo vemos una pantalla en negro.

Escribir una escena de juego


Como la última vez, eliminamos la mayor parte del código y luego realizamos los ajustes necesarios para la escena . También tiene sus propios métodos. Por ejemplo, dado que agregamos nuestra escena al ViewController, necesitamos el método didMove() :

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

Además, cuando comienza el juego, se llama al método Update() para cada cuadro:

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

Y también necesitamos algunos controladores para tocar la pantalla:

 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 } 

Como saben, Swift es famoso por la presencia de azúcar sintáctica . Azúcar sintáctico: estos son aspectos técnicos que simplifican la vida del desarrollador y aceleran la escritura del código. Todo esto ayuda mucho en la configuración de la escena, lo que vamos a hacer ahora. En primer lugar, configure el color:

 backgroundColor = SKColor.white 

Dado que la serpiente trabaja en un avión, no necesitamos física, y puede apagarla para que la serpiente no se caiga debido a la gravedad. Además, no necesitamos que el juego gire, 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 

Ahora crea los botones:

 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) 

Cuando haya escrito un fragmento de código, debe considerar si el código puede mejorarse o refactorizarse para que pueda reutilizarse en el futuro. Mira, básicamente tenemos dos botones en la pantalla, se usa el mismo código para crearlos. Entonces, este código puede eliminarse en una función separada. Para hacer esto, cree una nueva clase y, en consecuencia, el archivo ControlsFactory.swift con el siguiente código:

 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 } } 

Para dibujar una manzana aleatoria que nuestra serpiente "comerá", cree la clase Apple y el archivo 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 } } 

Y describimos nuestra manzana con la función createApple() en 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) } 

Bueno, ha llegado el turno de la serpiente. Constará de dos partes: el cuerpo ( SnakeBodyPart.swift ) y la cabeza ( SnakeHead.swift ).

SnakeBodyPart.swift Code:

 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") } } 

SnakeHead.swift Code:

 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") } } 

Sin embargo, no lo aburriremos con una descripción de cada línea, porque los detalles de la creación del archivo GameScene.swift y otras clases se muestran bien en el video. Solo ofrecemos ver el código 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" } 

El resultado fue el juego de serpiente más simple:



Nos llevó aproximadamente una hora y media escribir el juego. Si desea adquirir habilidades de programación en Swift, repita todos los pasos usted mismo. Por cierto, aquí obtendrá acceso completo a todos los archivos de código que se utilizaron en este proyecto.

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


All Articles