通过Snake了解Swift



大家好! 预期将推出“ iOS开发人员”课程 “基础课程”我们又组织了一次公开课 。 这次网络研讨会是为有使用任何语言和平台进行开发但希望学习Swift语言并精通iOS开发的人们而设计的。 在本课中,我们详细研究了Swift语言语法和关键构造 ,并熟悉了主要的开发工具。



网络研讨会参与者了解到:

  • Swift语言是什么,它的功能是什么?
  • Xcode开发环境如何帮助您入门
  • 如何为iOS创建简单的游戏。

网络研讨会由Yandex的iOS开发人员Alexei Sobolevsky主持。

自己动手做蛇


在工作中,我们使用了集成开发环境Xcode 。 这是由Apple创建的便捷,免费且实用的环境。

在开始时,我们创建了一个新项目并选择了基本的“游戏”文件集:



事不宜迟,他们将项目称为“ Snake”。 默认情况下保留所有设置,确保SpriteKit在游戏技术行中。

项目创建的详细信息。

执行上述操作后,将在窗口的左侧显示为我们的项目自动创建的文件列表。 最重要的文件之一是AppDelegate.swift ,它可以在应用程序发生任何重大事件(启动,推送,单击链接等)时帮助系统与我们的代码通信。 该文件的代码:

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

同样重要的文件是GameScene.swiftGameViewController.swift 。 GameScene类创建场景,而GameViewController负责我们看到的应用程序的一个屏幕(一个屏幕-一个GameViewController)。 当然,并不总是支持此规则,但是总的来说,它是可行的。 由于我们的应用程序非常简单,因此我们只有一个GameViewController。 让我们从他开始。

编写一个GameViewController


我们将删除默认代码。 视图控制器有几种可以根据屏幕状态工作的方法。 例如,当所有屏幕元素均已加载且屏幕即将出现在智能手机上时,将触发viewDidLoad() 。 由于我们有一个游戏,因此我们需要将游戏场景放置在我们的视图控制器中(这是蛇将奔跑的地方,并且游戏的所有其他事件都将发生)。

创建一个场景:

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

let是一个常量和一个关键字。 Swift还使用var关键字,该关键字是定义变量所必需的。 使用var ,我们可以在程序运行时多次更改变量的值。 使用let时,我们无法在初始化后更改变量的值。

现在,我们需要确保将要放置创建的场景的视图与所需的类型相对应。 为此,请使用guard构造-与if相同,只是相反(如果不是):

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

确保屏幕元素匹配所需的类型后,我们将场景添加到其中:

 skView.presentScene(scene) 

您还需要显示每秒的帧数(FPS):

 skView.showsFPS = true 

然后显示场景中所有类型的元素数:

  skView.showsNodeCount = true 

让我们将元素显示在屏幕上,而不管它们在元素层次结构中的顺序如何:

 skView.ignoresSiblingOrder = true 

并且不要忘记我们的场景应该延伸到屏幕的整个宽度:

 scene.scaleMode = .resizeFill 

这是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) } } 

创建GameViewController.swift文件的详细信息。

因此,我们创建了一个场景,但是它是空的,因此,如果现在运行模拟器,我们只会看到黑屏。

编写GameScene


与上次一样,我们删除了大部分代码,然后对场景进行必要的设置 。 它也有自己的方法。 例如,由于将场景添加到ViewController中,因此需要didMove()方法:

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

此外,当游戏开始时,每帧都会调用Update()方法:

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

我们还需要一些处理程序来点击屏幕:

 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 } 

如您所知,Swift以语法糖的存在而闻名。 语法糖-这些技术方面的内容可以简化开发人员的工作,加快代码编写速度。 所有这些对设置场景很有帮助,我们现在要做的是。 首先,设置颜色:

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

编写一段代码后,应该考虑是否可以对代码进行改进或重构,以便将来可以重用 。 看,我们基本上在屏幕上有两个按钮,用于创建使用相同的代码。 因此,可以在单独的函数中取出此代码。 为此,使用以下代码创建一个新类,并相应地创建 ControlsFactory.swift文件:

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

要绘制我们的蛇将“吃掉”的随机苹果,请创建Apple类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 } } 

然后,我们使用GameScene.swift中createApple()函数描述我们的苹果:

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

好吧,这条蛇来了。 它由两部分组成:主体( SnakeBodyPart.swift )和头部( SnakeHead.swift )。

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

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

但是, 由于在视频中很好地显示了创建GameScene.swift文件和其他类的详细信息因此我们不会对每行进行描述。 我们提供的仅是为了查看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" } 

结果就是最简单的Snake游戏:



我们花了大约一个半小时来编写游戏。 如果您想掌握Swift的编程技能,请自己重复所有步骤。 顺便说一句, 在这里您将获得对该项目中使用的所有代码文件的完全访问权限。

Source: https://habr.com/ru/post/zh-CN469537/


All Articles