Search code examples
iosxcodesprite-kitsktilemapnode

Custom group in TileMap set does not select the proper tile


I made a custom group with every possible combination of surrounding tiles.

Custom group screenshot

I use it with the automapping mode enabled on my TileMap, and here is the result:

Render in the editor

As you can see, there are several errors:

Annotated render (green = proper tile, red = wrong tile)

I tried it both programmatically and in the editor. The behavior is the same.

My thought is that the automapping engine takes the first matching element, when dealing with corners (above left, below right). Here are some of the errors, linked to the element the engine should have take:

Annotated tiles

All the wrong one are before the matching tile. But I could not find any way to reorder tiles to test this theory (in the editor or even in a configuration file as the group is in a binary file).

Is there any way to resolve this problem?

Thanks in advance for your time.

EDIT

I tried to delete and recreate some of the wrong tiles, and it confirms the theory: when dealing with angles, the automapping engine takes the first matching element, even if corners are deactivated in them. It does not look after to check if there is a better tile matching also the corner rule.

The question stays the same: is there a way to correct this behavior, or do I have to code the automapping engine?


Solution

  • I have been working on this for 2 weeks now. I was having some serious problems with SpriteKit SKTileMapNode auto-tiling. I followed this article and figured out what I needed to do to get the correct auto-tiling. I looked all over the internet digging through old forums and websites trying to piece together exactly what the article was explaining.

    Finally, after weeks of testing and countless trial/error I came up with a functioning piece of code that simulates what the auto-tiling is supposed to do in SpriteKit.

    // Auto-tiling tool to bypass the auto-mapping in SpriteKit.
    // This tool opens up a lot more options than the one provided by SpriteKit
    class TileData {
    
        var map:SKTileMapNode
        var column:Int
        var row:Int
        //var array2D = [[Int]]()
    
        init(Column: Int, Row: Int, Map: SKTileMapNode) {
            column = Column
            row = Row
            map = Map
        }
    
        func returnTileData(C: Int, R: Int) -> Int {
            var directions = 0
            if map.tileGroup(atColumn: C, row: R) == tileGroups[48] {
                if map.tileGroup(atColumn: C - 1, row: R + 1) == tileGroups[48] {
                    directions += 1
                }
                if map.tileGroup(atColumn: C, row: R + 1) == tileGroups[48] {
                    directions += 2
                }
                if map.tileGroup(atColumn: C + 1, row: R + 1) == tileGroups[48] {
                    directions += 4
                }
                if map.tileGroup(atColumn: C - 1, row: R) == tileGroups[48] {
                    directions += 8
                }
                if map.tileGroup(atColumn: C + 1, row: R) == tileGroups[48] {
                    directions += 16
                }
                if map.tileGroup(atColumn: C - 1, row: R - 1) == tileGroups[48] {
                    directions += 32
                }
                if map.tileGroup(atColumn: C, row: R - 1) == tileGroups[48] {
                    directions += 64
                }
                if map.tileGroup(atColumn: C + 1, row: R - 1) == tileGroups[48] {
                    directions += 128
                }
            }
            let east = (directions & Dir.East.rawValue) == Dir.East.rawValue
            let west = (directions & Dir.West.rawValue) == Dir.West.rawValue
            let south = (directions & Dir.South.rawValue) == Dir.South.rawValue
            let north = (directions & Dir.North.rawValue) == Dir.North.rawValue
            let northEast = (directions & Dir.NorthEast.rawValue) == Dir.NorthEast.rawValue
            let northWest = (directions & Dir.NorthWest.rawValue) == Dir.NorthWest.rawValue
            let southEast = (directions & Dir.SouthEast.rawValue) == Dir.SouthEast.rawValue
            let southWest = (directions & Dir.SouthWest.rawValue) == Dir.SouthWest.rawValue
    
            return getTileData(east: east, west: west, north: north, south: south,
                           northWest: northWest, northEast: northEast, southWest:southWest, southEast: southEast)
        }
    
        func getTileData(east: Bool, west: Bool, north: Bool, south: Bool, northWest: Bool, northEast: Bool, southWest: Bool, southEast: Bool) -> Int {
            var directions = (east ? Dir.East.rawValue : 0) | (west ? Dir.West.rawValue : 0)  | (north ? Dir.North.rawValue : 0) | (south ? Dir.South.rawValue : 0)
    
            directions |= ((north && west) && northWest) ? Dir.NorthWest.rawValue : 0
            directions |= ((north && east) && northEast) ? Dir.NorthEast.rawValue : 0
            directions |= ((south && west) && southWest) ? Dir.SouthWest.rawValue : 0
            directions |= ((south && east) && southEast) ? Dir.SouthEast.rawValue : 0
    
            return directions
        }
    }
    

    I'll try to explain exactly what it does and how to get it working.

    The algorithm starts by filling the first map with data. It takes the custom class, passes in a column, row, and the first pre-filled map. It checks the neighbors of each tile in the first map, returns a bit-mask based off of it, and sets a tile into the second map based off of the returned bit-mask.

    Here is the whole process. Granted, it could probably be improved. But that will take time.

    // There are 47 possible tile orientations.
    let tileBits = [2, 8, 10, 11, 16, 18, 22, 24, 26, 27,
                    30, 31, 64, 66, 72, 74, 75, 80, 82, 86,
                    88, 90, 91, 94, 95, 104, 106, 107, 120,
                    122, 123, 126, 127, 208, 210, 214, 216, 218,
                    219, 222, 223, 248, 250, 251, 254, 255, 0]
    
    // Because of how buggy SKTileMapNodes currently are, two tile maps are needed for this process
    
    let tileMap = SKTileMapNode(tileSet: tileSet, columns: columns, rows: rows, tileSize: tileSize)
    let tileMap2 = SKTileMapNode(tileSet: tileSet, columns: columns, rows: rows, tileSize: tileSize)
    
    for c in 0..<tileMap.numberOfColumns {
        for r in 0..<tileMap.numberOfRows {
            // Fill your first tile map in here. 
            // Pretty standard stuff.
        }
    }
    
    for c in 0..<tileMap2.numberOfColumns {
        for r in 0..<tileMap2.numberOfRows {
            // Assign variable to the class and pass in the pre-filled tileMap
            let tile = TileData(Column: c, Row: r, Map: tileMap)
    
            // Get the bit-mask of the tile at (column, row)
            let number = tile.returnTileData(C: c, R: r)
    
            // If the array of tileBits contains the bitmask
            if tileBits.contains(number) {
                // Find out where it is at in the array
    
                guard let bit = tileBits.firstIndex(of: number) else { return }
    
                // Set the Tile Group
                tileMap2.setTileGroup(tileGroups[bit], forColumn: c, row: r)
            }
        }
    }
    // tileMap.setScale(0.2)
    self.addChild(tileMap2)
    

    There are 47 possible tile arrangements. Basically, you have to create an array of 48 tile groups. 1-47 are the possible tile orientations. 48 is there to represent filled space in the first map. Another array stores all 47 of the bit-masks for the possible tile orientations. It takes the returned bit-mask and compares it to that array to find the index at which it is located. Then it accesses the array of tile groups and sets a tile into the second tile map based off of the index of the bit-mask array.

    Hopefully that made sense.

    Here is the tile Sprite-sheet with each tile organized from left to right, and least to greatest.

    2, 8, 10, 11, 16, 18, 22, 24, 26, 27, 30, 31, 64, 66, 72, 74, 75, 80, 82, 86, 88, 90, 91, 94, 95, 104, 106, 107, 120, 122, 123, 126, 127, 208, 210, 214, 216, 218, 219, 222, 223, 248, 250, 251, 254, 255, 0

    Image 1