2.3. Working with Layers Document

Previous: Working with Maps - Next: Working with ObjectsReturn to Index


Each tilemap layer is represented by a corresponding subclass of TKLayerNode (for the view) and TKLayer (model). A TKMapNode instance has one TKLayerNode instance as child node for each layer.

Types of Layers

There are three types of layers, represented by the following classes:

  • Tile layers: TKTileLayerNode / TKTileLayer
  • Object layers: TKObjectLayerNode / TKObjectLayer
  • Image layers: TKImageLayerNode / TKImageLayer

The job of tile layers is to render the tilemap. Each tile is represented by a sprite node. A tile layer can freely use tiles of any tileset, although “sprinkling” tiles of different tilesets across the layer may have a detrimental effect on performance as it could interrupt sprite batching, thus creating additional draw calls.

Object layers contain and display the “objects” that you can graphically edit in Tiled. You may not want to “see” these objects in the game, in that case simply remove the TKObjectLayerNode from the TKMapNode instance after you are done processing the objects on the object layer. Alternatively you can set self.hidden to 1 in the layer’s properties to automatically have it hidden in the game while leaving it visible in Tiled.

Image layers simply contain a sprite node with the associated image.

Abstract Base Classes

The TKLayerNode and TKLayer classes are abstract and provide the common functionality and properties shared by all layers.

The TKLayerNode class provides access to the TKLayer instance via its layer property. For convenience it also has tileLayer, objectLayer and imageLayer properties which simply return the layer instance cast to the specific type.

The TKLayerNode class provides access to its TKMapNode and TKMap instances via mapNode and map properties.

The TKLayer class provides access to its map via its map property. It also has a type property that assists in casting the layer instance to the correct subclass if needed.

The TKLayer’s properties provides access to the layer’s TKProperties instance.

Working with Tile Layers

Tile layers are represented by TKTileLayerNode and TKTileLayer.

TKTileLayerNode’s tileOverlap property allows you to specify a tile overlap per layer. For a description of this property see the previous ‘Working with Maps’ article.

The TKTileLayer class provides most of the functionality you’ll need to work with tile layers.

Tile Layer Dimensions

TKTileLayer has a size in grid coordinates which is identical to TKMap’s size property for as long as Tiled does not allow layers to have a different size than the map itself.

You’ll also get the layer’s bounds and innerBounds in points, and corresponding boundsForLayerSize: and innerBoundsForLayerSize methods. These methods give you the dimensions of the tile layer (or an arbitrary size in grid coordinates). This helps define the scrollable area, ie when to stop scrolling the map. The “inner” bounds refers to the area that is guaranteed to be covered 100% by tiles, since isometric and hexagonal tiles have shapes that do not match a rectangle’s outline.

Converting Coordinates

Another commonly needed aspect is converting coordinates from grid coordinates to points and vice versa. TKTileLayer handles this correctly for all map orientations. Coordinate conversion is also the mechanism behind “tile picking”, ie selecting a tapped/clicked tile.

When converting coordinates the only thing you need to keep in mind is that all coordinates are relative to the TKMapNode’s position. So you may need to convert a touch coordinate to the coordinate space of the TKMapNode first.

Converting Point to Grid Coordinates

Use TKTileLayer’s convertPoint: method to convert a point in the TKMapNode’s coordinate space to a grid coordinate.

TKPoint gridCoord = [tileLayer convertPoint:pointInMap];

// Swift
let gridCoord = tileLayer.convertPoint(pointInMap)

Converting Grid Coordinates to Point

Use TKTileLayer’s convertGridCoordinate: method to convert a grid coordinate to a point in the TKMapNode’s coordinate space.

CGPoint pointInMap = [tileLayer convertGridCoordinate:gridCoord];

// Swift
let pointInMap = tileLayer.convertGridCoordinate(gridCoord)

In addition you can specify a TKTileOffset using convertGridCoordinate:offset if you need a different point coordinate other than the tile’s lower-left corner. In this example, the returned point will be at the center of the tile.

