[SpriteKit] 制作瓦片地图小游戏

àì夳堔傛蜴生んèń 2022-05-31 09:53 460阅读 0赞

概述

SpriteKit制作瓦片地图游戏,深入了解2D游戏制作过程

详细

代码下载:http://www.demodashi.com/demo/10703.html

说实话这个2D游戏实战的入门看的我脑浆子都沸腾了, 好多新的概念涌入, 没做过游戏开发的我表示真的难以接受, 吸收效率与之前相比也下降好多, 不过越往后学, 就能够加深对之前知识的掌握, 这可能也是看书的好处吧, 今天我也把对瓦片地图的一些学习经验记录下来供大家探讨.

1500739840512037570.png

说实话, 我很推荐Ray家的资源, 由浅入深手把手的教学, 内容前后呼应, 看几本书就能涵盖国内4个月培训班的课程体系. 遵循本系列一贯的风格, 我们还是从基础的API开始看起, 对API掌握熟练的话, 多敲两个Demo就能够基本的上手任何项目了.

一、瓦片地图技术要点

1、SKTileMapNode

  1. @available(iOS 10.0, *)
  2. open class SKTileMapNode : SKNode, NSCopying, NSCoding
  3. public init(tileSet: SKTileSet, columns: Int, rows: Int, tileSize: CGSize)
  4. open var numberOfColumns: Int
  5. open var numberOfRows: Int
  6. open var tileSize: CGSize
  7. open var mapSize: CGSize { get }
  8. open var tileSet: SKTileSet
  9. open var colorBlendFactor: CGFloat
  10. open func tileDefinition(atColumn column: Int, row: Int) -> SKTileDefinition?
  11. open func tileGroup(atColumn column: Int, row: Int) -> SKTileGroup?
  12. open func setTileGroup(_ tileGroup: SKTileGroup?, forColumn column: Int, row: Int)
  13. open func tileColumnIndex(fromPosition position: CGPoint) -> Int
  14. open func tileRowIndex(fromPosition position: CGPoint) -> Int
  15. open func centerOfTile(atColumn column: Int, row: Int) -> CGPoint
  • init(tileSet: SKTileSet, columns: Int, rows: Int, tileSize: CGSize) 瓦片地图节点的初始化方法
  • numberOfColumns 瓦片地图的列数
  • numberOfRows 瓦片地图的行数
  • tileSize 瓦片地图中每个瓦片的尺寸
  • mapSize 瓦片地图的尺寸
  • tileSet 瓦片地图的瓦片集
  • colorBlendFactor 瓦片的渲染着色
  • tileDefinition(atColumn column: Int, row: Int) -> SKTileDefinition? 根据列数和行数返回瓦片定义
  • tileGroup(atColumn column: Int, row: Int) -> SKTileGroup? 根据列数和行数返回瓦片组
  • setTileGroup(_ tileGroup: SKTileGroup?, forColumn column: Int, row: Int) 根据列数和行数设置瓦片组
  • tileColumnIndex(fromPosition position: CGPoint) -> Int 根据瓦片位置返回瓦片在瓦片地图中列数下标
  • tileRowIndex(fromPosition position: CGPoint) -> Int 根据瓦片位置返回瓦片在瓦片地图中行数下标

2、SKTileSet

  1. @available(iOS 10.0, *)
  2. open class SKTileSet : NSObject, NSCopying, NSCoding
  3. public init(tileGroups: [SKTileGroup])
  4. open var tileGroups: [SKTileGroup]
  5. open var name: String?
  6. open var defaultTileGroup: SKTileGroup?
  7. open var defaultTileSize: CGSize
  8. open var type: SKTileSetType
  9. @available(iOS 10.0, *)
  10. public enum SKTileSetType : UInt {
  11. case grid
  12. case isometric
  13. case hexagonalFlat
  14. case hexagonalPointy
  15. }
  • init(tileGroups: [SKTileGroup]) 根据瓦片组初始化瓦片集
  • tileGroups 瓦片组
  • name 瓦片集的标识
  • defaultTileGroup 瓦片集默认瓦片组
  • defaultTileSize 瓦片集默认瓦片尺寸
  • type 瓦片集类型 - 网格, 等值, 六边形

