Working with Maps
- Loading a Tilemap
- Infinite Maps
- Tiled Globals
- Tile Rendering
- Tile Render Flags
- Effects Rendering
- Tile Cracking
Loading a Tilemap
Creating a tilemap node is very straightforward; simply call the SKTilemap.load(tmxFile:)
function to read a Tiled tmx file from disk:
if let tilemap = SKTilemap.load(tmxFile: "MyTilemap.tmx") {
worldNode.addChild(tilemap)
}
If you are implementing the TilemapDelegate
and SKTilesetDataSource
protocols, you can optionally specify those when you load the tilemap:
if let tilemap = SKTilemap.load(tmxFile: tmxFile,
delegate: self as? TilemapDelegate,
tilesetDataSource: self as? SKTilesetDataSource) {
worldNode.addChild(tilemap)
}
Once the map is loaded, you’ll need to add your tilemap node to your SKScene.update
method (unless you’re rendering tiles with SpriteKit Actions):
class GameScene: SKScene {
var tilemap: SKTilemap?
/// Update the tilemap.
override func update(_ currentTime: TimeInterval) {
self.tilemap?.update(currentTime)
}
}
Locating Assets
Loading a tilemap from your project can be tricky depending on how you set up you assets. Passing the inDirectory
argument to the SKTilemap.load
method allows you to specify a directory in which to search for assets.
Once the tilemap is rendered, you can get information about the map’s source file by querying the SKTilemap.url
property.
Infinite Maps
Infinite maps work exactly as other maps do - the only difference is that internally tile layers are composed of chunks (sub-layers) with offset values.
Tiled Globals
The TiledGlobals
class stores default properties that you may tweak for your own projects.
Property | Description |
---|---|
TiledGlobals.renderer |
SpriteKit renderer (get only). |
TiledGlobals.loggingLevel |
Logging verbosity. |
TiledGlobals.updateMode |
Default tile update mode. |
TiledGlobals.zDeltaForLayers |
Default layer SpriteKit offset. |
TiledGlobals.enableRenderCallbacks |
Allow render statistics. |
TiledGlobals.enableCameraCallbacks |
Allow camera updates. |
TiledGlobals.renderQuality |
Global render quality values. |
TiledGlobals.contentScale |
Retina display scale factor (get only). |
TiledGlobals.version |
Framework version (get only). |
Tile Rendering
By default, SKTiled updates each tile on a concurrent background queue. This includes tile animations, ensuring that frames are synced each frame. For performance, tile data is cached and updated with each frame. How the cache is updated is determined by the TileUpdateMode
flag. There are three settings for rendering maps, choose the option that is best suited to your content:
Mode | Description |
---|---|
full |
All tiles are updated each frame. |
dynamic |
Only animated tiles are updated. |
actions |
No tiles are updated, animations are rendered with SpriteKit actions. |
The default tile update method is dynamic
which provides a good mix of performance and accuracy. For best performance, use actions
and for greatest accuracy consider full
.
Tile update mode can be passed as an argument to the load method, or set later:
// tilemap will update only animated tiles
if let tilemap = SKTilemap.load(tmxFile: "MyTilemap.tmx",
delegate: nil,
updateMode: TileUpdateMode.dynamic) {
}
// change the rendering to update all tiles for every frame
tilemap.updateMode = TileUpdateMode.full
Full & Dynamic
Using either of these methods, you can change the SKNode.speed
property of your scene/tilemap/layer which will scale the speed of tile animations accordingly (including moving in reverse). In addition, tile animations are updated independent of frame rate, so animation speed will be the same regardless of fps.
SpriteKit Actions
If your tilemap is set to run in TileUpdateMode.actions
mode, animated tiles will render with SpriteKit Actions. Actions are more efficient with memory and CPU usage. If you are using a large map or don’t need exact frame synchronization, this option might be more viable. While faster, this method might result in frames getting out of sync slightly so use accordingly. Tile animations will respect pausing using this method, but animation speed can’t be changed reliably.
Manually Starting & Stopping
To manually enable/disable SpriteKit actions, call the SKTilemap.runAnimationAsActions(_:restore:)
method:
// run animations as actions
tilemap.runAnimationAsActions(true)
// remove all animations & restore initial texture
tilemap.runAnimationAsActions(false, restore: true)
Passing a value of false
will remove all actions and effectively halt all animation. It is also possible to start & stop SpriteKit actions for each layer individually:
// run SpriteKit actions for all animated tiles in the layer
tileLayer.runAnimationAsActions()
// remove all SpriteKit actions for the layer (stop tile animations)
tileLayer.removeAnimationActions(restore: false)
Tile Render Flags
Individual tiles can also have separate render flags that determine their behavior.
For more information, see the Working with Tiles section.
Effects Rendering
SKTiledLayerObject
layer types are subclassed from the SpriteKit SKEffectNode
node. The SKEffectNode
node renders its children into a private buffer and additionally allows Core Image effects to be applied. You may also apply shaders and distortions to these nodes, allowing for sophisticated rendering effects.
To enable shader effects, turn on the SKTilemap.shouldEnableEffects
attribute. This will consume slightly more CPU & GPU power, so by default it is disabled to conserve resources. One additional benefit is that rendering into a buffer eliminates tile “cracking” artifacts.
tilemap.shouldEnableEffects = true
Effects rendering also softens the edges of nearest-neighbor filtered textures slightly, which may be undesirable for retro-themed games with blocky edges.
Be careful enabling this option for large maps; the SKEffectNode
node buffer maximum texture size is 2048x2048. If your map is larger than that, your map will render as cropped.
Shaders & Filters
Applying a shader (or filter) is done the same way as it is done with a sprite node:
// emable effects rendering on the map node
tilemap.shouldEnableEffects = true
// load a shader from the current project
let shader = SKShader(fileNamed: "waves")
// create an attribute to pass the tilemap's render size to the shader as an attribute
shader.attributes = [SKAttribute(name: "a_map_size", type: .vectorFloat2)]
// set the tilemap shader
tilemap.shader = shader
// pass the tilemap size to the shader
tilemap.setValue(SKAttributeValue(vectorFloat2: tilemap.sizeInPoints.toVec2), forAttribute: "a_map_size")
Applying a CoreImage filter is similar:
let blurFilter = CIFilter(name: "CIBoxBlur", parameters: ["inputRadius": 20])
tilemap.filter = blurFilter
For more details, check out Apple’s SKShader tutorial page & Apple’s SKEffectNode page .
Next: Working with Tilesets - Index