3.1. Add nodes to the map Document

Previous: 3. How To … - Next: 3.2. Scroll LayersReturn to Index


You will likely want to create custom nodes based on either Tile objects or the tiles themselves. This article explains how to do so.

Note: The TKNode class in this article is an alias for whichever node class (a class inheriting from SKNode / CCNode) you wish to create.

Creating Nodes from Objects

When creating the map, supply a TKMapNodeDelegate instance to the initializer:

TKMapNode* mapNode = [TKMapNode mapNodeWithContentsOfFile:path
                                               renderSize:CGSizeMake(160, 90)
                                                 delegate:self];

// Swift
let mapNode = TKMapNode(contentsOfFile:path, renderSize:CGSize(160, 90), delegate:self)

Here, the scene self shall be the delegate, so it needs to implement the TKMapNodeDelegate protocol. First, add the protocol to the class interface:

@interface MapScene : TKScene <TKMapNodeDelegate> {
}

// Swift
class MapScene : TKMapNodeDelegate {
}

Then implement the object creation callback method in the delegate class' implementation file:

-(TKNode*) nodeForObject:(TKObject*)object objectLayerNode:(TKObjectLayerNode*)objectLayerNode {
    TKNode* customNode = [TKNode nodeWithAwesome];
    // Setup the node with TKObject data here ...
    return customNode;
}

// Swift
func nodeForObject(object: TKNode, objectLayerNode: TKObjectLayerNode) -> TKNode {
    let customNode = TKNode()
    // Setup the node with TKObject data here ...
    return customNode;
}

This method will be called once for every object on every object layer.

If a particular TKObject should not generate a node, simply return nil from this method. Otherwise, create a node of your choice based on the data provided by TKObject and return the node.

If you prefer to add the node as child yourself, especially when it shouldn’t be added to the TKObjectLayerNode instance, simply add the node to the desired parent:

-(TKNode*) nodeForObject:(TKObject*)object objectLayerNode:(TKObjectLayerNode*)objectLayerNode {
    TKNode* customNode = [TKNode nodeWithAwesome];
    // Setup the node with TKObject data here ...

    // add the node as child yourself
    [theParentNode addChild:customNode];

    return customNode;
}

// Swift
func nodeForObject(object: TKNode, objectLayerNode: TKObjectLayerNode) -> TKNode {
    let customNode = TKNode()
    // Setup the node with TKObject data here ...

    // add the node as child yourself
    theParentNode.addChild(customNode)

    return customNode
}

TilemapKit will only try to add the node as child of the corresponding TKObjectLayerNode if the node’s parent property is nil.

Returning the node will also apply the correct zPosition to the node, based on the object’s Y coordinate. If you want to set zPosition yourself, then simply return nil to prevent the node from further modifications by TilemapKit.

Note: If you prefer a blocks-based API in place of the delegate please let us know at support(at)tilemapkit.com.

Creating Nodes from Tiles

If you want to create nodes from tiles, you can generally do so in two ways. Both follow the same approach of enumerating tiles.

Note: A simpler, blocks-based tile enumeration API is proposed. Vote on the linked suggestion to make it happen (sooner).

Obtain a TKTileLayerNode instance

Assuming you have a TKMapNode already created and it contains a tile layer named “node tile layer”, you can get this layer node as follows:

TKTileLayerNode* tileLayerNode = (TKTileLayerNode*)[mapNode layerNodeNamed:@"node tile layer"];
TKTileLayer* tileLayer = tileLayerNode.tileLayer;

// Swift
let tileLayerNode = mapNode.layerNodeNamed("node tile layer") as! TKTileLayerNode
let tileLayer = tileLayerNode.tileLayer

You can also enumerate through all tile layers if you want to analyze all of them:

for (TKTileLayerNode* tileLayerNode in mapNode.tileLayerNodes) {
    TKTileLayer* tileLayer = tileLayerNode.tileLayer;
}

// Swift
for tileLayerNode in mapNode.tileLayerNodes {
    let tileLayer = tileLayerNode.tileLayer
}

Generating Nodes from Tiles on the Layer

Use the TKTileLayer instance to enumerate over individual tile GIDs:

for (NSUInteger x = 0; x < tileLayer.size.width; x++) {
    for (NSUInteger y = 0; y < tileLayer.size.height; y++) {
        TKTileGID tileWithoutFlags = [tileLayer tileAt:TKPointMake(x, y)];

        if (tileWithoutFlags == 123) {
            // Perform action for tile GID 123, for instance creating a node ...
        }
    }
}

// Swift
for x in 0..<tileLayer.size.width {
    for y in 0..<tileLayer.size.height {
        let tileWithoutFlags = tileLayer.tileAt(TKPoint(x, y))

        if (tileWithoutFlags == 123) {
            // Perform action for tile GID 123, for instance creating a node ...
        }
    }
}

Caution: Adding or removing tilesets from the map can change the resulting GIDs. If you expect tilesets to change in your map, you should avoid using tile GIDs directly, or at least #define them to constants so that you can more easily re-map them in case they do change.

Generating Nodes from Tile Properties

Using tile properties is the recommended way to perform an action (such as creating a node) based on a tile. The enumeration is the same as above, except that you don’t look at the particular tile’s GID but rather you check its TKProperties instance - if any.

for (NSUInteger x = 0; x < tileLayer.size.width; x++) {
    for (NSUInteger y = 0; y < tileLayer.size.height; y++) {
        TKTileGID tileWithoutFlags = [tileLayer tileAt:TKPointMake(x, y)];
        TKProperties* tileProperties = [mapNode.map propertiesForTile:tileWithoutFlags];

        // create a node if the tile's properties say so
        NSString* nodeClass = [tileProperties stringForKey:@"createNodeWithClass"];
        if (nodeClass) {
            TKNode* node = [NSClassFromString(nodeClass) node];

            // optionally assign other 'node.' prefixed values to node's properties
            [tileProperties assignToObject:node prefix:@"node."];
        }
    }
}

// Swift
for x in 0..<tileLayer.size.width {
    for y in 0..<tileLayer.size.height {
        let tileWithoutFlags = tileLayer.tileAt(TKPoint(x, y))
        let tileProperties = mapNode.map.propertiesForTile(tileWithoutFlags)

        // create a node if the tile's properties say so
        if let nodeClass = tileProperties.stringForKey("createNodeWithClass") {
            let node = TKNode()

            // optionally assign other 'node.' prefixed values to node's properties
            tileProperties.assignToObject(node, prefix:"node.")
        }
    }
}

This lets you perform custom actions based on the tile’s properties. Creating a node of any kind is easy, you can even specify the node’s class in Tiled if you prefer to have that flexibility! Well, that is if you’re an Objective-C user. Instantiating Swift classes from strings is more complex, if not impossible.

Moreover, you can easily edit all of the node’s properties right within Tiled, and then apply them. For instance, take these tile properties:

All those that have the prefix node. will be assigned to the node in this line:

[tileProperties assignToObject:node prefix:@"node."];

// Swift
tileProperties.assignToObject(node, prefix:"node.")

You only need to ensure that these properties exist on the node, and that you use the correct data type (implicit conversions are okay, for instance an integer is assignable to a CGFloat property).

The prefix is important because it enables you to specify properties for other objects as well. For instance if you create several nodes from a single tile you can set the properties of each individual node by using a different prefix for each.

Since this method uses the KVC techniques outlined here you can further improve on the behavior by implementing KVC methods on the target class that perform value conversions or input validation.


Previous: 3. How To … - Next: 3.2. Scroll LayersReturn to Index