What’s New In SpriteKit on iOS 10: A Look At Tile Maps

Caroline Begbie

Update 9/26/16: This tutorial has been updated for Xcode 8 and Swift 3.

What's New In SpriteKit? - Tile maps! Read on to find out more.

What’s New In SpriteKit? – Tile maps! Read on to find out more.

WWDC 2016 introduced a whole new batch of goodies. One of the best is the SpriteKit Tile Editor. It’s a blast to use, and you can get your backgrounds up and running and looking great really fast.

In this tutorial you’ll learn all about tiles – how to import them and paint them in the new Tile Editor. In the new game Rubber Duckie Rescue that’s sure to be a smash-hit, you’ll rescue some rubber duckies along the way.

Getting Started

First, download the starter project for Rubber Duckie Rescue, and make sure you have Xcode 8 installed.

Currently the game is in its infancy; by the end of this tutorial, you’ll control a car that will drive through an exciting tiled landscape on a mission to rescue those pesky ducks.

Build and run the app. You’ll see that you have a car that responds to touch. Touch to send the car to a particular location, or pan to have the car follow the touch. At the moment, the background is just a solid gray colour. But don’t worry! You’ll change that momentarily.

GameStart

What is a Tile Map?

When creating a background, you have the option of either using one large image or using smaller images called tiles. These tiles are then carefully placed one-by-one, creating a complete image.

If you’re creating a golfing game, for example, you might have grass tiles and sandy bunker tiles. You might also have tiles that define the edges between the grass and the bunker.

Advantages of a Tile Map

  • Control: Previously, each tile would be a new SKSpriteNode, and placing all those nodes could be a bit of a nightmare.

    Now you place one SKTileMapNode, and then paint it with tiles. It’s easy to place individual tiles and amazingly easy to paint a whole background in seconds. You can even paint animated tiles!

    You can place a village on one tiled layer and grass on a lower layer. To change from summer to winter, just replace the lush verdant grass layer with a desolate brown layer. The village layer remains the same.

  • Performance: Aside from the convenience of painting tiles, performance will be improved. Large images take up memory. Smaller tiles can be repeated, and take up fewer resources.
  • Flexibility: Each tile can be addressed in code. This means you know exactly on which tile your character is currently standing.

    In this tutorial, you’ll be making the background environment using grass and water tiles that have different effects on the car’s movement. You’ll also create a tiled area with ducks placed randomly for the car to rescue.

Types of Tile Maps

There are four types of Tile Sets you can create: grid, isometric, hexagonal pointy or hexagonal flat. You choose the tile set depending on the style of your game.

Rubber Ducky Rescue uses a square grid. This a top down game, but you can also use grid tiles for a side scroller.

GridTiles

Isometric tiles are a diamond shape as in Ravenwood Fair.

There are two ways of placing hexagons: with the point on top or the flat side on top. This is an example of a pointy hexagon tile grid:

PointyHexTiles

Sid Meier’s Civilization moved with great visual effect from an isometric grid in Civ 4 to a hexagonal grid in Civ 5.

How to Create a Tile Map

In Rubber Duckie Rescue, you’ll first paint two simple background tile maps with grass and water, replacing the current gray background. Later, you’ll create a new tile map in code with the objects that the car needs to collect.

To create a tile map, you’ll first need some tile images. Tile map art is an art form all of its own. Fortunately, there are a few free asset sites that developers can use.

Your game will use assets from kenney.nl. This is a great site with tons of tiles and other sprites available. Thanks Kenney! =]

all-the-assets

Managing Your Images in the Asset Catalog

The starter project has an asset catalog named Assets.xcassets. It includes all of the tile and sprite images you’ll need for this tutorial.

Images

The tile images are in texture atlases instead of folders. Texture atlases look and behave just like a folder.

The advantage of using texture atlases is performance. When your game is rendered, SpriteKit will make a “draw call” on the GPU for each and every texture you send it. If you combine multiple images into one texture — called a texture atlas — instead of having multiple draw calls, there’s only one, which is more efficient.

The asset catalog can automatically combine your sprite images into texture atlases. You don’t need to worry about how to work out the position of the sprite image in the texture atlas — SpriteKit handles all that for you.