3、SKTileGroup

  1. @available(iOS 10.0, *)
  2. open class SKTileGroup : NSObject, NSCopying, NSCoding
  3. open class func empty() -> Self
  4. public init(tileDefinition: SKTileDefinition)
  5. public init(rules: [SKTileGroupRule])
  6. open var name: String?
  • empty() 返回一个空的瓦片组
  • init(tileDefinition: SKTileDefinition) 根据瓦片定义初始化瓦片组
  • init(rules: [SKTileGroupRule]) 根据瓦片组规则初始化瓦片组
  • name 瓦片组的标识

4、SKTileGroupRule

  1. @available(iOS 10.0, *)
  2. open class SKTileGroupRule : NSObject, NSCopying, NSCoding
  3. public init(adjacency: SKTileAdjacencyMask, tileDefinitions: [SKTileDefinition])
  4. open var adjacency: SKTileAdjacencyMask
  5. open var tileDefinitions: [SKTileDefinition]
  6. open var name: String?
  • init(adjacency: SKTileAdjacencyMask, tileDefinitions: [SKTileDefinition]) 根据瓦片链接和瓦片定义初始化瓦片组规则
  • adjacency 瓦片链接
  • tileDefinitions 瓦片规则
  • name 瓦片组规则标识

5、SKTileDefinition

  1. @available(iOS 10.0, *)
  2. open class SKTileDefinition : NSObject, NSCopying, NSCoding
  3. public init(texture: SKTexture)
  4. public init(textures: [SKTexture], normalTextures: [SKTexture], size: CGSize, timePerFrame: CGFloat)
  5. open var userData: NSMutableDictionary?
  6. open var name: String?
  7. open var size: CGSize
  8. open var timePerFrame: CGFloat
  9. open var rotation: SKTileDefinitionRotation
  10. open var flipVertically: Bool
  11. open var flipHorizontally: Bool
  • init(texture: SKTexture) 根据纹理初始化瓦片定义
  • init(textures: [SKTexture], normalTextures: [SKTexture], size: CGSize, timePerFrame: CGFloat) 根据纹理集合, 尺寸, 和帧率初始化瓦片定义
  • userData 瓦片定义的用户数据
  • name 瓦片定义的标识
  • timePerFrame 瓦片定义的帧率
  • rotation 瓦片定义的旋转规则
  • flipVertically 是否垂直翻转
  • flipHorizontally 是否水平翻转

二、程序实现

API, 了解一些基本的就够了, 如果要深究可以打开头文件逐个尝试, 我们现在就来实现一个小游戏, 这个游戏中包含了3个场景, 控制人物在规定时间内消灭所有的害虫, 我们着手进行游戏的开发吧!

1500741065428074248.png

1、step1 设置游戏场景的属性

  1. class GameScene: SKScene {
  2. var background: SKTileMapNode! //背景瓦片地图节点
  3. var obstaclesTileMap: SKTileMapNode? //障碍物瓦片地图节点
  4. var bugsprayTileMap: SKTileMapNode? //杀虫喷剂瓦片地图节点
  5. var bugsNode = SKNode() //害虫的节点
  6. var player = Player() //玩家的节点
  7. var hud = HUD() //文字说明
  8. var firebugCount: Int = 0 //高级害虫的节点数
  9. var timeLimit: Int = 10 //时间限制
  10. var elapsedTime: Int = 0 //经过时间
  11. var startTime: Int? //开始时间
  12. var currentLevel: Int = 1 //当前关卡等级
  13. var gameState: GameState = .initial { //游戏状态默认为初始状态
  14. didSet {
  15. hud.updateGameState(from: oldValue, to: gameState) //更新游戏状态
  16. }
  17. }
  18. ...
  19. }

