- Joined
- Oct 14, 2010
- Messages
- 100
So I've attempted creating a random dungeon generator that should be somewhat advanced. It's in early stages so it doesn't look good.
But I've encountered 2 problems that I can't seem to fix:
Problem 1: Destructible positioning
In the code I use a 2D virtual grid where each node can either be a room or solid wall. For example:
In-game, every virtual node is 512 units apart from the next node. So the example grid would result in a 3584 x 1536 units large dungeon, in-game.
However, when creating destructibles (for walls, gates, etc.) it sometimes glitches and isn't precisely 512 units apart.
The purple "ceiling" in this screenshot should be more to the right so there's no hole. It works for most of the destructibles, but some are messed up.
Problem 2: Random gates
Again, a problem that leaves me clueless.
As you can see on the screenshot, doors should only lead to other rooms, but for some reason it randomly creates doors leading to a solid wall.
Demo map attached to this post
You can test it in-game by pressing "escape".
But I've encountered 2 problems that I can't seem to fix:
Problem 1: Destructible positioning
In the code I use a 2D virtual grid where each node can either be a room or solid wall. For example:
Code:
...##.. # = room
..###.. . = solid wall
....##.
In-game, every virtual node is 512 units apart from the next node. So the example grid would result in a 3584 x 1536 units large dungeon, in-game.
However, when creating destructibles (for walls, gates, etc.) it sometimes glitches and isn't precisely 512 units apart.
The purple "ceiling" in this screenshot should be more to the right so there's no hole. It works for most of the destructibles, but some are messed up.
Problem 2: Random gates
Again, a problem that leaves me clueless.
As you can see on the screenshot, doors should only lead to other rooms, but for some reason it randomly creates doors leading to a solid wall.
Demo map attached to this post
JASS:
library DG requires Assertion, Array, StringUtils, Stack
// ***************************************
// *
// * Configuration Settings
// *
// ***************************************
globals
// Width & height of the map (specified in nodes)
public constant integer MAX_WIDTH = 32
public constant integer MAX_HEIGHT = 32
public constant real NODE_SIZE = 512.0
// Pattern symbols
public constant string PATTERN_SYMBOL_ROOM = "#"
public constant string PATTERN_SYMBOL_NONE = "."
public constant string PATTERN_SYMBOL_LAYER = "|"
// Options
public constant real FEATURES_PER_NODE = 0.07
public constant boolean SOFT_LOAD = true
public constant real EPSILON = -0.15
endglobals
// ***************************************
// *
// * Layout
// *
// ***************************************
private struct LayoutHelper extends array
readonly static integer array data[MAX_WIDTH][MAX_HEIGHT]
public method operator [] takes integer y returns integer
debug call assert(integer(this) >= 0 and integer(this) < DG_Layout.width and y >= 0 and y < DG_Layout.height, "DG::LayoutHelper::[] - coordinates not within bounds")
return data[this][y]
endmethod
public method operator []= takes integer y, integer value returns nothing
debug call assert(integer(this) >= 0 and integer(this) < DG_Layout.width and y >= 0 and y < DG_Layout.height, "DG::LayoutHelper::[]= - coordinates not within bounds")
if (value == 0) then
call DG_Layout.roomNodes.remove(DG_Position.create(this, y))
elseif (data[this][y] == 0) then
call DG_Layout.roomNodes.add(DG_Position.create(this, y))
endif
set data[this][y] = value
endmethod
endstruct
public struct Layout extends array
private static integer layoutWidth
private static integer layoutHeight
readonly static Stack roomNodes = 0
public static method operator [] takes integer x returns LayoutHelper
return x
endmethod
public static method operator width takes nothing returns integer
return layoutWidth
endmethod
public static method operator height takes nothing returns integer
return layoutHeight
endmethod
public static method operator width= takes integer value returns nothing
call assert(value > 0 and value <= MAX_WIDTH, "DG::Layout::width= - invalid width")
set layoutWidth = value
endmethod
public static method operator height= takes integer value returns nothing
call assert(value > 0 and value <= MAX_HEIGHT, "DG::Layout::height= - invalid height")
set layoutHeight = value
endmethod
public static method reset takes integer value returns nothing
local integer x = 0
local integer y
// Reset LayoutGrid
loop
exitwhen x >= layoutWidth
set y = 0
loop
exitwhen y >= layoutHeight
set Layout[x][y] = value
set y = y + 1
endloop
set x = x + 1
endloop
// Reset list of room nodes
if (roomNodes != 0) then
call roomNodes.destroy()
endif
set roomNodes = Stack.create()
endmethod
public static method getRandomWall takes nothing returns DG_Wall
local DG_Position position = 0
local DG_Position adjacent = 0
local DG_Direction facing = 0
loop
set position = DG_Position(roomNodes.random())
set facing = DG_Direction.random()
set adjacent = position.adjacent(facing)
if (Layout[adjacent.x][adjacent.y] == 0 and position != adjacent) then
return DG_Wall.create(position, facing)
endif
endloop
return 0
endmethod
public static method toString takes nothing returns string
local string str = "|cff0000ff"
local integer x = 0
local integer y = 0
local boolean lastRoom = false
loop
exitwhen x >= layoutWidth
set y = 0
loop
exitwhen y >= layoutHeight
if (Layout[x][y] > 0) then
if (lastRoom) then
set str = str + "#"
else
set str = str + "|r|cffffffff#"
set lastRoom = true
endif
elseif (lastRoom) then
set str = str + "|r|cff0000ff#"
set lastRoom = false
else
set str = str + "#"
endif
set y = y + 1
endloop
set str = str + "|r\n|cff0000ff"
set lastRoom = false
set x = x + 1
endloop
set str = str + "|r"
return str
endmethod
endstruct
// ***************************************
// *
// * Direction (NORTH, EAST, SOUTH, WEST)
// *
// ***************************************
public struct Direction extends array
public static Direction NORTH = 0
public static Direction EAST = 1
public static Direction SOUTH = 2
public static Direction WEST = 3
private static method create takes nothing returns DG_Direction
call assert(false, "DG::Direction::create - should not be called!")
return 0
endmethod
public method rotate takes integer num90 returns DG_Direction
local integer direction = this + num90/90
return direction - (direction / 4) * 4
endmethod
public method angle takes nothing returns real
return -90.0 * this
endmethod
public static method random takes nothing returns DG_Direction
return GetRandomInt(0, 3)
endmethod
public method toString takes nothing returns string
if (this == NORTH) then
return "north"
elseif (this == EAST) then
return "east"
elseif (this == SOUTH) then
return "south"
else
return "west"
endif
endmethod
endstruct
// ***************************************
// *
// * Position
// *
// ***************************************
public struct Position extends array
public static method checkBoundariesX takes integer x returns integer
if (x < 0) then
return 0
elseif (x >= Layout.width) then
return Layout.width - 1
endif
return x
endmethod
public static method checkBoundariesY takes integer y returns integer
if (y < 0) then
return 0
elseif (y >= Layout.height) then
return Layout.height - 1
endif
return y
endmethod
public static method create takes integer x, integer y returns Position
return checkBoundariesX(x) * 0xFFFF + checkBoundariesY(y)
endmethod
public method operator x takes nothing returns integer
return this / 0xFFFF
endmethod
public method operator y takes nothing returns integer
return this - (this.x * 0xFFFF)
endmethod
public method operator x= takes integer value returns Position
return checkBoundariesX(value) * 0xFFFF + this.y
endmethod
public method operator y= takes integer value returns Position
return this.x * 0xFFFF + checkBoundariesY(value)
endmethod
public method operator data takes nothing returns integer
return Layout[this.x][this.y]
endmethod
public method operator data= takes integer value returns nothing
set Layout[this.x][this.y] = value
endmethod
public method toString takes nothing returns string
return "(" + I2S(this.x) + "," + I2S(this.y) + ")"
endmethod
public method add takes Position other returns Position
return Position.create(this.x + other.x, this.y + other.y)
endmethod
public method sub takes Position other returns Position
return Position.create(this.x - other.x, this.y - other.y)
endmethod
public method operator north takes nothing returns Position
return this.x * 0xFFFF + checkBoundariesY(this.y + 1)
endmethod
public method operator east takes nothing returns Position
return checkBoundariesX(this.x + 1) * 0xFFFF + this.y
endmethod
public method operator south takes nothing returns Position
return this.x * 0xFFFF + checkBoundariesY(this.y - 1)
endmethod
public method operator west takes nothing returns Position
return checkBoundariesX(this.x - 1) * 0xFFFF + this.y
endmethod
public method adjacent takes Direction direction returns Position
if (direction == Direction.NORTH) then
return this.north
elseif (direction == Direction.EAST) then
return this.east
elseif (direction == Direction.SOUTH) then
return this.south
elseif (direction == Direction.WEST) then
return this.west
else
return 0
endif
endmethod
public static method random takes integer width, integer height returns Position
return GetRandomInt(0, width-1) * 0xFFFF + GetRandomInt(0, height-1)
endmethod
endstruct
// ***************************************
// *
// * Wall
// *
// ***************************************
public struct Wall
readonly Position position
readonly Direction direction
// A wall is a position and a direction
// i.e. at node[x][y], the wall facing EAST
// opposing wall is node[x+1][y] facing WEST
public static method create takes Position position, Direction direction returns Wall
local Wall this = Wall.allocate()
set this.position = position
set this.direction = direction
return this
endmethod
// Returns the other side of the wall (returns 0 if the wall is a border wall)
public method opposite takes nothing returns Wall
local Position p = this.position.adjacent(this.direction)
// Check if wall has opposite wall
if (p == this.position) then
return 0
endif
return Wall.create(p, this.direction.rotate(180))
endmethod
public method opposes takes Wall other returns boolean
return this.position.adjacent(this.direction) == other.position and this.direction == other.direction.rotate(180)
endmethod
endstruct
// ***************************************
// *
// * Pattern
// *
// ***************************************
// TO DO: pattern generator: simply create all 5x5 strings randomly consisting of . and #
public struct Pattern
private string data
readonly integer width
readonly integer height
readonly integer size
public static method create takes string data returns Pattern
local Pattern this = Pattern.allocate()
set this.width = StringFind(data, PATTERN_SYMBOL_LAYER, 0)
set this.height = StringCount(data, PATTERN_SYMBOL_LAYER) + 1
set this.size = StringCount(data, PATTERN_SYMBOL_ROOM)
set this.data = StringReplace(data, PATTERN_SYMBOL_LAYER, "")
return this
endmethod
public method toString takes nothing returns string
local string str = ""
local integer x = 0
local integer y = 0
loop
exitwhen x >= this.width
set y = 0
loop
exitwhen y >= this.height
if (this.at(Position.create(x, y)) == PATTERN_SYMBOL_ROOM) then
set str = str + "|cffffffff#|r"
else
set str = str + "|cff0000ff#|r"
endif
set y = y + 1
endloop
set str = str + "\n"
set x = x + 1
endloop
return str
endmethod
private method onDestroy takes nothing returns nothing
call BJDebugMsg("DG::Pattern::onDestroy - not supposed to be destroyed")
endmethod
public method at takes Position pos returns string
return StringCharAt(this.data, pos.x * this.height + pos.y)
endmethod
// Return the position of a node in the pattern with a wall facing in the specified direction.
// For example: stars * are nodes with a wall facing east (random node is returned)
// ### --> ##*
// ##. Direction EAST #*.
public method randomWallFacing takes Direction direction returns Position
local Position result = 0
local Position adjacent = 0
// Implementation: keep picking random position until it's a correct node
// Alternative: select all wall nodes, pick random. But that's not what this implementation is currently doing...
loop
set result = Position.random(this.width, this.height)
set adjacent = result.adjacent(direction)
if (this.at(result) == PATTERN_SYMBOL_ROOM) then
// check if adjacent is out of boundaries (i.e. does not lie within the pattern
exitwhen adjacent == result or adjacent.x >= this.width or adjacent.y >= this.height
// check if adjacent node is SYMBOL_NONE
exitwhen this.at(adjacent) == PATTERN_SYMBOL_NONE
endif
// else, keep picking random position
endloop
return result
endmethod
// Scans the Layout for collisions
private method checkLayout takes Position position, integer id returns boolean
local Position posX = position
local Position temp = position
local integer x = 0
local integer y = 0
loop
exitwhen x >= this.width
set y = 0
set position = posX
loop
exitwhen y >= this.height
// Check Layout[x][y]
if (this.at(Position.create(x, y)) == PATTERN_SYMBOL_ROOM) then
if (id != 0) then
set Layout[position.x][position.y] = id
elseif (Layout[position.x][position.y] != 0) then
return false
endif
endif
// Check if adjacent node is within bounds
set temp = position.adjacent(Direction.NORTH)
if (temp == position) then
return false
endif
set position = temp
set y = y + 1
endloop
// Check if adjacent node is within bounds
set temp = posX.adjacent(Direction.EAST)
if (temp == posX) then
return false
endif
set posX = temp
set x = x + 1
endloop
return true
endmethod
public method scan takes Position position returns boolean
return this.checkLayout(position, 0)
endmethod
public method register takes Position position, integer id returns nothing
call this.checkLayout(position, id)
endmethod
endstruct
// ***************************************
// *
// * Room
// *
// ***************************************
public struct Room extends array
public integer height
public string class
public DG_Pattern pattern
endstruct
// ***************************************
// *
// * Destructible
// *
// ***************************************
public interface Object
public method instance takes real x, real y, real z, real angle returns nothing
endinterface
public struct Destructible extends Object
private static integer instances = 0
private static integer maxInstances = 100
private integer id
private real z
private real distance
private real angle
private real face
private real minScale
private real maxScale
private integer maxVariation
private static method onInit takes nothing returns nothing
static if (SOFT_LOAD) then
set maxInstances = 3
endif
endmethod
public static method create takes integer id, real x, real y, real z, real face, real minScale, real maxScale, integer maxVariation returns Destructible
local Destructible this = Destructible.allocate()
set this.id = id
set this.distance = SquareRoot(x*x + y*y)
set this.angle = Atan2(y, x)
set this.z = z
set this.face = face
set this.minScale = minScale
set this.maxScale = maxScale
set this.maxVariation = maxVariation
return this
endmethod
// Angle in radians
public method instance takes real x, real y, real z, real angle returns nothing
local real a = angle * bj_DEGTORAD + this.angle
local real rx = x + this.distance * Cos(a) + DG_EPSILON
local real ry = y + this.distance * Sin(a) + DG_EPSILON
//call BJDebugMsg("Angle: " + R2S(angle))
//call BJDebugMsg("Position: " + R2S(rx) + "," + R2S(ry))
// TO DO: register destructible (to be removed) ?
call CreateDestructableZ(this.id, rx, ry, this.z + z, angle + this.face, GetRandomReal(this.minScale, this.maxScale), GetRandomInt(0, this.maxVariation - 1))
set instances = instances + 1
if (instances == maxInstances) then
set instances = 0
call TriggerSleepAction(0)
endif
endmethod
endstruct
// ***************************************
// *
// * Doodad
// *
// ***************************************
public struct Doodad extends Object
readonly DG_Setting setting
readonly string class
readonly Array destructibles
public static method create takes Destructible d returns Doodad
local Doodad this = Doodad.allocate()
set this.setting = setting
set this.class = class
set this.destructibles = Array.create()
call this.destructibles.push(d)
return this
endmethod
public method add takes Destructible d returns Doodad
call this.destructibles.push(d)
return this
endmethod
public method instance takes real x, real y, real z, real angle returns nothing
local integer i = 0
loop
exitwhen i >= this.destructibles
call Destructible(this.destructibles[i]).instance(x, y, z, angle)
set i = i + 1
endloop
endmethod
endstruct
// ***************************************
// *
// * Setting
// *
// ***************************************
private struct SettingDoodadHelper extends array
// I probably want to set wall doodads under these categories as well (but not add them to classNames so randomClassName() still works)
private Table doodads
private Table classNames
private integer lastClassName
public static method create takes DG_Setting setting returns SettingDoodadHelper
local SettingDoodadHelper this = setting
set this.doodads = Table.create()
set this.doodads[StringHash("wall")] = Array.create()
set this.doodads[StringHash("wall-corner")] = Array.create()
set this.doodads[StringHash("wall-floor")] = Array.create()
set this.doodads[StringHash("wall-stairs")] = Array.create()
set this.doodads[StringHash("wall-gate")] = Array.create()
set this.classNames = Table.create()
set this.lastClassName = 0
return this
endmethod
public method operator [] takes string className returns Array
if (this.doodads[StringHash(className)] == 0) then
set this.classNames.string[this.lastClassName] = className
set this.lastClassName = this.lastClassName + 1
set this.doodads[StringHash(className)] = Array.create()
endif
return this.doodads[StringHash(className)]
endmethod
public method random takes string className returns Object
local Array arr = this[className]
if (arr != 0) then
return arr.random()
endif
return 0
endmethod
public method randomClassName takes nothing returns string
return this.classNames.string[GetRandomInt(0, this.lastClassName - 1)]
endmethod
// [] takes string, returns Array of doodads
// random takes string class returns random doodad
// ^^ not good enough yet. We need string class + location type (i.e. library / wall decoration vs library / floor decoration)
// ^^ well, do we really need it that complicated?
endstruct
public struct Setting
private static Table name2setting
private static Setting lastSetting
readonly string name
readonly Array patterns
public SettingDoodadHelper doodads
public real height
public integer maxHeight
private static method onInit takes nothing returns nothing
set name2setting = Table.create()
endmethod
public static method create takes string name returns Setting
local Setting this = Setting.allocate()
debug call assert(name2setting[StringHash(name)] == 0, "DG::Setting::create - name already taken")
set this.name = name
set this.patterns = Array.create()
set this.doodads = SettingDoodadHelper.create(this)
set name2setting[StringHash(name)] = this
set lastSetting = this
return this
endmethod
private method onDestroy takes nothing returns nothing
call BJDebugMsg("DG::Setting::onDestroy - not supposed to be destroyed!")
endmethod
public static method operator [] takes string name returns Setting
return name2setting[StringHash(name)]
endmethod
public static method random takes nothing returns Setting
return GetRandomInt(1, lastSetting)
endmethod
endstruct
// ***************************************
// *
// * Layout Engine
// *
// ***************************************
public function LayoutEngine_execute takes Dungeon this returns nothing
local integer roomId = 1
local DG_Position position = 0
local DG_Position patternPosition = 0
local DG_Wall wall = 0
local DG_Pattern pattern
// Reset layout
set DG_Layout.width = this.width
set DG_Layout.height = this.height
call DG_Layout.reset(0)
// Create first room
// Note: you could modify this to create it at a fixed position, i.e. at dungeon entrance
set position = DG_Position.create(this.width / 2, this.height / 2)
set pattern = DG_Pattern(this.setting.patterns.random())
call pattern.register(position, roomId)
set DG_Room(roomId).height = 0
set DG_Room(roomId).pattern = pattern
set roomId = roomId + 1
// debug call log(DG_Layout.toString())
loop
exitwhen roomId >= this.rooms
set wall = DG_Layout.getRandomWall()
set pattern = this.setting.patterns.random()
//debug call log("Pattern selected: \n" + pattern.toString())
set patternPosition = pattern.randomWallFacing(wall.direction.rotate(180))
//debug call log("Pattern position: " + patternPosition.toString())
// substract patternPosition, AND then add 1 depending on the direction (y+1 for NORTH, x-1 for WEST, etc.)
set position = wall.position.adjacent(wall.direction).sub(patternPosition)
if (pattern.scan(position)) then
call pattern.register(position, roomId)
set DG_Room(roomId).height = IMaxBJ(0, IMinBJ(this.setting.maxHeight, GetRandomInt(Room(Layout[wall.position.x][wall.position.y]).height - 1, Room(Layout[wall.position.x][wall.position.y]).height + 1)))
set DG_Room(roomId).pattern = pattern
// Register gate
if (wall.direction == DG_Direction.NORTH) then
set this.gatesNorth[wall.position] = 1
elseif (wall.direction == DG_Direction.EAST) then
set this.gatesEast[wall.position] = 1
elseif (wall.direction == DG_Direction.SOUTH) then
set this.gatesNorth[wall.position.adjacent(DG_Direction.SOUTH)] = 1
elseif (wall.direction == DG_Direction.WEST) then
set this.gatesEast[wall.position.adjacent(DG_Direction.WEST)] = 1
endif
set roomId = roomId + 1
//debug call log(" room created")
//debug call log(DG_Layout.toString())
static if (not SOFT_LOAD) then
call TriggerSleepAction(0.0)
endif
endif
call wall.destroy()
static if (SOFT_LOAD) then
call TriggerSleepAction(0.0)
endif
endloop
endfunction
// ***************************************
// *
// * Wall Engine
// *
// ***************************************
// TO BE IMPLEMENTED: MULTI-LEVEL DUNGEONS
public function WallEngine_execute takes Dungeon this returns nothing
local integer x = 0
local integer y
local real rx = this.x
local real ry
local DG_Position position
local DG_Position adjacent
local DG_Room room
local DG_Room adjacentRoom
loop
exitwhen x >= Layout.width
set y = 0
set ry = this.y
set position = DG_Position.create(x, y)
loop
exitwhen y >= Layout.height
set room = DG_Room(Layout[x][y])
// Check WEST wall
if (x == 0) then
call DG_Object(this.setting.doodads["wall"].random()).instance(rx, ry, 0, DG_Direction.WEST.angle())
endif
// Check SOUTH wall
if (y == 0) then
call DG_Object(this.setting.doodads["wall"].random()).instance(rx, ry, 0, DG_Direction.SOUTH.angle())
endif
// Check EAST wall
set adjacent = position.adjacent(DG_Direction.EAST)
set adjacentRoom = DG_Room(Layout[adjacent.x][adjacent.y])
if ((adjacent == position) or room != adjacentRoom) then
if (this.gatesEast.exists(position)) then
call DG_Object(this.setting.doodads["wall-gate"].random()).instance(rx, ry, 0, DG_Direction.EAST.angle())
else
call DG_Object(this.setting.doodads["wall"].random()).instance(rx, ry, 0, DG_Direction.EAST.angle())
endif
endif
// Check NORTH wall
set adjacent = position.adjacent(DG_Direction.NORTH)
set adjacentRoom = DG_Room(Layout[adjacent.x][adjacent.y])
if ((adjacent == position) or room != adjacentRoom) then
if (this.gatesNorth.exists(position)) then
call DG_Object(this.setting.doodads["wall-gate"].random()).instance(rx, ry, 0, DG_Direction.NORTH.angle())
else
call DG_Object(this.setting.doodads["wall"].random()).instance(rx, ry, 0, DG_Direction.NORTH.angle())
endif
endif
// Check SOLID_WALL nodes
if (room == 0) then
call DG_Object(this.setting.doodads["wall-floor"].random()).instance(rx, ry, this.setting.height, DG_Direction.NORTH.angle())
endif
set position = adjacent
set y = y + 1
set ry = ry + DG_NODE_SIZE
endloop
set x = x + 1
set rx = rx + DG_NODE_SIZE
endloop
endfunction
// ***************************************
// *
// * Dungeon
// *
// ***************************************
struct Dungeon
private integer seed
readonly integer width
readonly integer height
readonly real x
readonly real y
readonly Setting setting
readonly Table gatesNorth
readonly Table gatesEast
readonly integer rooms
public static method create takes integer seed, integer width, integer height returns Dungeon
local Dungeon this = Dungeon.allocate()
set this.seed = seed
set this.width = width
set this.height = height
return this
endmethod
public method instance takes real x, real y returns nothing
debug call log("*** Initializing Dungeon Settings")
call SetRandomSeed(this.seed)
set this.setting = Setting.random()
set this.x = x
set this.y = y
set this.gatesNorth = Table.create()
set this.gatesEast = Table.create()
set this.rooms = R2I(FEATURES_PER_NODE * this.width * this.height + 0.5)
debug call log("*** Running Layout Engine...")
call DG_LayoutEngine_execute(this)
debug call log("*** Printing layout...")
debug call log(Layout.toString())
debug call log("*** Creating walls...")
call DG_WallEngine_execute(this)
debug call log("*** Cleaning up...")
// TO DO: clean up doors
endmethod
endstruct
endlibrary
JASS:
DUNGEON GENERATOR
=================
'Overview'
The content of this document:
1. Overview
2. Installation
3. Library design & API
'Installation'
Copy all triggers in "Libraries" as they are dependencies
Copy the "Dungeon Generator" triggers
Copy any object data you wish to transfer
Initialize settings & globals
"Configuration options"
MAX_WIDTH The maximum width of a single Dungeon
MAX_HEIGHT The maximum height of a single dungeon
NODE_SIZE The in-game size of a dungeon node (best to keep as-is)
PATTERN_SYMBOL_ROOM The pattern symbol for "room node"
PATTERN_SYMBOL_NONE The pattern symbol for "no room node"
PATTERN_SYMBOL_LAYER The pattern symbol for "next row"
FEATURES_PER_NODE A dungeon of size (width, height) has exactly width * height nodes.
A "feature" is a room, i.e. how many rooms to create for a dungeon of a certain size
SOFT_LOAD If true, a dungeon is created slowly but smoothly (without lag in-game).
This is ideal for games where the next level of a dungeon can be created in the background
while players are still fighting their way through the previous dungeon
'Library Design'
The library is designed as a (large) set of auxiliary structs, and "engine" functions.
The "engine" functions are functions responsible for actually generating the dungeon content.
"Auxiliary structs"
1. DG_Layout (static)
Layout is a 2D grid that maps positions (x, y) to room nodes.
A grid node is either 0 (solid wall) or a room-id, a unique identifier for a room. A room consists of several adjacent gridnodes.
Layout[x][y] Retrieve/modify room node at position (x, y)
Layout.width Retrieve/modify the width of the grid
Layout.height Retrieve/modify the height of the grid
Layout.reset(value) Resets the grid to the specified value (only width x height nodes)
Layout.toString() Retrieve string representation of the grid
Layout.getRandomWall Retrieve a random wall (only returns nodes with a room-id, thus excluding 0-node)
2. DG_Direction
There are 4 directions: north, east, south and west.
DG_Direction.NORTH Direction pointing up
DG_Direction.EAST Direction pointing right
DG_Direction.SOUTH Direction pointing down
DG_Direction.WEST Direction pointing left
DG_Direction.random() Retrieves a random direction
direction.rotate(90) Rotates a direction by a multiple of 90 degrees
direction.toString() Retrieve string representation of the direction
3. DG_Position
A position is a set of (x, y) integers.
All positions should - under normal circumstances - be within Layout boundaries
A position does not have to be destroyed
DG_Position.checkBoundariesX(x) Maps the x coordinate between Layout boundaries (between 0 and Layout.width - 1)
DG_Position.checkBoundariesY(y) Maps the y coordinate between Layout boundaries (between 0 and Layout.height - 1)
DG_Position.create(x, y) Creates a new Position
position.x Retrieve/modify X coordinate
position.y Retrieve/modify Y coordinate
position.data Retrieve/modify data at Layout[x][y]
position.toString() Retrieve string representation of the position
position.add(second) Addition of 2 positions
position.sub(second) Substraction of 2 positions
position.north Retrieve position north of this position
position.east Retrieve position east of this position
position.south Retrieve position south of this position
position.west Retrieve position west of this position
position.adjacent(direction) Retrieve position adjacent towards the given direction
position.random(width, height) Retrieve random position (0/width, 0/height)
4. DG_Wall
A wall is a combination of a position and a direction.
Specifically, a wall is positioned at a grid node, and pointing towards the specified direction.
Each grid node can have up to 4 walls (each facing 1 of the 4 directions).
A grid node has a wall in a certain direction if the adjacent node (towards that direction) is either another room or solid wall (0)
A wall must be destroyed after use!
DG_Wall.create(position, direction) Creates a wall at position, facing direction
wall.opposite Retrieve the opposing wall (at adjacent position, facing opposing direction)
i.e. (3, 5) & NORTH opposes (3, 6) & SOUTH
wall.opposes(other) Returns true if the walls are opposing each others
5. DG_Pattern
A pattern is a room pattern. A room pattern can look like
##. and defines how a room looks like when placed in the grid.
### A dungeon can choose between multiple patterns when generating rooms.
### A pattern is defined using a data string (read below)
DG_Pattern.create(data) Creates a pattern using the data string. A data string looks like: "##.|###|###",
where "#" means "room node", "." means "no room node" and "|" means "start new row".
The example describes the pattern as seen above
pattern.toString() Retrieves a string representation of the pattern
pattern.width Retrieves the width of the pattern
pattern.height Retrieves the height of the pattern
pattern.size Retrieves the number of room nodes in the pattern
pattern.at(position) Retrieves character at the specified position (either "#" or ".")
pattern.randomWallFacing(direction) Retrieves a DG_Position (not DG_Wall, as suggested by the function name).
#*. For example: randomWallFacing(Direction.EAST) would return any position containing
##* a wall facing the EAST. In the example pattern, the possible room nodes "#" have been
##* replaced by a "*" symbol to show which nodes could be returned.
pattern.scan(position) Scans a position on the Layout grid. Returns true if the pattern does not collide on the Grid
pattern.register(position, room) Writes the pattern on the Grid (set grid node data = room)
6. DG_Room
A room is a unique identifier for a set of properties that define the room.
room.height The "z" height of the room
room.pattern The pattern of the room
room.class The class name of the room (i.e. "library", "torture", etc.)
7. DG_Object (interface)
An object is a real in-game entity (i.e. doodad, destructible)
object.instance(x, y, z, angle) Creates an instance of the object at position (x, y, z), facing specified angle
8. DG_Destructible (DG_Object)
A destructible is a single destructible in the object editor
DG_Destructible.create(id, x, y, z, face, minScale, maxScale, maxVariation)
Defines a destructible. id = object editor RAW ID
x, y, z: offset position when the destructible is instantiated
face: default facing angle of destructible
minScale & maxScale: specifies size boundaries of destructible instances
maxVariation: specifies the possible variations of the destructible (between 0 and maxVariation - 1)
9. DG_Doodad (DG_Object)
A doodad is a collection of destructibles that are relatively positioned
DG_Doodad.create(destructible) Creates a new doodad
doodad.add(destructible) Adds a new destructible to the doodad
10. DG_Setting
A setting is a set of options that define a specific decor/atmosphere for a dungeon.
Each dungeon has 1 associated setting, which defines the look of the dungeon, ranging from Stone dungeons to forests and villages.
DG_Setting.create(name) Creates a setting with specified identifier
DG_Setting[name] Retrieves a setting with specified identifier
DG_Setting.random() Retrieves a random setting
setting.name Retrieves the name of the setting
setting.patterns Array* of patterns
setting.doodads[class] Array of Objects, i.e. setting.doodads["library"] retrieves all "library" doodads
Predefined / required classes: "wall", "wall-corner", "wall-stairs", "wall-gate", "wall-floor"
setting.doodads.random(class) Retrieves random Doodad of class
setting.doodads.randomClassName Retrieves random class name (i.e. "library" or "torture", ...)
setting.maxHeight Retrieve/modify the number of layers in the dungeon (a dungeon consists of multiple layers, specifying the height of a room)
setting.height The in-game height of a single layer (a room at layer 2 is at height*2)
Array container
a.push(object) Adds the object to the array
a.size Retrieves the number of objects in the array
a[index] Retrieves the object at the index
a.random() Retrieves a random object in the array
"Engine Functions"
1. LayoutEngine_execute(Dungeon this)
This function is responsible for generating a dungeon layout for the specified dungeon.
Specific responsabilities:
- Creating a specified number of rooms (and specifying their height/pattern)
- Filling the Layout grid with interconnecting roomnodes so the dungeon layout "looks" like a dungeon
- Initializing the Dungeon door list (a list of all doors/stairs in the dungeon)
2. WallEngine_execute(Dungeon this)
This function is responsible for generating all the walls, floors, stairs and gates of a dungeon
Specific responsabilities:
- Instantiating all walls, floors, stairs and gates of the dungeon.
At this point we should have a physical dungeon in-game
3. DecorationEngine_execute(Dungeon this)
This function is responsible for decorating the dungeon.
4. ThreatEngine_execute(Dungeon this)
This function is responsible for creating threats in the dungeon (basically: encounters & traps)
"Dungeon"
Dungeon.create(seed, width, height) Creates a new dungeon
dungeon.instance(x, y) Instantiates the dungeon at in-game coordinates (x, y)
Attachments
Last edited: