//~~~~~~~~~~~~~~~~~~~~ // Dungeon Mapper //~~~~~~~~~~~~~~~~~~~~ // Random Plus! function randPlus(low, high) { return Math.floor((Math.random() * (high - low)) + low); } // Main Dungeon object var Dungeon = { map: null, map_size: null, rooms: [], stats: null, tree: [], stack: [], gid: 1, minRoomSize: 5, minSizeFactor: 0.3, clear: function () { this.map_size = null; this.map = []; this.rooms = []; this.corridors = []; this.stats = {}; this.drooms = []; this.tree = {}; this.stack = []; this.gid = 1; }, generate: function (size) { this.clear(); this.map_size = size; // init the map array to null for (var x = 0; x < this.map_size; x++) { this.map[x] = []; for (var y = 0; y < this.map_size; y++) { this.map[x][y] = 0; } } // First generate a BSP tree of the dungeon (Kinda looks like fractals) // Algo - http://doryen.eptalys.net/articles/bsp-dungeon-generation/ var X = 1; var Y = 1; var W = this.map_size - 2; var H = this.map_size - 2; // Root Node var rootBox = {}; rootBox.x = X; rootBox.y = Y; rootBox.w = W; rootBox.h = H; this.tree[this.gid] = rootBox; this.gid++; // Build Tree this.buildTree(1); // Next, build rooms in the leaf nodes of the tree for (var nodeID in this.tree) { var node = this.tree[nodeID]; if (node.hasOwnProperty("L")) { continue; } var room = {}; room.w = randPlus(this.minRoomSize, node.w); room.h = randPlus(this.minRoomSize, node.h); room.x = node.x + Math.floor((node.w - room.w) / 2); room.y = node.y + Math.floor((node.h - room.h) / 2); room.center = {}; room.center.x = Math.floor(room.x + room.w / 2); room.center.y = Math.floor(room.y + room.h / 2); room.id = nodeID; this.rooms.push(room); this.tree[nodeID].hasRoom = nodeID; } // Assign the rooms to the Map for (var i = 0; i < this.rooms.length; i++) { var r = this.rooms[i]; console.log("Room: ", [r.x, r.y, r.w, r.h]); for (var x = r.x; x < (r.x + r.w); x++) { for (var y = r.y; y < (r.y + r.h); y++) { this.map[x][y] = 1; } } } // Build Corridors this.joinRooms(); // Collect the stats this.stats.algo = 'bsp'; this.stats.rooms = this.rooms.length; this.stats.minRoomSize = this.minRoomSize; this.stats.splitFactor = this.minSizeFactor; }, // Recursively build the tree buildTree: function (root) { var X = this.tree[root].x; var Y = this.tree[root].y; var W = this.tree[root].w; var H = this.tree[root].h; this.tree[root].center = {}; this.tree[root].center.x = Math.floor(X + W / 2); this.tree[root].center.y = Math.floor(Y + H / 2); var ok = 0; // Select a split - Horizontal((0) or Vertical(1) // This allows you to have a valid splitType oppurtunity var splitType = 1; if (this.minSizeFactor * W < this.minRoomSize) { // no space for splitting vertically, try Horizontal splitType = 0; } else if (this.minSizeFactor * H < this.minRoomSize) { // no space for splitting vertically, try Vertical splitType = 1; } else { // random - Both H and V are valid splits if (Math.random() > 0.5) { splitType = 0; } } // generate 2 boxes (child nodes) // Ensure minimum size - else quit producing new boxes if (splitType) { roomSize = this.minSizeFactor * W; if (roomSize >= this.minRoomSize) { var w1 = randPlus(roomSize, (W - roomSize)); var w2 = W - w1; var box1 = {}; box1.x = X; box1.y = Y; box1.w = w1; box1.h = H; box1.alignment = 'V'; var box2 = {}; box2.x = X + w1; box2.y = Y; box2.w = w2; box2.h = H; box2.alignment = 'V'; ok++; } } else { roomSize = this.minSizeFactor * H; if (roomSize >= this.minRoomSize) { var h1 = randPlus(roomSize, (H - roomSize)); var h2 = H - h1; var box1 = {}; box1.x = X; box1.y = Y; box1.w = W; box1.h = h1; box1.alignment = 'H'; var box2 = {}; box2.x = X; box2.y = Y + h1; box2.w = W; box2.h = h2; box2.alignment = 'H'; ok++; } } if (ok) { this.tree[this.gid] = box1; this.tree[root].L = this.gid; this.gid++; this.tree[this.gid] = box2; this.tree[root].R = this.gid; this.gid++; this.stack.push([this.tree[root].L, this.tree[root].R]); this.buildTree(this.tree[root].L); this.buildTree(this.tree[root].R) } }, // Join rooms using corridors joinRooms: function () { var join; while (join = this.stack.pop()) { var a = join[0]; var b = join[1]; // console.log("join: "+[a,b]+" split: "+this.tree[a].alignment); // console.log(this.tree[a].center); // console.log(this.tree[b].center); var x = Math.min(this.tree[a].center.x, this.tree[b].center.x); var y = Math.min(this.tree[a].center.y, this.tree[b].center.y); var size = randPlus(1, this.minRoomSize); var w = size; var h = size; // Vertical corridor if (this.tree[a].alignment == 'H') { x -= Math.floor(size / 2) + 1; h = Math.abs(this.tree[a].center.y - this.tree[b].center.y); } else { // Horizontal corridor y -= Math.floor(size / 2) + 1; w = Math.abs(this.tree[a].center.x - this.tree[b].center.x); } // Ensure Legal bounds x = x < 0 ? 0 : x; y = y < 0 ? 0 : y; console.log("Corridor: " + [x, y, w, h]); for (var i = x; i < x + w; i++) { for (var j = y; j < y + h; j++) { if (this.map[i][j] == 0) this.map[i][j] = 2; } } } }, print: function () { console.log("DUNGEON"); for (var x = 0; x < this.map_size; x++) { var row = x; if (x < 10) { row += ' '; } else { row += ' '; } for (var y = 0; y < this.map_size; y++) { row += this.map[x][y] + ' '; } console.debug(row); } }, getMap: function () { return this.map; }, getRooms: function () { return this.rooms; }, getTree: function () { return this.tree; }, getStats: function () { return this.stats; } }; //~~~~~~~~~~~~~~~~~~~~ // Phaser Game Engine //~~~~~~~~~~~~~~~~~~~~ var MAP_SIZE = 32; Dungeon.generate(MAP_SIZE); // Dungeon.print(); var map = Dungeon.getMap(); var rooms = Dungeon.getRooms(); var stats; var gmap; var layer0; var marker; var state_toggleBox = 0; var game = new Phaser.Game(640, 384, Phaser.AUTO, 'phaserCanvas', { preload: preload, create: create, update: update, render: render }); var tileLibrary = []; function preload() { console.clear(); game.load.crossOrigin = true; game.load.image('wall', 'http://i.imgur.com/BKyejcY.png'); game.load.image('floor', 'http://i.imgur.com/ZJiMggh.png'); game.load.image('corridor', 'http://i.imgur.com/ZXhfWKR.png'); game.load.spritesheet('button', 'http://i.imgur.com/Ay6IfKz.png', 80, 20); tileLibrary[0] = 'wall'; tileLibrary[1] = 'floor'; } function create() { var startX = 200; var startY = 32; // Creates a blank tilemap gmap = game.add.tilemap(); // Add a Tileset image to the map gmap.addTilesetImage('wall', 'wall', 12, 12, null, null, 0); gmap.addTilesetImage('floor', 'floor', 12, 12, null, null, 1); gmap.addTilesetImage('corridor', 'corridor', 12, 12, null, null, 2); // Creates a new blank layer and sets the map dimensions. layer0 = gmap.create('layer0', MAP_SIZE, MAP_SIZE, 12, 12); layer0.resizeWorld(); stats = Dungeon.getStats(); for (var i = 0; i < MAP_SIZE; i++) { for (var j = 0; j < MAP_SIZE; j++) { gmap.putTile(map[i][j], i, j, layer0); } } game.add.button(400, 256, 'button', genMap, this, 0, 1, 2); game.add.button(400, 300, 'button', toggleBox, this, 0, 1, 2); marker = game.add.graphics(); } function update() { } function render() { game.debug.text("gen", 400 + 16, 256 + 12, '#F00'); game.debug.text("box: " + state_toggleBox, 400 + 5, 300 + 14, '#000'); var i = 0; for (var key in stats) { game.debug.text(key + ": " + stats[key], 400, 48 + (i * 16)); i++; } for (var i = 0; i < rooms.length; i++) { var r = rooms[i]; game.debug.text(r.w + "x" + r.h, r.center.x * 12, r.center.y * 12); } } function genMap() { console.clear(); Dungeon.generate(MAP_SIZE); // Dungeon.print(); map = Dungeon.getMap(); stats = Dungeon.getStats(); rooms = Dungeon.getRooms(); for (var i = 0; i < MAP_SIZE; i++) { for (var j = 0; j < MAP_SIZE; j++) { gmap.putTile(map[i][j], i, j, layer0); } } drawBox(); } function toggleBox() { state_toggleBox = state_toggleBox ? 0 : 1; drawBox(); } //~~~ function drawBox() { if (state_toggleBox) { marker.clear(); marker.lineStyle(4, 0x000000, 1); tree = Dungeon.getTree(); for (var node in tree) { var t = tree[node]; marker.drawRect(t.x * 12, t.y * 12, t.w * 12, t.h * 12); } } else { marker.clear(); } }
<div id="phaserCanvas"></div>