2、step2 加载游戏场景的初始化设置

  1. required init?(coder aDecoder: NSCoder) {
  2. super.init(coder: aDecoder)
  3. background =
  4. childNode(withName: "background") as! SKTileMapNode //通过节点名读取背景瓦片地图节点
  5. obstaclesTileMap = childNode(withName: "obstacles")
  6. as? SKTileMapNode //通过节点名读取障碍物瓦片地图节点
  7. if let timeLimit =
  8. userData?.object(forKey: "timeLimit") as? Int {
  9. self.timeLimit = timeLimit //通过节点的用户数据设置每个场景的时间限制
  10. }
  11. // 1
  12. let savedGameState = aDecoder.decodeInteger(forKey: "Scene.gameState") //解档保存游戏状态
  13. if let gameState = GameState(rawValue: savedGameState), gameState == .pause { //当解档保存游戏状态为暂停时
  14. self.gameState = gameState //赋值游戏状态
  15. firebugCount = aDecoder.decodeInteger(
  16. forKey: "Scene.firebugCount") //解档高级害虫数
  17. elapsedTime = aDecoder.decodeInteger(
  18. forKey: "Scene.elapsedTime") //解档经过时间
  19. currentLevel = aDecoder.decodeInteger(
  20. forKey: "Scene.currentLevel") //解档当前关卡等级
  21. // 2
  22. player = childNode(withName: "Player") as! Player //根据节点名读取玩家节点
  23. hud = camera!.childNode(withName: "HUD") as! HUD //根据节点名读取文字说明
  24. bugsNode = childNode(withName: "Bugs")! //根据节点名读取害虫节点
  25. bugsprayTileMap = childNode(withName: "Bugspray")
  26. as? SKTileMapNode //通过节点名读取杀虫喷雾瓦片地图节点
  27. }
  28. addObservers() //添加观察者
  29. }
  30. deinit {
  31. NotificationCenter.default.removeObserver(self) //移除观察者
  32. }

3、step3 当场景移动到屏幕时的设置

  1. override func didMove(to view: SKView) {
  2. if gameState == .initial { //当游戏状态为初始状态时
  3. addChild(player) //添加玩家到场景
  4. setupWorldPhysics() //添加物理世界
  5. createBugs() //添加害虫
  6. setupObstaclePhysics() //添加障碍物
  7. if firebugCount > 0 { //如果有高级害虫
  8. createBugspray(quantity: firebugCount + 10) //添加杀虫喷雾
  9. }
  10. setupHUD() //添加文字说明
  11. gameState = .start //设置游戏状态为开始状态
  12. }
  13. setupCamera() //添加摄像头
  14. }

4、step4 物理世界的设置

  1. func setupWorldPhysics() {
  2. background.physicsBody =
  3. SKPhysicsBody(edgeLoopFrom: background.frame) //设置边缘物理体
  4. background.physicsBody?.categoryBitMask = PhysicsCategory.Edge //设置物理体标识为边缘
  5. physicsWorld.contactDelegate = self //物理世界代理
  6. }

5、step5 创建害虫的设置

  1. func createBugs() {
  2. guard let bugsMap = childNode(withName: "bugs")
  3. as? SKTileMapNode else { return } //校验害虫瓦片地图节点
  4. // 1
  5. for row in 0..<bugsMap.numberOfRows { //逐行遍历害虫瓦片地图
  6. for column in 0..<bugsMap.numberOfColumns { //逐列遍历害虫瓦片地图
  7. // 2
  8. guard let tile = tile(in: bugsMap,
  9. at: (column, row))
  10. else { continue } //校验瓦片地图中的每个瓦片
  11. // 3
  12. let bug: Bug
  13. if tile.userData?.object(forKey: "firebug") != nil { //从用户数据中判断是否为高级害虫
  14. bug = Firebug() //将害虫设置为高级害虫
  15. firebugCount += 1 //高级害虫书自增
  16. } else {
  17. bug = Bug() //将害虫设置为普通害虫
  18. }
  19. bug.position = bugsMap.centerOfTile(atColumn: column,
  20. row: row) //从害虫瓦片地图中读取位置并赋值
  21. bugsNode.addChild(bug) //添加节点
  22. bug.moveBug() //移动害虫
  23. }
  24. }
  25. // 4
  26. bugsNode.name = "Bugs" //设置害虫节点标识
  27. addChild(bugsNode) //添加父节点到场景
  28. // 5
  29. bugsMap.removeFromParent() //删除害虫瓦片地图地图节点
  30. }