CGPoint pointInMap = [tileLayer convertGridCoordinate:gridCoord offset:TKTileOffsetCenter];

// Swift
let pointInMap = tileLayer.convertGridCoordinate(gridCoord, offset:.Center)

The offset can also be obtained separately via pointOffsetFor:. The offsets are defined in TKTileOffset and include the tile’s corners, its center, and for isometric and hexagonal tiles their corner vertices.

Converting Tile Shapes into Vertices

Since you may need the vertices of a tile, you can use createVerticesForTileAt:winding: to get all 4 (hex: 6) vertices of a tile’s shape in a giving TKVertexWinding (clockwise or counter-clockwise).

CGPoint* vertexBuffer = [tileLayer createVerticesForTileAt:gridCoord 
                                                   winding:TKVertexWindingCounterClockwise];
free(vertexBuffer); // the buffer is yours, you'll have to free it eventually!

// Swift
let vertexBuffer = tileLayer.createVerticesForTileAt(gridCoord, 
                                                     winding:TKVertexWindingCounterClockwise)
vertexBuffer.destroy() // the buffer is yours, you'll have to free it eventually!

The winding may be important depending on what you need the vertices for, eg shape drawing, physics collision shapes, pathfinding obstacles. Refer to the respective documentation to see if there is a requirement for winding - or if it’s not documented and you don’t get the desired result, try the other winding type.

As a rule of thumb, physics engines typically expect counter-clockwise winding.

Looking up Neighbor Coordinates

Another common task is to traverse a tilemap by looking at neighboring coordinates. This is trivial for orthogonal and regular isometric maps, but it involves additional calculations for staggered isometric and hexagonal maps. Therefore, prefer to use the neighborCoordAt:inDirection: method to get a neighboring tile in a given direction.

TKPoint neighborCoord = [tileLayer neighborCoordAt:gridCoord 
                                       inDirection:TKCardinalDirectionNorthEast];

// Swift    
let neighborCoord = tileLayer.neighborCoordAt(gridCoord, inDirection:.NorthEast)

The direction is defined as TKCardinalDirection enum that provides the North, South, West, etc directions.

Note: The cardinal directions are used because left/right/up/down are ambiguous for several map orientations. For instance, what is “left” in an isometric map? The tile to the left is actually on a “diagonal” direction. What would be “up” in a hexagonal map where the hex tiles are in “pointy up” orientation? That’s not normally a direction one can travel, ie it would lead “over the corner” of the hex tile, not over one of its 6 sides.

Get, Set and Remove Tiles and Tile Flags

TKTileLayer provides several means to get, set and remove tiles and tile flags.

Tile GID w/o flags Tile GID incl. flags Tile Flags only
Get tileAt: tileWithFlagsAt: tileFlagsAt:
Set setTile:at: setTileWithFlags:at: setTileFlags:at:
Remove removeTileAt: removeTileAndFlagsAt: clearTileFlagsAt:

Removing a tile is exactly the same as setting the tile’s GID to 0 (aka TKTileGIDEmpty).

The important part to understand is that a tile is represented by a so-called GID (Global IDentifier), represented by the type TKTileGID which is a 32-Bit unsigned integer: uint32_t.

This GID can have TKTileFlags encoded in it, altering its value. So when you compare or set tile GIDs you have to account for the fact that a tile GID may contain flags. Otherwise a comparison may fail if a GID with a flag encoded in it is encountered, or by setting the tile GID you may inadvertently remove its existing flags.

Thus it is recommended that if you intend to change a coordinate’s GID you should first obtain the GID including flags, and if necessary mask out (“remove”) the flags as in: TKTileGid tileWithoutFlags = (tileGID & TKTileFlagMask).

Caution: If you compare tile GIDs, be sure to do so only on GIDs without flags unless you want both the tile’s GID and their flags to match!

For more information about GIDs please refer to Appendix B: About Tile GIDs.

Coord-Based Tile Animations

A TKTileLayer stores the coord-based instances of tile animations. These are animations that run at a specific coordinate with their own animation state, meaning they play independently of any other tile animation.

