iosswiftsprite-kitsktilemapnode

Having trouble writing an Algorithm to fill in a tile-map given the 4 corner positions


I'm building an algorithm to procedurally generate a dungeon using Swift 4 and SpriteKit. I have done the part to generate the rooms but now I need some help with carving out the rooms.

let mapWidth = 225
let mapHeight = 150

var tileMap = SKTileMapNode()

var rooms = [Room]()

class Room {
    // these values hold grid coordinates for each corner of the room
    var x1:Int
    var x2:Int
    var y1:Int
    var y2:Int

    var width:Int
    var height:Int

    // center point of the room
    var center:CGPoint

    init (x:Int, y:Int, w:Int, h:Int) {

        x1 = x
        x2 = x + w
        y1 = y
        y2 = y + h
        width = w
        height = h
        center = CGPoint(x: ((x1 + x2) / 2),
                         y: ((y1 + y2) / 2))
    }
    func intersects(room:Room) -> Bool {
        if x1 <= room.x2 && x2 >= room.x1 && y1 <= room.y2 && y2 >= room.y1 {
            return true
        } else {
            return false
        }
    }
}

This is the snippet that I am using to determine the size of the room, the coordinates of its four corners, and the position of the room in the tileMap grid.

I adapted this code from this article.

Continuing from the article, I gather the information from the room generator and place it into an array that I can access so I can carve out the rooms in the tileMap.

func placeRooms() {

    let numberOfRooms = Int(arc4random_uniform(20) + 10)

    for i in 0..<numberOfRooms {
        let w = Int(arc4random_uniform(15) + 5);
        let h =  Int(arc4random_uniform(15) + 5);
        let x = Int(arc4random_uniform(UInt32(mapWidth)));
        let y = Int(arc4random_uniform(UInt32(mapHeight)));

        // create room with randomized values
        let newRoom = Room(x:x, y:y, w:w, h:h);

        var failed = false
        for otherRoom in rooms {
            if newRoom.intersects(room: otherRoom) {
                failed = true
            }
        }
        if failed == false {
            rooms.append(newRoom)
        }
    }
    createRooms()
}

But now I am on to using all the information to carve out the rooms in the tileMap. I have the four corner positions, I just need to know how to fill out everything in-between them. This is what I have gotten so far:

func createRooms() {
    let tile1 = SKTexture(imageNamed: "black")
    let tile2 = SKTexture(imageNamed: "gray")

    let black = SKTileGroup(tileDefinition: SKTileDefinition(texture: tile1, size: CGSize(width: 32, height: 32)))
    let gray = SKTileGroup(tileDefinition: SKTileDefinition(texture: tile2, size: CGSize(width: 32, height: 32)))

    let tileSet = SKTileSet(tileGroups: [gray,black])

    tileMap = SKTileMapNode(tileSet: tileSet, columns: mapWidth, rows: mapHeight, tileSize: CGSize(width: 32, height: 32))

    for c in 0..<tileMap.numberOfColumns {
        for r in 0..<tileMap.numberOfRows  {
            for i in 0..<rooms.count {
                if rooms[i].x1 <= c && rooms[i].x2 >= c && rooms[i].y1 <= r && rooms[i].y2 >= r {
                    tileMap.setTileGroup(gray, forColumn: c, row: r)
                    print("Room Tile")
                }
                if tileMap.tileGroup(atColumn: c, row: r) != gray {
                    tileMap.setTileGroup(black, forColumn: c, row: r)
                }
            }
        }
    }
    self.addChild(tileMap)
    tileMap.setScale(0.05)
}

Edit: ^^This is the only part you need to focus on in case you are feeling overwhelmed. I just need to know how to take 4 corner coordinates and fill in everything in-between.


Solution

  • I figured it out. I used the same code that checks for intersecting rooms to determine the position of every room based off of the four corners and then fill in everything in-between.

    func createRooms() {
    let tile1 = SKTexture(imageNamed: "black")
    let tile2 = SKTexture(imageNamed: "gray")
    
    let black = SKTileGroup(tileDefinition: SKTileDefinition(texture: tile1, size: CGSize(width: 32, height: 32)))
    let gray = SKTileGroup(tileDefinition: SKTileDefinition(texture: tile2, size: CGSize(width: 32, height: 32)))
    
    let tileSet = SKTileSet(tileGroups: [gray,black])
    
    tileMap = SKTileMapNode(tileSet: tileSet, columns: mapWidth, rows: mapHeight, tileSize: CGSize(width: 32, height: 32))
    
    for c in 0..<tileMap.numberOfColumns {
        for r in 0..<tileMap.numberOfRows  {
            for i in 0..<rooms.count {
                if rooms[i].x1 <= c && rooms[i].x2 >= c && rooms[i].y1 <= r && rooms[i].y2 >= r {
                    tileMap.setTileGroup(gray, forColumn: c, row: r)
                }
                if tileMap.tileGroup(atColumn: c, row: r) != gray {
                    tileMap.setTileGroup(black, forColumn: c, row: r)
                }
            }
        }
    }
    

    I also updated the code in the question because the old code was really inefficient and some of it was unnecessary. Here are some images of what this algorithm can generate. The number of rooms varies from 10-30 and can be 5-25 tiles in width/height. The size of the tile map can be changed to spread out the rooms more. Currently, the tile map size is 225x150 with 32x32 pixel tiles.

    Image 1

    Image 2

    Image 3

    My next goal is to figure out how to connect each of them with corridors.