6、step6 添加障碍物的设置

  1. func setupObstaclePhysics() {
  2. guard let obstaclesTileMap = obstaclesTileMap else { return } //校验障碍物瓦片地图节点
  3. // 1
  4. for row in 0..<obstaclesTileMap.numberOfRows {
  5. for column in 0..<obstaclesTileMap.numberOfColumns {
  6. // 2
  7. guard let tile = tile(in: obstaclesTileMap,
  8. at: (column, row))
  9. else { continue }
  10. guard tile.userData?.object(forKey: "obstacle") != nil
  11. else { continue }
  12. // 3
  13. let node = SKNode() //创建节点
  14. node.physicsBody = SKPhysicsBody(rectangleOf: tile.size) 根据瓦片尺寸创建物理体
  15. node.physicsBody?.isDynamic = false //不进入物理世界
  16. node.physicsBody?.friction = 0 //摩擦系数为0
  17. node.physicsBody?.categoryBitMask =
  18. PhysicsCategory.Breakable //设置物理体标识
  19. node.position = obstaclesTileMap.centerOfTile(
  20. atColumn: column, row: row)
  21. obstaclesTileMap.addChild(node)
  22. }
  23. }
  24. }

7、step7 添加杀虫喷雾的设置

  1. func createBugspray(quantity: Int) {
  2. // 1
  3. let tile = SKTileDefinition(texture:
  4. SKTexture(pixelImageNamed: "bugspray")) //创建瓦片定义
  5. // 2
  6. let tilerule = SKTileGroupRule(adjacency:
  7. SKTileAdjacencyMask.adjacencyAll, tileDefinitions: [tile]) //创建瓦片组规则
  8. // 3
  9. let tilegroup = SKTileGroup(rules: [tilerule]) //创建瓦片组
  10. // 4
  11. let tileSet = SKTileSet(tileGroups: [tilegroup]) //创建瓦片集
  12. // 5
  13. let columns = background.numberOfColumns //读取背景瓦片地图节点的列数
  14. let rows = background.numberOfRows //读取背景瓦片地图节点的行数
  15. bugsprayTileMap = SKTileMapNode(tileSet: tileSet,
  16. columns: columns,
  17. rows: rows,
  18. tileSize: tile.size) //创建新的瓦片地图节点
  19. // 6
  20. for _ in 1...quantity {
  21. let column = Int.random(min: 0, max: columns-1) //随机列数
  22. let row = Int.random(min: 0, max: rows-1) //随机行数
  23. bugsprayTileMap?.setTileGroup(tilegroup,
  24. forColumn: column, row: row) //在新额的瓦片地图节点上随机生成瓦片组
  25. }
  26. // 7
  27. bugsprayTileMap?.name = "Bugspray" //设置瓦片地图节点的标识
  28. addChild(bugsprayTileMap!) //将瓦片地图添加到场景
  29. }

8、step8 添加摄像头设置

  1. func setupCamera() {
  2. guard let camera = camera, let view = view else { return }
  3. let zeroDistance = SKRange(constantValue: 0)
  4. let playerConstraint = SKConstraint.distance(zeroDistance,
  5. to: player) //对玩家进行约束
  6. // 1
  7. let xInset = min(view.bounds.width/2 * camera.xScale,
  8. background.frame.width/2)
  9. let yInset = min(view.bounds.height/2 * camera.yScale,
  10. background.frame.height/2)
  11. // 2
  12. let constraintRect = background.frame.insetBy(dx: xInset,
  13. dy: yInset)
  14. // 3
  15. let xRange = SKRange(lowerLimit: constraintRect.minX,
  16. upperLimit: constraintRect.maxX)
  17. let yRange = SKRange(lowerLimit: constraintRect.minY,
  18. upperLimit: constraintRect.maxY)
  19. let edgeConstraint = SKConstraint.positionX(xRange, y: yRange)
  20. edgeConstraint.referenceNode = background
  21. // 4
  22. camera.constraints = [playerConstraint, edgeConstraint]
  23. }