You’ll create your tile sets using the images in this asset catalog.

Creating a Tile Set

Before getting into too much detail, you’ll do a quick overview of the tile creating and painting process. Later, you’ll create and paint a more complex tile set.

Choose File > New > File, scroll down and choose the iOS > SpriteKit Tile Set template and click Next.

NewTileSet

Name the set Tiles, and click Create.

Select Tiles.sks in the project navigator.

A new Grid Tile Set and a Tile Group has automatically been created for you. You will have a group for every set of tiles. In this tutorial, you’ll create groups for Grass and Water.

TileSet

First, select Tile Set, and in the Attributes Inspector, rename the Tile Set to Ground Tiles.

NameTileSet

Now, select new tile group in the set list panel. Again, in the Attributes Inspector, rename it to Water Tile.

Below the Attributes Inspector, select the Media Library. If you’ve not used this before, you’ll quickly discover how handy it is for creating tile sets.

MediaLibrary

Locate the WaterCenter image and drag it onto the empty tile area.

FirstTile

That’s the first tile group defined, now you’ll create a second tile group.

Select Ground Tiles in the set list panel and click the + at the bottom. Select New Single Tile Group.

AddNewTileGroup

Select new tile group and in the Attributes Inspector change the name of new tile group to Grass Tile.

Locate the GrassCenter1 image in the Media Library and drag it to the empty Tile area.

CompletedFirstTiles

Working With Tile Variants

Click on the grass tile in the center of the screen.

TileVariants

At the bottom of the screen you can add new tile variants. When you paint your tiles, the editor will automatically choose one of these variants randomly, so that you get some variation in your map. Each variant can be given a Placement Weight in the Attributes Inspector. If you, for example, gave one variant a placement weight of 1, and a second variant a placement weight of 2, then the second variant would be laid down twice as often when you paint.

Drag the GrassCenter2 image to the empty variant slot and then the GrassCenter3 image to the next empty variant slot.

TileVariantsAll

Note: At the time of the latest update to this article, there’s a small bug. If you paint your tiles now, the grass tile won’t paint properly. To combat this, just quit Xcode and restart. (You can alternatively add an extra new empty tile group to your tile set and delete it straightaway.)

You’ve created your first tile set and now it’s time to go painting!

The Tile Map Editor

Select GameScene.sks. This scene currently just contains the car sprite.

From the Object Library at the bottom right drag a Tile Map Node onto the scene.

ObjectLibrary

In the Attributes Inspector your Ground Tiles set has automatically been chosen as it’s the only tile set in the project. The Tile Size has automatically been set to the size of the tiles in that Tile Set. Change the Map Size to 32 Columns and 24 Rows. Ensure that the Position X and Y are set to 0 and Scale X and Y are set to 1.

TileMapNode

Double-click on the Tile Map Node in the scene editor to start the tile editor. Or you can instead choose Editor > Edit Tile Map with the tile map node selected. If you have layers of tile map nodes, this menu option is very convenient.

TileEditor

If you don’t see a grid, zoom into the scene until you do.

At the top of the scene view there’s a new tool bar with the brush tool already selected and loaded with the water tile. Click and drag across the tile map and paint!

Painting

To change the tile, click the icon just to the left of the brush icon on the toolbar and choose the Grass tile from the drop down display.

TileGroups

Paint again. Notice the three Grass variants paint randomly. As much fun as this is, it’ll soon get even better :].

PaintingGrass

The icons on the toolbar are similar to other paint programs. Going from left to right:

Toolbar

  • The Hand tool allows you to drag the scene around.
  • The Eyedropper tool picks a tile from those already painted.
  • You’ve already seen what the Select Tile and Brush tools do.
  • The Flood Fill tool flood fills similar contiguous tiles with your selected tile.
  • The Erase tool will remove tiles.
  • If you are painting large areas, Select Brush Size allows you to change the brush size.
  • The two stamp tools are fun. Choose Create Stamp (the stamp tool with the little circle). Click in a square and then click in another square to the right and down a bit to make a rectangular selection of tiles. You’ve now created a stamp.
  • Choose Select Stamp (the stamp tool with the arrow), and a drop down of all the stamps you’ve created will show. Click on a stamp to select it, and then paint with it. The shaded area shows where the stamp will go. This makes it super easy to paint large areas.
  • Randomize clears the tiles and randomizes the currently selected tile. This is a good way to paint a large area.