To play a tile animation at a given coordinate, you would first get a global TKTileAnimation from TKMap and then call:

TKTileAnimation* animation = [map tileAnimationNamed:@"trapdoor"];
TKTileAnimation* newAnim = [tileLayer playTileAnimation:animation atCoord:TKPointMake(12, 37)];
newAnim.repeatCount = 0;

// Swift    
let animation = map.tileAnimationNamed("trapdoor")
let newAnim = tileLayer.playTileAnimation(animation, atCoord:TKPoint(12, 37))
newAnim.repeatCount = 0

This will create a copy of the tile animation named “trapdoor” and play it once, with other animation parameters identical to the global animation’s parameters. If you need to tweak the parameters, for instance reversing the animation or changing its speed, you simply take the returned animation (the copy) and change its properties as needed.

If there’s already an existing animation playing at this coordinate, it will be replaced.

Coord-based animations remain in memory and associated with their coordinate until removed. This makes it easier to play infrequent, event-based tile animations at a given coordinate, such as doors.

To access an existing coord-based animation, you call:

TKTileAnimation* animation = [tileLayer tileAnimationAtCoord:TKPointMake(12, 37)];

// Swift
let animation = tileLayer.tileAnimationAtCoord(TKPoint(12, 37))

This will return the tile animation at the given coordinate, whether it is still playing or not. It will return nil if there’s no animation for this coordinate.

To finally remove an animation and optionally keeping its currently displayed tile animation frame, you call:

TKPoint coord = TKPointMake(12, 37);
[tileLayer removeTileAnimationAtCoord:coord keepFrame:NO];

// Swift
let coord = TKPoint(12, 37)
tileLayer.removeTileAnimationAtCoord(coord, keepFrame:false)

Not keeping the current tile animation frame will instantly restore the tile animation’s original tile at the coordinate, ie the tile the animation is associated with.

Working with Object Layers

Object layers are represented by TKObjectLayerNode and TKObjectLayer.

TKObjectLayerNode has no additional functionality as of yet. But it may have child nodes that represent the objects on the object layer. By default the TKObjectLayerNode creates shape nodes that draw the object shapes, except for Tile objects which are represented by sprite nodes.

The TKObjectLayer class contains a list of objects, instances of the class TKObject (discussed next).

You can get a TKObject instance by its name via objectNamed:. You can also add and remove objects via addObject: and removeObject: methods.

Creating Nodes for Objects

You may want to create custom nodes for objects on an object layer. In that case pass an object implementing the TKMapNodeDelegate protocol as the delegate parameter of the TKMapNode initializer.

The TKMapNodeDelegate should implement the following method in order to create custom nodes for an object:

-(TKNode*) nodeForObject:(TKObject*)object objectLayerNode:(TKObjectLayerNode*)objectLayerNode {
    // Create node here
    TKNode* myNode = ...

    // Return the node to add it to the objectLayerNode with zPosition based on Y coordinate
    return myNode;
}

// Swift
func nodeForObject(object: TKObject, objectLayerNode: TKObjectLayerNode) {
    // Create node here
    let myNode = ...

    // Return the node to add it to the objectLayerNode with zPosition based on Y coordinate
    return myNode
}

Based on the TKObject and the TKObjectLayerNode you can decide to create a node object, and which kind of node object. Then return the node. If the returned node’s parent property is nil, it will automatically be added as child of the objectLayerNode instance.

Return nil if you decide not to create a custom node for the given TKObject, or if you don’t want the node’s properties to be modified by the TKObject’s properties or the calculated zPosition (based on object’s Y coordinate).

Working with Image Layers

Image layers are represented by TKImageLayerNode and TKImageLayer classes.

TKImageLayerNode has a single property: sprite. This is the sprite that displays the image file. If needed, you could take this sprite, remove it from its parent, add it to a different node and then remove the tile layer node from the map.

The TKImageLayer class is similarly unspectacular. Currently it has a position and an imageFile string.


Previous: Working with Maps - Next: Working with ObjectsReturn to Index