9、step9 获取瓦片的一些帮助方法

  1. func tile(in tileMap: SKTileMapNode,
  2. at coordinates: TileCoordinates)
  3. -> SKTileDefinition? {
  4. return tileMap.tileDefinition(atColumn: coordinates.column,
  5. row: coordinates.row)
  6. }
  7. func tileCoordinates(in tileMap: SKTileMapNode,
  8. at position: CGPoint) -> TileCoordinates {
  9. let column = tileMap.tileColumnIndex(fromPosition: position)
  10. let row = tileMap.tileRowIndex(fromPosition: position)
  11. return (column, row)
  12. }
  13. func tileGroupForName(tileSet: SKTileSet, name: String)
  14. -> SKTileGroup? {
  15. let tileGroup = tileSet.tileGroups
  16. .filter { $0.name == name }.first
  17. return tileGroup
  18. }

10、step10 点击场景的设置

  1. override func touchesBegan(_ touches: Set<UITouch>,
  2. with event: UIEvent?) {
  3. guard let touch = touches.first else { return }
  4. switch gameState {
  5. // 1
  6. case .start: //开始状态
  7. gameState = .play //切换成游戏状态
  8. isPaused = false //开始
  9. startTime = nil
  10. elapsedTime = 0
  11. // 2
  12. case .play: //游戏状态
  13. player.move(target: touch.location(in: self)) //移动玩家
  14. case .win: //获胜状态
  15. transitionToScene(level: currentLevel + 1) //切换场景
  16. case .lose: //落败状态
  17. transitionToScene(level: 1) //切换场景
  18. case .reload: //唤醒状态
  19. // 1
  20. if let touchedNode =
  21. atPoint(touch.location(in: self)) as? SKLabelNode {
  22. // 2
  23. if touchedNode.name == HUDMessages.yes { //如果点击的节点是YES
  24. isPaused = false
  25. startTime = nil
  26. gameState = .play
  27. // 3
  28. } else if touchedNode.name == HUDMessages.no { //如果点击的节点是NO
  29. transitionToScene(level: 1)
  30. }
  31. }
  32. default:
  33. break
  34. }
  35. }

11、step11 切换场景的设置

  1. func transitionToScene(level: Int) {
  2. // 1
  3. guard let newScene = SKScene(fileNamed: "Level\(level)")
  4. as? GameScene else {
  5. fatalError("Level: \(level) not found")
  6. }
  7. // 2
  8. newScene.currentLevel = level
  9. view!.presentScene(newScene,
  10. transition: SKTransition.flipVertical(withDuration: 0.5))
  11. }

12、step12 刷帧

  1. override func update(_ currentTime: TimeInterval) {
  2. if gameState != .play {
  3. isPaused = true //如果不是游戏状态就暂停刷帧
  4. return
  5. }
  6. if !player.hasBugspray {
  7. updateBugspray() //如果玩家没有杀虫喷雾, 就进行更新
  8. }
  9. advanceBreakableTile(locatedAt: player.position) //更新障碍物的物理体状态
  10. updateHUD(currentTime: currentTime) //更新文字说明
  11. checkEndGame() //检查是否达到胜负条件
  12. }

13、step13 更新杀虫喷雾

  1. func updateBugspray() {
  2. guard let bugsprayTileMap = bugsprayTileMap else { return }
  3. let (column, row) = tileCoordinates(in: bugsprayTileMap,
  4. at: player.position)
  5. if tile(in: bugsprayTileMap, at: (column, row)) != nil {
  6. bugsprayTileMap.setTileGroup(nil, forColumn: column,
  7. row: row)
  8. player.hasBugspray = true
  9. }
  10. }

