Conhecendo Swift com Snake



Olá pessoal! Antecipando o lançamento do curso “iOS-developer. Curso básico ” , organizamos outra lição aberta . Este seminário on-line foi desenvolvido para pessoas com experiência em desenvolvimento em qualquer idioma e plataforma, mas que desejam aprender a linguagem Swift e dominar o desenvolvimento para iOS. Na lição, examinamos detalhadamente a sintaxe e as principais construções da linguagem Swift , familiarizando-nos com as principais ferramentas de desenvolvimento.



Os participantes do webinar aprenderam:

  • qual é a linguagem Swift, quais são suas características;
  • Como o ambiente de desenvolvimento do Xcode ajuda você a começar
  • como criar um jogo simples para iOS.

O webinar foi conduzido por Alexei Sobolevsky , desenvolvedor iOS da Yandex.

Faça você mesmo Snake


Para o trabalho, usamos o ambiente de desenvolvimento integrado Xcode . Este é um ambiente conveniente, gratuito e funcional criado pela Apple.

No começo, criamos um novo projeto e selecionamos o conjunto básico de arquivos "Jogo":



Sem mais delongas, eles chamaram o projeto de "Cobra". Todas as configurações foram deixadas por padrão, garantindo que o SpriteKit estivesse na linha de tecnologia de jogos.

Detalhes da criação do projeto.

Após executar as ações acima, uma lista de arquivos criados automaticamente para o nosso projeto será exibida na parte esquerda da janela. Um dos arquivos mais importantes é o AppDelegate.swift , que ajuda o sistema a se comunicar com o nosso código quando há eventos significativos para o aplicativo (iniciar, pressionar, clicar no link etc.). Código deste arquivo:

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

Arquivos igualmente importantes são GameScene.swift e GameViewController.swift . A classe GameScene cria a cena, e o GameViewController é responsável por uma tela do aplicativo que vemos (uma tela - uma GameViewController). Obviamente, essa regra nem sempre é suportada, mas geralmente funciona. Como nossa aplicação é bastante simples, teremos apenas um GameViewController. Vamos começar com ele.

Escrevendo um GameViewController


Vamos excluir o código padrão. O controlador de exibição possui vários métodos que funcionam, dependendo do estado da tela. Por exemplo, viewDidLoad() acionado quando todos os elementos da tela já foram carregados e a tela está prestes a aparecer no smartphone. Como temos um jogo, devemos colocar a cena do jogo em nosso controle de exibição (é aqui que a cobra irá correr e todos os outros eventos do jogo ocorrerão).

Crie uma cena:

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

let é uma constante e uma palavra-chave. Swift também usa a palavra-chave var, necessária para definir uma variável. Usando var , podemos alterar o valor das variáveis ​​várias vezes durante a execução do programa. Usando let, não podemos alterar o valor das variáveis ​​após a inicialização.

Agora precisamos garantir que a exibição na qual colocaremos a cena criada corresponda ao tipo desejado. Para fazer isso, use a construção de guarda - é o mesmo que if , apenas o contrário (se não):

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

Depois de garantir que o elemento screen corresponda ao tipo desejado, adicionamos nossa cena a ele:

 skView.presentScene(scene) 

Você também precisa mostrar o número de quadros por segundo (FPS):

 skView.showsFPS = true 

Em seguida, exiba o número de elementos de todos os tipos em cena:

  skView.showsNodeCount = true 

E vamos fazer com que os elementos apareçam na tela, independentemente de sua ordem na hierarquia de elementos:

 skView.ignoresSiblingOrder = true 

E não esqueça que nossa cena deve ser esticada em toda a largura da tela:

 scene.scaleMode = .resizeFill 

Aqui está o código final para o arquivo 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) } } 

Detalhes da criação do arquivo GameViewController.swift.

Então, criamos a cena, mas ela está vazia; portanto, se rodarmos o emulador agora, veremos apenas uma tela preta.

Escrevendo um GameScene


Como na última vez, excluímos a maior parte do código e, em seguida, executamos as configurações necessárias para a cena . Ele também tem seus próprios métodos. Por exemplo, como adicionamos nossa cena ao ViewController, precisamos do método didMove() :

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

Além disso, quando o jogo começa, o método Update() é chamado para cada quadro:

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

E também precisamos de alguns manipuladores para tocar na tela:

 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 você sabe, Swift é famoso pela presença de açúcar sintático . Açúcar sintático - esses são aspectos técnicos que simplificam a vida do desenvolvedor, aceleram a escrita do código. Tudo isso ajuda muito na montagem da cena, o que faremos agora. Antes de tudo, defina a cor:

 backgroundColor = SKColor.white 

Como a cobra trabalha em um avião, não precisamos de física, e você pode desativá-la para que a cobra não caia devido à gravidade. Além disso, não precisamos do jogo para girar, 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 

Agora crie os botões:

 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) 

Ao escrever um pedaço de código, considere se o código pode ser aprimorado ou refatorado para poder ser reutilizado no futuro. Olha, basicamente temos dois botões na tela, para a criação dos quais o mesmo código é usado. Portanto, esse código pode ser retirado em uma função separada. Para fazer isso, crie uma nova classe e, consequentemente, o arquivo ControlsFactory.swift com o seguinte 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 desenhar uma maçã aleatória que nossa cobra “comerá”, crie a classe Apple e o arquivo 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 } } 

E descrevemos nossa maçã com a função createApple() em 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) } 

Bem, chegou a vez da cobra. Ele será composto de duas partes: o corpo ( SnakeBodyPart.swift ) e a cabeça ( SnakeHead.swift ).

Código SiftBodyPart.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") } } 

Código 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") } } 

No entanto, não incluiremos uma descrição de cada linha, porque os detalhes da criação do arquivo GameScene.swift e outras classes são bem exibidos no vídeo. Oferecemos apenas para ver o código final do 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" } 

O resultado foi o jogo mais simples de Snake:



Demoramos cerca de uma hora e meia para escrever o jogo. Se você deseja obter habilidades de programação no Swift, repita todas as etapas você mesmo. A propósito, aqui você terá acesso total a todos os arquivos de código que foram usados ​​neste projeto.

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


All Articles