SF Rogue Room Generation

The first major task in creating SF Rogue, was to create a level for the Rogue to actually move around. There were a few specific rules I wanted to adhere to

  1. The rooms had to be varying sizes
  2. Rooms should not overlay each other
  3. Where possible the rooms should connect directly, corridors should be kept to a minimum
  4. No room should be inaccessible
  5. Each level had to be random

Challenge 1 Accepted!

Obviously the final app was a full blown project, but to show the thought processes I went through and to get some examples up and running, I'll be using Playgrounds.

In Xcode create a new Playground, using the Blank template, we will be using SpriteKit for the rendering, but we don't need all of the boilerplate code that comes with the Game template.

Once created ensure that you can see the project navigator view



From the tasks its clear that a "Room" is going to be key to how the code works and is therefore a candidate for it own class. Separating out this functionality will also keep the code cleaner and easier to read the deeper we go into project.

We are now going to create a couple of Swift files, one to keep all the constants we are using and another to hold the Room class itself. Create the Constants file first and enter the following code...

enum Constants {
    struct Constraints {
        static let tileSize = 4                 // The rendered tile size
        static let minimumExtent = 3            // The minimum number of rows or columns
        static let maximumExtent = 9            // The maximum number of rows or columns
        static let minimumPositionOffset = 10   // Minimum random offset for positioning a room
        static let maximumPositionOffset = 40   // Maximum random offset for positioning a room
    }
}

We've set up these constants for 2 reasons...

  1. Magic numbers are bad
  2. If we want to tweak these settings later, they are all in one central location and each will have a specific purpose

Now create another Swift file called Room, and enter the following code...

import Foundation
    import SpriteKit
    
    public class Room: NSObject {
        var node: SKShapeNode   // Used to hold the "floor" of the room
        var number: Int         // Used to identify the room
        var cols: Int           // Number of columns the room is made up of
        var rows: Int           // Number of rows the room is made up of
    
        public init(number: Int) {
            self.number = number
    
            // Create a random number of columns and rows, based on restrictions.
            // Multiply the random number by 2 always ensures that we get an "even" sized room
    
            self.cols = Int.random(in: Constants.Constraints.minimumExtent..<Constants.Constraints.maximumExtent) * 2
            let width = Int(cols * Constants.Constraints.tileSize)
            self.rows = Int.random(in: Constants.Constraints.minimumExtent..<Constants.Constraints.maximumExtent) * 2
            let height = Int(rows * Constants.Constraints.tileSize)
    
            // Create a basic node using the size, give it a border  and fill it in so we can see it
            self.node = SKShapeNode(rectOf: CGSize(width: width, height: height))
            self.node.lineWidth = 2
            self.node.fillColor = .red
            self.node.name = "room\(self.number)"
    
            // A random position for the room
            let x = Int(Int.random(in: Constants.Constraints.minimumPositionOffset..<Constants.Constraints.maximumPositionOffset) * Constants.Constraints.tileSize)
            let y = Int(Int.random(in: Constants.Constraints.minimumPositionOffset..<Constants.Constraints.maximumPositionOffset) * Constants.Constraints.tileSize)
    
            self.node.position = CGPoint(x: x, y: y)
        }
    }

This code gives us a basic room, of a random size and in a random location, challenge 1 is a step closer to being complete. The one slight problem with these rooms, is that we can't actually see them!

We now need to create a SpriteKit scene, a number of rooms and render them to the scene. Go back to the main Playground file and enter the following code...

import PlaygroundSupport
    import SpriteKit
    
    class GameScene: SKScene {
    
        // Array that will be used to hold all of the created rooms, for future use
        private var rooms = [Room]()
    
        private var totalRooms = 20
    
        override func didMove(to view: SKView) {
            // Now we have a view to render too, create how many rooms we need for the map
            for number in 0..<totalRooms {
                let room = Room(number: number)
                self.rooms.append(room)
                room.render(scene: self)
            }
        }
    }
    
    // Sets up the scene for us to render to
    func createScene() -> SKView {
        let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 768, height: 1024))
    
        let scene = GameScene(size: sceneView.frame.size)
        // Set the scale mode to scale to fit the window
        scene.scaleMode = .aspectFill
        scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    
        // Present the scene
        sceneView.presentScene(scene)
        return sceneView
    }
    
    PlaygroundSupport.PlaygroundPage.current.liveView = createScene()

This code won't actually run at the moment as the Room doesn't implement the render method. Go back to the Room class and add the following function...

public func render(scene: SKScene) {
    scene.addChild(self.node)
}

Now the Playground should compile and be runnable. Press the play button, wait for the 20 iterations to loop through and you should see something like the following appearing in your Live View...

If you can't see the Live View, ensure you are in the Playground file and then click this icon that appears top right and select Live View

Challenge 1 Complete!

Congratulations, we now have a number of random sized rooms being rendered in SpriteKit. Admittedly they aren't very useful rooms and very cramped, but these problems will be corrected in future posts.

The Playground for this article is available on GitHub

If you have any questions or comments you can reach me on Twitter.