When you’ve finished experimenting, fill the whole map with water tiles. You can flood fill empty areas. This will be the background, and you’ll be able to overlay more detail in a second tile map node.

WaterTilesPainted

Now that you’ve had an overview of how to create and paint tiles, in the next section you’ll create a more complicated tile set with edge tiles.

Working With Adjacency Groups

Adjacency groups let you define what edge images should be painted when you paint your tiles. For example, if you are creating an island, you would have a center tile of grass, and the edge tiles would be the beach going into the ocean. As you paint, center grass tiles will be laid down with the edges automatically surrounding them.

There are two approaches to tile edges. You can have solid edge tiles that transition from one surface to another, like this tile set from kenney.nl:

KenneyTileMap1

The other approach is to have transparent edges such as this set, which you will be using here:

KenneyTileMap2A

If you use transparent edges, you must layer your tile map nodes so the background shows through the transparency. You’ve already painted the water background. Now you’ll add another layer for the land.

Go back to Tiles.sks to edit your tile set. Control+click Ground Tiles and select New > 8-Way Adjacency Group from the menu.

NewAdjacency

Rename new tile group to Grass. When you select the Grass tile group, you’ll see a grid of tiles to fill.

AdjacencyTileGrid

This may seem confusing at first, but all will become clear.

Filter the media library with grass to show all the grass tile images.

GrassTiles

Drag the tiles to the relevant positions in the grid. I’ve named each image according to its position in the grid. Add the three variants for the center just as you did before. This time, select the GrassCenter1 variant and change the Placement Weight to 10. This way you’ll get more plain tiles when you paint.

GrassTilesWithEdges

When you drag the images into place, you might make mistakes. I, myself, frequently confuse corners and edges :]. No worries. Simply drag the correct image over the wrong image and choose Replace tile variant texture from the pop-up menu. Note that if you choose the other option you can add variants this way.

When you’ve finished, your grid should look like the image above. Quit and restart Xcode so that your changes will propagate to the scene editor.

Open GameScene.sks in the project navigator.

Drag a new Tile Map Node onto the scene. Change the name of the node to landBackground. You’ll need this because later, you’ll refer to the node in code. Change the Map Size to 32 Columns and 24 Rows. Verify that the Position X and Y are set to 0 and Scale X and Y are set to 1.

LandTileNode

Double-click on the scene editor to edit the tile map.

On the toolbar, click Select Tile. The new Grass tile group will show on the drop down along with the other two tile groups.

GrassTileGroup

Select the right-most tile group Grass, click the brush tool on the toolbar and start painting.

PaintedEdgeTiles

See how the edges magically surround where you paint!

Remembering back to that confusing grid in Tiles.sks, here’s how the tiles were painted:

TilePositions

In the Attributes Inspector untick Enable Automapping.

EnableAutomapping

Now when you click on Select Tile, the drop-down will show all available tiles. You can now paint individual squares in the grid with your selected tile.

EnableAutomappingTiles

When you’ve finished painting click Done. Now, build and run your app to admire your new background :].

AppBackground Tiles

New SpriteKit Tile Classes

In addition to creating tile maps with the scene editor, you can also create them in code. I know what you’re thinking, “Why do that when you can use the scene editor?”

Suppose you need to recognize specific tiles in your code, such as the water tiles? In this tutorial, water tiles slow the car’s speed. You can do this in code!

Another reason is randomness. In this tutorial, the player collects duckies and gas cans. If you painted these with the editor, they would remain in the same spot for every game. If you add them in code, you can randomize their placement.

Reviewing what you’ve done so far, each item you created has a corresponding new SpriteKit class.

SKTileClassesA

In GameScene.sks, they are:

  • SKTileMapNode: the node you placed in the scene editor.
  • SKTileSet: the tile set you assigned in the Attributes Inspector for the tile map node.

