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
- The rooms had to be varying sizes
- Rooms should not overlay each other
- Where possible the rooms should connect directly, corridors should be kept to a minimum
- No room should be inaccessible
- 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...
- Magic numbers are bad
- 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.