Procedural Tile Maps with Sprite Kit

Nick Cracchiolo
5 min readMay 9, 2018

--

I recently started a new project: to build a city building/simulation game. I wanted to create procedural terrains using SpriteKit and it’s SKTileMapNode class. I began looking online and reading the documentation and there wasn’t much about how to do this programmatically. So, I decided to write this tutorial series on what I learn while building my new game. First up, creating a basic tile grid procedurally!

SpriteKit and Tiles

Apple has created a pretty easy way to manage a whole bunch of tiles and their placements into an SKScene, however this doesn’t allow us (at least not that I have discovered) to procedurally generate our tile maps. Therefore we must dig a little deeper into their implementation.

First let’s create a new Xcode project using the “Game” Template and set our engine to SpriteKit.

New Xcode Project Selection Screen

Next let’s add one Apple’s default tile sets. So we go to File -> New -> File. Then go to the “Resouce” section and select “SpriteKit Tile Set”. Then select whatever tile type you are looking for, (Apple supports Grid, Isometric, and Hexagonal) it doesn’t much matter what one you are using for this example.

New File Selection screen for “SpriteKit Tile Set” under the “Resource” section
TileSet type selection

This creates an SKTileSet, with all of the tile images included. It then seperates the set into distinct SKTileGroups. These groups will be the different terrains, so water, grass, sand, etc. Within those groups are SKDefinitions which define what tile goes where when being placed. We don’t need to worry much about this right now but for more info go see this WWDC 2016 video: “What’s New in SpriteKit, Session 610”.

SKTileSet example from MyTileSet.sks with SKTileGroups: “Grass”, “Sand”, “Cobblestone”, and “Water”

GKNoise

The most important part of this map is that it is procedurally generated, and in order to do this, we will use GKNoise to create a “map” of how we are going to layout our tiled world. Apple has several different types of prebuilt noise sources (GKNoiseSource), but for our use case we are going to use GKPerlinNoiseSource. This source is designed to give us natural looking terrains.

func createNoiseMap() -> GKNoiseMap {
//Get our noise source, this can be customized further
let source = GKPerlinNoiseSource()
//Initalize our GKNoise object with our source
let noise = GKNoise.init(source)
//Create our map,
//sampleCount = to the number of tiles in the grid (row, col)
let map = GKNoiseMap.init(noise, size: vector2(1.0, 1.0), origin: vector2(0, 0), sampleCount: vector2(50, 50), seamless: true)
return map
}

First we create our noise source based off GKPerlinNoiseSource.

Then using our source we create a new GKNoise object.

Finally we create a map using that noise object. Make sure to set the sampleCount parameter to the same number of rows and cols that you will use for the SKTileMapNode later on. See GKNoiseMap for more on how its parameters work.

SKTileMapNode

Ok so now we can finally make our map. To do this in SpriteKit we will be using an SKTileMapNode which will merge all of our individual tile sprites into a single node, making the tile map basically an SKNode. It also inherets everything that SKNode could do, so your can transform this tile map with SKActions, just as you would an SKSpriteNode.

Lets look at some code

//Get out noise map we made above
let noiseMap = createNoiseMap()
//Define our SKTileSet:
//This is the name of the Set within the .sks TileSet file
let tileSet = SKTileSet(named: "Sample Isometric Tile Set")!
//Define our Tile Size:
//This depends on the set but the Isometric set uses 128x64
let tileSize = CGSize(width: 128, height: 64)
//Define our Columns and Rows
let rows = 50, cols = 50
//Create our Tile Map
let map = SKTileMapNode(tileSet: tileSet,
columns: cols,
rows: rows,
tileSize: tileSize)
map.enableAutomapping = true
for col in 0..<cols {
for row in 0..<rows {
//Get a value from our Noise Map returns -1.0 to 1.0
let val = noiseMap.value(at: vector2(Int32(row),Int32(col)))
//We will then decide what tiles correspond to what value
switch val {
case -1.0..<(-0.5):
if let g = tileSet.tileGroups.first(where: {
($0.name ?? "") == "Water"}) {
map.setTileGroup(g, forColumn: col, row: row)
}
default:
if let g = tileSet.tileGroups.first(where: {
($0.name ?? "") == "Grass"}) {
map.setTileGroup(g, forColumn: col, row: row)
}
}
}
}
self.addChild(map)

Our code is pretty simple, we generate our Noise Map, Tile Set, Tile Size, and map size. Then we initialize our SKTileMap node and enable automapping. Automapping is used to allow SpriteKit to automatically set the tile type based on the tiles around it. You can see the different kinds of tiles used while looking at an SKTileGroup in the TileSet .sks file that we created in our Xcode project.

Next we set what tile group goes where based on value specified in the Noise Map. Because the Noise Map returns a value from -1.0 to 1.0, we will use Swift Ranges to build our map. You can cutomize this area all your like, but my example, sets any tile with a -1.0 to -0.5 value to water and the rest to grass (The default Isometric tile set also has cobblestone and sand tiles that you can use too). Finally we add our map as a child of our SKScene.

Summary

We have now sucessfully created a tile map with procedurally generated terrain. Some next steps would be to add your own tiles to the SKTileSet, modify the switch statement in our tile set double for-loop, and/or play around with the GKNoiseMap object.

To see the sample xcode project check out my github.

Also need a Grade Book/Grade Calculator, go download my iOS app Barely Passing free on the App Store.

Example iOS Simulator output at 0.2 scale

--

--