Open Tiles.sks. Here you have:

  • SKTileSet: the item at the top of the tree, here named Ground Tiles.
  • SKTileGroup: the tile groups inside the set. In this set, they are Water Tile, Grass Tile and Grass.
  • SKTileGroupRule: defines the adjacency rule for each tile. For example, Upper Left tile, Center tile or Right Edge tile.

Click on the Grass group, and select the Center rule.

  • SKTileDefinition: each Rule has a number of tile definitions. These are the tile variants. For example, the Center tile has three SKTileDefinition variants that are used randomly when painting.

Select one of the Center variants at the bottom of the screen. In the Attributes Inspector, you see all the properties available to you for each SKTileDefinition variant.

If you had animated tiles, you would see the per-frame information, and you’d be able to control the frame rate. Imagine your water tiles lapping against the grass tiles :].

The Attributes Inspector for each variant is also where you can supply user data for each tile variant. This will be useful later when you want to determine whether an object is a duck or a gas can.

Dealing With Tiles in Code

Finally, you get to write some code :]. Isn’t it amazing how far you went without writing any?

When the car “drives” over the water tiles its speed should slow dramatically. There are two ways of approaching this.

You can add a user data isWater Boolean to all of the water tiles, and check the user data in code. Or, because you created the water on a separate layer, you can use the transparency in the landBackground tile map node, and test to see if the tile under the car’s position is empty. That’s what we’ll do here.

In GameScene.swift, add the property for landBackground to GameScene:

var landBackground:SKTileMapNode!

Next, add this code to loadSceneNodes():

guard let landBackground = childNode(withName: "landBackground") 
                               as? SKTileMapNode else {
  fatalError("Background node not loaded")
}
self.landBackground = landBackground

Here, you loaded the tile map node into the landBackground property.

Now, add the following to update(_:):

let position = car.position
let column = landBackground.tileColumnIndex(fromPosition: position)
let row = landBackground.tileRowIndex(fromPosition: position)

update(_:) is performed every frame; it’s a good place to check the position of the car. Here, you converted the car’s position into a column and a row. This is so you can extract the actual tile from the tile map.

Next, add this code to the end of update(_:):

let tile = landBackground.tileDefinition(atColumn: column, row: row)

tile now contains the SKTileDefinition for the tile at the column and row.

Finally, add this code to the end of update(_:):

if tile == nil {
  maxSpeed = waterMaxSpeed
  print("water")
} else {
  maxSpeed = landMaxSpeed
  print("grass")
}

If there’s no tile painted, the car’s maximum speed is lowered to the appropriate value. You’re using the transparency in landBackground to determine this. Transparent tiles are assumed to be water.

Build and run the app.

AppWaterSpeedA

The car’s maximum speed should be now be much slower when it’s driving on water. You can confirm this by reviewing the console too.

Collecting objects

Now it’s time to use the knowledge gained so far to create a tile map filled with objects for the car to collect.

You’ll randomly fill the tile map with rubber duckies and gas cans, with one caveat — the ducks stay on water tiles, and the gas on grass tiles.

Create a new tile set for the objects. Choose File > New > File and select the iOS > SpriteKit Tile Set template, then click Next. Name the set ObjectTiles and click Create.

In ObjectTiles.sks, change the name of the tile set to Object Tiles.

Change new tile group to Duck and drag the rubberduck image from the Media Library onto the tile.

Select the duck tile, and then the tile variant at the bottom. In the Attributes Inspector, click the + under User Data. You may have to scroll down the inspector on a smaller screen. Double-click userData1 and change this to duck. It doesn’t matter what type it is as you will just check for existence of this value, so just leave it as an Integer.

RubberDuck

Control+click Object Tiles in the set list and choose New > Single Tile Group. Rename this group to Gas Can and drag the gascan image to the tile.

Select the gas can tile variant, and add user data just like before. Except this time, name the user data gascan.

Now you’ll create a new tile map node entirely in code.

In GameScene.swift, add this new property:

var objectsTileMap:SKTileMapNode!

After loadSceneNodes(), add a new method:

func setupObjects() {
  let columns = 32
  let rows = 24
  let size = CGSize(width: 64, height: 64)
  
  // 1
  guard let tileSet = SKTileSet(named: "Object Tiles") else {
    fatalError("Object Tiles Tile Set not found")
  }

  // 2
  objectsTileMap = SKTileMapNode(tileSet: tileSet,
                                 columns: columns,
                                 rows: rows,
                                 tileSize: size)

  // 3
  addChild(objectsTileMap)
}