14、step14 更新障碍物的物理体状态

  1. func advanceBreakableTile(locatedAt nodePosition: CGPoint) {
  2. guard let obstaclesTileMap = obstaclesTileMap else { return }
  3. // 1
  4. let (column, row) = tileCoordinates(in: obstaclesTileMap,
  5. at: nodePosition)
  6. // 2
  7. let obstacle = tile(in: obstaclesTileMap,
  8. at: (column, row))
  9. //3
  10. guard let nextTileGroupName =
  11. obstacle?.userData?.object(forKey: "breakable") as? String
  12. else { return }
  13. // 4
  14. if let nextTileGroup =
  15. tileGroupForName(tileSet: obstaclesTileMap.tileSet,
  16. name: nextTileGroupName) {
  17. obstaclesTileMap.setTileGroup(nextTileGroup,
  18. forColumn: column, row: row) //设置新的瓦片组到瓦片地图中
  19. }
  20. }

15、step15 更新文字说明

  1. func updateHUD(currentTime: TimeInterval) {
  2. // 1
  3. if let startTime = startTime {
  4. // 2
  5. elapsedTime = Int(currentTime) - startTime
  6. } else {
  7. // 3
  8. startTime = Int(currentTime) - elapsedTime
  9. }
  10. // 4
  11. hud.updateTimer(time: timeLimit - elapsedTime) //对文字说明进行更新
  12. }

16、step16 检查是否达到胜负条件

  1. func checkEndGame() {
  2. if bugsNode.children.count == 0 { //是否消灭全部害虫
  3. player.physicsBody?.linearDamping = 1
  4. gameState = .win
  5. } else if timeLimit - elapsedTime <= 0 { //是否时间用完
  6. player.physicsBody?.linearDamping = 1
  7. gameState = .lose
  8. }
  9. }

17、step17 物理世界代理的设置

  1. extension GameScene : SKPhysicsContactDelegate {
  2. func remove(bug: Bug) { //消灭害虫
  3. bug.removeFromParent()
  4. background.addChild(bug)
  5. bug.die()
  6. hud.updateBugCount(with: bugsNode.children.count)
  7. }
  8. func didBegin(_ contact: SKPhysicsContact) {
  9. let other = contact.bodyA.categoryBitMask
  10. == PhysicsCategory.Player ?
  11. contact.bodyB : contact.bodyA
  12. switch other.categoryBitMask {
  13. case PhysicsCategory.Bug:
  14. if let bug = other.node as? Bug {
  15. remove(bug: bug) //当玩家接触到普通害虫, 消灭普通害虫
  16. }
  17. case PhysicsCategory.Firebug:
  18. if player.hasBugspray {
  19. if let firebug = other.node as? Firebug {
  20. remove(bug: firebug)
  21. player.hasBugspray = false //当玩家手持杀虫喷雾接触高级害虫才能消灭高级害虫
  22. }
  23. }
  24. case PhysicsCategory.Breakable:
  25. if let obstacleNode = other.node {
  26. // 1
  27. advanceBreakableTile(locatedAt: obstacleNode.position) //更新障碍物
  28. // 2
  29. obstacleNode.removeFromParent() //删除原障碍物
  30. }
  31. default:
  32. break
  33. }
  34. if let physicsBody = player.physicsBody {
  35. if physicsBody.velocity.length() > 0 {
  36. player.checkDirection() //进行玩家方向的设置
  37. }
  38. }
  39. }
  40. }

