Thursday 23 July 2015

Populating the Dungeon

(A lot of the work in this post is based off ideas introduced in this excellent blog post. I highly recommend reading it.)

Using the algorithm outlined in my previous post I have a dungeon generating, ensuring the chosen rooms are included and filling in the blanks with random rooms. The random rooms can be given an irregular shape by running one of multiple algorithms on them (At the moment the supported ones are Overlapping Rectangles, Starburst, Cellular Automata, Chambers. Credit to Unangband/Andrew Doull.)

So the output of this algorithm will look something like so:


As you can see it contains 2 vaults (one with a downward stair), and the rooms themselves are made more interesting by running them through the Cellular Automata algorithm. I feel that this map is now interesting enough in structure to begin to place enemies and etc.


To give the map a coherent feel I decided to not go for the 'random' approach to enemy placement, instead opting for a faction system. The idea is that each level should have a Major faction that dominates it, with a big boss sitting in one of the rooms. Every other room will then be influenced by this faction, based on its distance from the boss room. Some other minor factions can be sprinkled throughout the map to give some variety to the encounters.
Furthermore to give the rooms the feel of being owned by a faction (other than just the monsters you encounter in that room), a set of 'Features' can be placed in the room, based on the faction that owns it (for example the 'Fungi' faction will have glowing mushrooms, and the 'Toad' faction will have pools of water).

How the algorithm works is thus:

  1. In the level definition a set of 'Major' factions and a set of 'Minor' factions are specified.
  2.  Once the level has been generated a single 'Major' faction is selected. The largest room in the dungeon is then assigned this faction.
  3. The furthest quarter of the rooms from this selected room are then assigned a random faction from the 'Minor' list.
  4. All the remaining rooms are assigned an 'Influence' value based on their distance from the central room (minor faction rooms are assigned a random influence value).

Then for each room

First we look to place the features. Features can be assigned an influence range to appear within, a placement location that determines where they are placed (either FurthestFromEntrance, Wall, Centre or Any) and a Coverage percent (this determines what percentage of the valid tiles to cover with this feature).
  1.  Seperate all floor tiles into 4 lists.
    • A list of tiles sorted by average distance from the entrances (for FurthestFromEntrance).
    • A list of the floor tiles that are adjacent to a wall (for Wall).
    • A list of all the floor tiles that are not adjacent to a wall (for Centre).
    • A list of all the floor tiles (for Any).
  2. For each feature defined in the feature list.
    • Check the feature is valid for this influence.
    • Get the percentage through this features range ( (influence-min) / (max-min) )
    • Multiply the coverage by this factor to get the coverage for this feature in this room.
    • Calculate the number of features to place. (NumValidTiles * CurrentCoverage)
    • Randomly pick tiles from the valid list and place the feature on this tile (remember to remove the tile from all the lists after using it).
This will leave a room filled with features for the faction. However there is no guarantee the new features havent blocked off the entrances, so to prevent this I try to path (using A*) between each pair of entrances, clearing features that block the route.

Now it is time to place the enemies. The faction definition contains a list of encounters (groups of monsters to spawn together), and the min/max influence they appear at. I then randomly pick a valid encounter for the rooms influence, and place the enemies on randomly selected floor tiles. (This part of the algorithm needs work, possibly by basing the number of spawned enemies off of the room size).

So at the end of all this you should be left with something like so:


This is the same level as before, but with the features added and enemies spawned. The major faction is the 'Fungi' faction and the 4 minor factions placed are all 'Toad' factions.

A quick picture of a dungeon being explored:


Another Before/After:





Friday 17 July 2015

Dungeon Generation

The focus this week has been on dungeon generation.

Up till this week I had been using a BSP to generate the level, but I was unsatisfied with this due to the lack of control I had over it. Furthermore a problem with the algorithms I could find posted online again had the same issue. So I set out to create a new algorithm with the following constraints:

1. Should allow control over the rooms to be included. For example 2 stair rooms, 1 large vault and 3 minor vaults.

2. Should fill the remaining space with random rooms.

3. Should connect everything up.

With these steps in mind I came up with this:

