Universal Map Generator¶
UMG is a scripting language dedicated to procedural generation of grid-based maps.
It is based on a number of ‘generators’ that can be composed in a similar way to HTML or to how widgets are used to build UI applications.
When a UMG script is run, its output is a map in which each cell contains an ordered list of tokens. The interpretation of these tokens is up to the consumer of the map, typically they will represent some physical objects. Each time a script is run, it can generate a different, random map, with the degree of randomness dependent on the script’s logic.
The UMG software can also render an ASCII representation of the map, seen above, but it only serves as a quick preview of the output, with only the top token being shown.
Basics¶
The most basic UMG script fills the entire map with a single token:
Set("floor")
Note that the size of the map is not encoded in the script, but passed in to UMG when the script is run. Therefore the script can generate maps of various sizes, much like a HTML document adjusts itself to the size of the browser window.
Every script consists of a single top-level generator. In the above example, it is a single Set command, but a generator can also be formed by a number of sub-generators chained together, which are executed in that order.
{
Set("floor")
Set("wall")
Remove("wall")
}
Many generators take sub-generators as arguments and execute them in sub-areas of the map. This script will surround a floor-filled level with a wall of width 1.
{
Set("floor")
Border(1, Set("wall"))
}
Here, Border is a generator that takes two arguments: the width of the border, and the generator that is executed on the border. So whatever map this script is executed on, it will place a wall on its outer-most cells.
It is possible to place a number of sub-generators of given sizes inside a map, which is useful for placing buildings or rooms.
Place(
minSize = {3 3}
maxSize = {6 6}
generator = Border(1, Set("wood_wall"))
count = 3
minSpacing = 1
)
The above script will randomly place three wooden buildings with height and width between 3 and 6. Place always makes sure that its sub-generators don’t overlap.
Generators¶
None¶
Does nothing.
Set¶
Set("token1", "token2", "token3")
Sets given tokens. They are added to the back of the list of tokens at each cell.
Filter¶
Filter(Predicate, Generator, [GeneratorIfNot]) # Last argument is optional
Runs the given generator, but only on cells that satisfy Predicate. See Predicates
If GeneratorIfNot is present, it is run on all the rest of the cells.
Example:
Filter(On("floor"), Set("treasure"))
Margin¶
Margin(Width, BorderGenerator, InsideGenerator)
Margin([TOP, BOTTOM, LEFT or RIGHT], Width, BorderGenerator, InsideGenerator)
Takes two generators, one is run on the border of given width, the other on the rest of the area. With the Position argument present, the margin is present only on the given side.
Example:
Margin(TOP, 3, Set("water"),
Margin(TOP, 10, Set("sand"), Set("grass"))
SplitH¶
SplitH(Ratio, LeftGenerator, RightGenerator)
Splits the area horizontally according to Ratio, and runs the corresponding generators.
SplitV¶
SplitV(Ratio, TopGenerator, BottomGenerator)
Splits the area vertically according to Ratio, and runs the corresponding generators.
Position¶
Position(
[MIDDLE, LEFT_CENTER, RIGHT_CENTER, TOP_CENTER, BOTTOM_CENTER, MIDDLE_V, MIDDLE_H],
Size,
Generator,
MinSize, # Optional. If Size is none, then it is randomly picked between MinSize and MaxSize
MaxSize # Optional.
)
Places the given generator in the area in the chosen position. In the options MIDDLE_V and MIDDLE_H, the sub-area’s size is stretched vertically or horizontally to the whole area. In the other cases the size is fixed.
Example:
# Places a 10x10 lake in the middle
Position(MIDDLE, {10 10}, Set("lake"))
# Places a wall of width between 2 and 4 running vertically through the middle.
Position(
MIDDLE_V,
none,
Set("wall"),
{2 0},
{4 0}
)
Place¶
Place(
Size,
Generator,
Count,
Predicate, # Optional.
MinSize, # Optional. If Size is none, then it is randomly picked between MinSize and MaxSize
MaxSize, # Optional.
minSpacing # Optional.
)
Place(
(---as above---),
.
.
(---as above---)
)
Run the sub-generator at a given number of randomly picked areas that satisfy Predicate. The second version takes multiple sub-generators with identical arguments, enclosed in brackets.
Example:
# Fills the level with rock and places 3-7 rooms of size 5-10, a minimum of two tiles apart.
{
Set("rock")
Place(none, Reset("floor"), {3 7}, True, {5 5}, {10 10}, 2)
}
# Similar to the above, but also with an extra 4x4 lava lake.
Place(
({4 4}, Reset("lava"), 1, True)
(none, Reset("floor"), {3 7}, True, {5 5}, {10 10}, 2)
)
NoiseMap¶
NoiseMap(
(Lower, Upper, Generator),
.
.
(Lower, Upper, Generator)
)
Generates a height map using Perlin noise and runs generators according to given altitude range. The total range spans from 0 to 1.
Example:
NoiseMap(
(0.0, 0.3, Set("water")),
(0.3, 0.4, Set("sand")),
(0.4, 0.8, Set("grass")),
(0.8, 1.0, Set("mountain))
)
Choose¶
Choose(
[Probability1] Generator1, # The probabilities are optional, defaults to uniform distribution
.
.
[ProbabilityN] GeneratorN
)
Runs a randomly chosen generator among specified.
Example:
Choose(
0.3 Set("water"),
0.7 Set("lava")
)
Connect¶
Connect(
ToConnectPredicate,
Cost, EntryPredicate, EntryGenerator
)
Connect(
ToConnectPredicate,
(Cost1, EntryPredicate1, EntryGenerator1),
.
.
(CostN, EntryPredicateN, EntryGeneratorN)
)
Runs a pathfinding algorithm to connect all of the grid cells specified by ToConnectPredicate. By default, the cost of traversing each cell is 1, but if the cell matches an EntryPredicateX, then it will be traversed with cost CostX, and EntryGeneratorX will be executed on that cell.
Example:
Fills the map with rock; places a 10x10 lake in the middle, and five 4x4 rooms; connects the rooms with cost of digging the rock equal to two, and cost of crossing the water equal to five.
{
Set("rock")
Position(MIDDLE, {10 10}, Set("water"))
Place(5, Reset("floor"), Not On("water"), {4 4})
Connect(On("floor"),
(2, On("rock"), Reset("corridor")),
(5, On("water"), Reset("bridge"))
)
}
FloodFill¶
FloodFill(Predicate, Generator)
Starting from cells matching Predicate, flood-fills the entire map, and runs Generator on the filled tiles.
Predicates¶
A predicate returns either true or false for a given cell.
True¶
Always returns true.
Area¶
Area(Radius, Predicate, [MinCount]) # MinCount is optional, by default set to 1.
Returns true if at least MinCount cells within Radius satisfy Predicate.
Example:
Runs a cellular automaton algorithm according to http://www.roguebasin.com/index.php?title=Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels.
{
Filter(Chance(0.45), Set("cell_added"))
Repeat(5, {
Filter(Area(1, On("cell_added"), 5), Set("cell_added2"))
Filter(On("cell_added2"), Set("cell_added"), Remove("cell_added"))
Remove("cell_added2")
})
}
Macros¶
Macros are a useful way to organize and reuse code. You can find a short tutorial here: https://pastebin.com/tHDtvwzx.
Using UMG in KeeperRL¶
It is possible to use UMG to design new maps and level in KeeperRL. The game uses a mapping to translate from tokens to in-game level generator actions, such as placing furniture, items or creatures.
The generators are defined in the file random_layouts.txt, for example:
"village" {
Set("grass")
...
}
"castle" {
Margin(1, Set("castle_wall"), Set("castle_floor"))
Place({1 1}, Choose(Set("sword"), Set("potion")), {5 10})
...
}
The mapping is defined in layout_mapping.txt, for example:
"default_mapping" {
"grass" Place "GRASS"
"castle_wall" Place "CASTLE_WALL"
"castle_floor" Place "FLOOR_STONE1"
"sword" Items 1 "Sword"
"potion" Items 1 Potion Heal FLESH
}
You can use your layout for an enemy, by referencing it in enemies.txt. For example:
"ORC_VILLAGE"
{
settlement = {
type = RandomLayout { "village" {20 15} "default_mapping" }
...
}
...
}
You can use multiple mappings to reskin your levels in various ways.
"super_level_mapping" inherit "default_mapping" {
"castle_wall" Place "MY_SUPER_WALL"
"sword" Items 1 "MySuperSword"
}
"SUPER_VILLAGE"
{
settlement = {
type = RandomLayout { "village" {20 15} "super_level_mapping" }
...
}
...
}
Testing layouts using the command line¶
If you open cmd.exe on Windows or a terminal window on Mac or Linux, you can generate a chosen layout in KeeperRL and draw its ASCII representation. For example:
./keeper --layout_name "castle" --layout_size 20:15
Your tokens must be defined in the file data_free/glyphs.txt, otherwise they will not be rendered.
Example glyphs.txt:
"grass" ~ green
"castle_wall" # gray
"castle_floor" . gray
"sword" ) blue
"potion" ! red