Going through the code:

  1. Take the tile set you created in ObjectTiles.sks. Note that the named parameter is the name of the tile set and not the name of the .sks file
  2. Create the new tile map node using the tile set.
  3. Add the tile map node to the scene hierarchy.

Add this to the end of setupObjects():

// 4
let tileGroups = tileSet.tileGroups
    
// 5
guard let duckTile = tileGroups.first(where: {$0.name == "Duck"}) else {
  fatalError("No Duck tile definition found")
}
guard let gascanTile = tileGroups.first(where: {$0.name == "Gas Can"}) else {
  fatalError("No Gas Can tile definition found")
}

// 6
let numberOfObjects = 64

Again, going through the code:

  1. Retrieve the list of tile groups from the tile set.
  2. Retrieve the tile definitions for the tiles from the tile groups array. tileGroups.first(where:) is a handy new method in Swift 3 to find the first occurrence of an object in an array.
  3. Set up the constant needed for placing the objects. You can change numberOfObjects to suit.

Now continue in the same method:

// 7
for _ in 1...numberOfObjects {
      
  // 8
  let column = Int(arc4random_uniform(UInt32(columns)))
  let row = Int(arc4random_uniform(UInt32(rows)))
      
  let groundTile = landBackground.tileDefinition(atColumn: column, row: row)

  // 9
  let tile = groundTile == nil ? duckTile : gascanTile
      
  // 10
 objectsTileMap.setTileGroup(tile, forColumn: column, row: row)
}

Again, going through the code:

  1. Loop to place 64 objects.
  2. Randomly select a column and row.
  3. If the tile selected is solid, select a gas can; otherwise select a duck. This will ensure ducks on water and gas cans on grass.
  4. Place the duck or gas can in the tile, at the selected column and row.

At the end of didMove(to:), call this new method:

setupObjects()

Build and run. You should see your background littered with ducks on the water and gas cans on the grass.

AppWithDucks

The final thing left to do is write the code to pick them all up with the car. This is similar to the code you wrote earlier to detect if you were on a water tile, except you’ll use the user data for the duck and the gas can.

Back in GameScene.swift, add two properties to the top of GameScene:

lazy var duckSound:SKAction = {
  return SKAction.playSoundFileNamed("Duck.wav", waitForCompletion: false)
}()
lazy var gascanSound:SKAction = {
  return SKAction.playSoundFileNamed("Gas.wav", waitForCompletion: false)
}()

Here, you created two actions to play the sounds when the objects are collected. These sounds are included in the starter project.

At the end of update(_:), add this code:

let objectTile = objectsTileMap.tileDefinition(atColumn: column, row: row)

if let _ = objectTile?.userData?.value(forKey: "gascan") {
  run(gascanSound)
  objectsTileMap.setTileGroup(nil, forColumn: column, row: row)
}
    
if let _ = objectTile?.userData?.value(forKey: "duck") {
  run(duckSound)
  objectsTileMap.setTileGroup(nil, forColumn: column, row: row)
}

Here, you check the user data to determine if it contains gascan or duck. Then, you play the appropriate sound and set the tile group to nil. This removes the tile from view.

Build and run the app, and collect gas cans and duckies galore! So much easier than sprite collision :].

AppCollectingDucks

Where to Go From Here?

I hope you enjoyed learning how to use the tile map editor. It’s a fantastic addition to the SpriteKit tools!

You can download the final project here.

To discover more tile techniques and other exciting additions, watch the WWDC 2016 video What’s New in SpriteKit.

If you have any questions or comments, please join the forum discussion below!

Credits:

Caroline Begbie

I’m an indie iOS developer. When I’m not developing, I’m playing around with 2D and 3D animation software, or learning Arduino and electronics.
In my past I’ve taught the elderly how to use their computers, done marionette shows for pre-schools, and created accounting and stock control systems for mining companies.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 44 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 13 total!

Resident Authors Team

... 17 total!

Podcast Team

... 3 total!

Recruitment Team

... 9 total!