The algorithm is based around 'docking' rooms to one of the four corners of a space. The space is then divided into two parts (based on the room's size), and the algorithm is called recursively on the subparts. The strength of this is that you can specify a list of rooms to be included, dock those in the spaces they will fit, then fill the rest with random rooms.

Example:


Starting with:

###############

###############

###############

###############

###############

###############

###############


Placing a single room:
...
...
...

You pick the NE corner, making the grid look like:
###############

###########...#

###########...#

###########...#

###############

###############

###############


Then the remaining space gets seperated as follows:
1111111111#####

1111111111#...#

1111111111#...#

1111111111#...#

1111111111#####

111111111122222

111111111122222

Where 1 and 2 are the two different zones created.

You can then run the algorithm on zone 1 and zone 2.

Using offsets and random sizes for the random rooms (and picking random corners) the resulting grid gets filled with rooms.

You can then place the doors and connect the rooms via corridors (I use a reduced delauney triangulation to find the pairs of doors to connect, then connect them by using A* to find a path).

Examples of the final output:

##################################################
##################################################
####...............###############################
####.#############.#######.......#################
####.#tBBbt#tbbBt#.#######.......+.########....###
####.#bcTcb#bcTcb#.#######.......#.########....###
####.#B.c.b#B6c6B#.#######.......#.########....###
####.#b...b#B...b#.#######.......#.########....###
####.#b...b#B...b#.#######.......#.########....+.#
####.#b...B#B...b#.###############.########....#.#
####.#B...B#b...b#.###############.########....#.#
####.#t...t#t...t#.##############..########....#.#
####.###.#####.###.############....#############.#
####.#...........#.##########...##.#############.#
####.#t.........t#.########...####.############..#
####.######.######.########.######.#########.....#
####......#+#......########.######.#########.###.#
###########........................########..###.#
###########..##########.#+########.######...####.#
############............#.......##...##...######.#
############.####+###.###.......##.#.##.########.#
############.#......#.###.......##.#..#.########.#
#####...####.#......#.###.......##......########.#
#####.>.+....#......#.###.......##..#+##########.#
#####...#.####......#.###.......##.##.........##.#
#########.####......#.###.......#..##.........##.#
#########.###########.###.......#.###.........##.#
#########.###########.###########.###.........##.#
#########.###########.###########.###.........##.#
#########.###########.###########.###.........##.#
#########.###########.###########.##############.#
#########.###########.###########.##############.#
#########.###########.###########.##############.#
#########.###########.###########.##############.#
#########...#########.............##############.#
###########.#########..#########################.#
###.......#.#########..#.........####..........#.#
###.......#.#########..#.........####..........+.#
###.......#.##......#..#.........####..........#.#
###.......#.##......#..#.........####..........#.#
###.......+.##......#..#.........####..........#.#
###.......#.##......#..#.........####..........#.#
###.......#.##......#..#.........####..........#.#
###.......#.##......#..+.........####..........#.#
###.......#.##......+..#.........####..........#.#
###########.##......#..#.........####..........#.#
###########..########..#########################.#
###########......................................#
##################################################
##################################################

Drawbacks:
The resulting map can tend to look quite 'gridlike' (though greater ranges for padding and room size will reduce this tendency).

The map space is not used efficiently (though in most cases, you dont want to pack the map too tightly, so its not the biggest issue).

If the grid is too small the rooms may not be able to be placed (Can brute force by increasing the size of the grid then regenerating, repeat until everything fits).


The way I use it is to have a level description that defines 'required' rooms and 'optional' rooms (with some guide for how to pick the optional ones, like Pick 2 from this list). So when creating a level I create a list of rooms to be added, created from all the 'Required' rooms, all the rooms selected from 'Optional' and any rooms added by the game (like stairs connecting to the previous room). Then using the algorithm all these rooms can be added to the grid, and the remaining space filled in with random stuff.

This algorithm seems to fit all my criteria, and so far I have been very happy with it.

New Roguelike Game

Announcing a new roguelike game:

Full graphical tiles.
Mouse driven at the core (keyboards are so overrated :p)
Enemy AI using Behaviour Trees.
Cooldown based abilites (no mana / stamina)
Speed based turns (entities get a different number of turns depending on their speed).
Crossplatform support (Java with Libgdx, so should work with windows, mac, linux, and presumably android once I make the UI support it).
Interesting passive abilities, items and status effects.