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