18、step18 观察者的设置

  1. extension GameScene {
  2. func applicationDidBecomeActive() {
  3. print("* applicationDidBecomeActive")
  4. if gameState == .pause {
  5. gameState = .reload //重新进入, 进行游戏重载
  6. }
  7. }
  8. func applicationWillResignActive() {
  9. print("* applicationWillResignActive")
  10. isPaused = true
  11. if gameState != .lose {
  12. gameState = .pause //暂停游戏进程
  13. }
  14. }
  15. func applicationDidEnterBackground() {
  16. print("* applicationDidEnterBackground")
  17. if gameState != .lose {
  18. saveGame() //进入后台保存游戏进度
  19. }
  20. }
  21. func addObservers() {
  22. NotificationCenter.default.addObserver(self,
  23. selector: #selector(applicationDidBecomeActive),
  24. name: .UIApplicationDidBecomeActive, object: nil)
  25. NotificationCenter.default.addObserver(self,
  26. selector: #selector(applicationWillResignActive),
  27. name: .UIApplicationWillResignActive, object: nil)
  28. NotificationCenter.default.addObserver(self,
  29. selector: #selector(applicationDidEnterBackground),
  30. name: .UIApplicationDidEnterBackground, object: nil)
  31. }
  32. }

19、step19 游戏的存储设置

  1. extension GameScene {
  2. func saveGame() {
  3. // 1
  4. let fileManager = FileManager.default
  5. guard let directory =
  6. fileManager.urls(for: .libraryDirectory,
  7. in: .userDomainMask).first
  8. else { return }
  9. // 2
  10. let saveURL = directory.appendingPathComponent("SavedGames")
  11. // 3
  12. do {
  13. try fileManager.createDirectory(atPath: saveURL.path,
  14. withIntermediateDirectories: true,
  15. attributes: nil)
  16. } catch let error as NSError {
  17. fatalError(
  18. "Failed to create directory: \(error.debugDescription)")
  19. }
  20. // 4
  21. let fileURL = saveURL.appendingPathComponent("saved-game")
  22. print("* Saving: \(fileURL.path)")
  23. // 5
  24. NSKeyedArchiver.archiveRootObject(self, toFile: fileURL.path) //文件处理器新建路径并归档
  25. }
  26. override func encode(with aCoder: NSCoder) { /对关键属性的归档
  27. aCoder.encode(firebugCount,
  28. forKey: "Scene.firebugCount")
  29. aCoder.encode(elapsedTime,
  30. forKey: "Scene.elapsedTime")
  31. aCoder.encode(gameState.rawValue,
  32. forKey: "Scene.gameState")
  33. aCoder.encode(currentLevel,
  34. forKey: "Scene.currentLevel")
  35. super.encode(with: aCoder)
  36. }
  37. class func loadGame() -> SKScene? { //重新加载存储游戏进程
  38. print("* loading game")
  39. var scene: SKScene?
  40. // 1
  41. let fileManager = FileManager.default
  42. guard let directory =
  43. fileManager.urls(for: .libraryDirectory,
  44. in: .userDomainMask).first
  45. else { return nil }
  46. // 2
  47. let url = directory.appendingPathComponent(
  48. "SavedGames/saved-game")
  49. // 3
  50. if FileManager.default.fileExists(atPath: url.path) {
  51. scene = NSKeyedUnarchiver.unarchiveObject( //根据路径进行解档游戏进程
  52. withFile: url.path) as? GameScene
  53. _ = try? fileManager.removeItem(at: url)
  54. }
  55. return scene
  56. }
  57. }

三、运行效果与文件截图

1、运行效果

strip

2、文件截图

blob.png

PestControl文件里的截图:

blob.png

PestControl.xcodeproj文件里的截图:

blob.png

四、其他补充

Notice: 忽略了一些节点的设置, 但不影响瓦片地图的理解.

注:本文著作权归作者,由demo大师(http://www.demodashi.com)宣传,拒绝转载,转载需要作者授权

发表评论

表情:
评论列表 (有 0 条评论,460人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Bing地图瓦片体系

      Bing地图瓦片体系称作BMTS。 Bing使用墨卡托投影。 为了优化地图的检索和显示,渲染地图被切割成256x256像素。不同Lod等级的象素数目不同,同样瓦片数