Changed procedural generation to use minimum spanning trees.

This commit is contained in:
Felix Ableitner 2013-08-07 00:13:04 +02:00
parent 5b3020ad3d
commit 602cc605e8
3 changed files with 358 additions and 367 deletions

View file

@ -1,305 +1,301 @@
/* /*
* Generator.cpp * Generator.cpp
* *
* Created on: 07.04.2013 * Created on: 07.04.2013
* Author: Felix * Author: Felix
*/ */
#include "Generator.h" #include "Generator.h"
#include <algorithm> #include <algorithm>
#include <assert.h> #include <assert.h>
#include <map> #include <map>
#include <set> #include <set>
#include <SFML/System.hpp> #include <SFML/System.hpp>
#include <Thor/Vectors.hpp> #include <Thor/Vectors.hpp>
#include "../Pathfinder.h" #include "../Pathfinder.h"
#include "../World.h" #include "../World.h"
#include "../sprites/Enemy.h" #include "../sprites/Enemy.h"
#include "../util/Log.h"
const int Generator::GENERATE_AREA_SIZE = 4;
const float Generator::GENERATE_AREA_RANGE = 4.0f; const int Generator::GENERATE_AREA_SIZE = 4;
const float Generator::GENERATE_AREA_RANGE = 4.0f;
/**
* Generates new random seed. /**
*/ * Generates new random seed.
Generator::Generator(World& world, Pathfinder& pathfinder) : */
mWorld(world), Generator::Generator(World& world, Pathfinder& pathfinder) :
mPathfinder(pathfinder) { mWorld(world),
} mPathfinder(pathfinder) {
}
/**
* Generates tiles near player position (maximum distance is determined by /**
* GENERATE_AREA_SIZE and GENERATE_AREA_RANGE). * Generates tiles near player position (maximum distance is determined by
*/ * GENERATE_AREA_SIZE and GENERATE_AREA_RANGE).
void */
Generator::generateCurrentAreaIfNeeded(const sf::Vector2f& playerPosition) { void
auto compare = [](const sf::Vector2i& a, const sf::Vector2i& b) { Generator::generateCurrentAreaIfNeeded(const sf::Vector2f& playerPosition) {
return a.x < b.x || (a.x == b.x && a.y < b.y); auto compare = [](const sf::Vector2i& a, const sf::Vector2i& b) {
}; return a.x < b.x || (a.x == b.x && a.y < b.y);
std::map<sf::Vector2i, float, decltype(compare)> open(compare); };
std::set<sf::Vector2i, decltype(compare)> closed(compare); std::map<sf::Vector2i, float, decltype(compare)> open(compare);
std::set<sf::Vector2i, decltype(compare)> closed(compare);
sf::Vector2i start((int) floor(playerPosition.x / Tile::TILE_SIZE.x),
(int) floor(playerPosition.y / Tile::TILE_SIZE.y)); sf::Vector2i start((int) floor(playerPosition.x / Tile::TILE_SIZE.x),
start /= GENERATE_AREA_SIZE; (int) floor(playerPosition.y / Tile::TILE_SIZE.y));
auto makePair = [&start](const sf::Vector2i& point) { start /= GENERATE_AREA_SIZE;
return std::make_pair(point, thor::length(sf::Vector2f(point - start))); auto makePair = [&start](const sf::Vector2i& point) {
}; return std::make_pair(point, thor::length(sf::Vector2f(point - start)));
};
open.insert(makePair(start));
while (!open.empty()) { open.insert(makePair(start));
auto intComp = [](const std::pair<sf::Vector2i, float>& left, while (!open.empty()) {
const std::pair<sf::Vector2i, float>& right) { auto intComp = [](const std::pair<sf::Vector2i, float>& left,
return left.second < right.second; const std::pair<sf::Vector2i, float>& right) {
}; return left.second < right.second;
sf::Vector2i current = };
std::min_element(open.begin(), open.end(), intComp)->first; sf::Vector2i current =
float distance = open[current]; std::min_element(open.begin(), open.end(), intComp)->first;
open.erase(current); float distance = open[current];
closed.insert(current); open.erase(current);
if (!mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) { closed.insert(current);
mGenerated[current.x][current.y] = true; if (!mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) {
sf::IntRect area(current * GENERATE_AREA_SIZE - mGenerated[current.x][current.y] = true;
sf::Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE) / 2, sf::IntRect area(current * GENERATE_AREA_SIZE -
sf::Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE)); sf::Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE) / 2,
generateTiles(area); sf::Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE));
for (const auto& enemyPosition : getEnemySpawns(area)) { generateTiles(area);
float distance = thor::length(enemyPosition - playerPosition); for (const auto& enemyPosition : getEnemySpawns(area)) {
if (distance > Character::VISION_DISTANCE) { float distance = thor::length(enemyPosition - playerPosition);
mWorld.insertCharacter(std::shared_ptr<Enemy>(new Enemy(mWorld, mPathfinder, enemyPosition))); if (distance > Character::VISION_DISTANCE) {
} mWorld.insertCharacter(std::shared_ptr<Enemy>(new Enemy(mWorld, mPathfinder, enemyPosition)));
} }
} }
if (mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) { }
if (closed.find(sf::Vector2i(current.x + 1, current.y)) == closed.end()) if (mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) {
open.insert(makePair(sf::Vector2i(current.x + 1, current.y))); if (closed.find(sf::Vector2i(current.x + 1, current.y)) == closed.end())
if (closed.find(sf::Vector2i(current.x, current.y + 1)) == closed.end()) open.insert(makePair(sf::Vector2i(current.x + 1, current.y)));
open.insert(makePair(sf::Vector2i(current.x, current.y + 1))); if (closed.find(sf::Vector2i(current.x, current.y + 1)) == closed.end())
if (closed.find(sf::Vector2i(current.x - 1, current.y)) == closed.end()) open.insert(makePair(sf::Vector2i(current.x, current.y + 1)));
open.insert(makePair(sf::Vector2i(current.x - 1, current.y))); if (closed.find(sf::Vector2i(current.x - 1, current.y)) == closed.end())
if (closed.find(sf::Vector2i(current.x, current.y - 1)) == closed.end()) open.insert(makePair(sf::Vector2i(current.x - 1, current.y)));
open.insert(makePair(sf::Vector2i(current.x, current.y - 1))); if (closed.find(sf::Vector2i(current.x, current.y - 1)) == closed.end())
} open.insert(makePair(sf::Vector2i(current.x, current.y - 1)));
} }
} }
}
/**
* Fill world with procedurally generated tiles. /**
* * Generates a minimum spanning tree on mTileNoise, starting from start with
* @param area Size and position of area to generate tiles for. Width and * a maximum total node weight of limit.
* height must each be a power of two. *
*/ * FIXME: Some nodes are selected more than once.
void */
Generator::generateTiles(const sf::IntRect& area) { std::vector<sf::Vector2i>
// Check if width and height are power of two. Generator::createMinimalSpanningTree(const sf::Vector2i& start,
assert(area.width && !(area.width & (area.width - 1))); const float limit) {
assert(area.height && !(area.height & (area.height - 1))); std::vector<sf::Vector2i> open;
std::vector<sf::Vector2i> selected;
array generatedTiles; open.push_back(start);
fill(generatedTiles, area, type::FLOOR); float totalWeight = 0.0f;
for (int x = area.left; x < area.left + area.width; x++) { while (totalWeight < limit) {
for (int y = area.top; y < area.top + area.height; y++) { sf::Vector2i current;
filterWalls(generatedTiles, x, y, 2, 1, 0); float minValue = std::numeric_limits<float>::max();
filterWalls(generatedTiles, x, y, 6, 1, 2); for (auto& o : open) {
filterWalls(generatedTiles, x, y, 10, 1, 4); if (mTileNoise.getNoise(o.x, o.y) + 1.0f < minValue) {
} current = o;
} minValue = mTileNoise.getNoise(o.x, o.y) + 1.0f;
}
for (int x = area.left; x < area.left + area.width; x++) }
for (int y = area.top; y < area.top + area.height; y++) { std::remove(open.begin(), open.end(), current);
// Merge map that we just generated with stored map. selected.push_back(current);
mTiles[x][y] = generatedTiles[x][y]; totalWeight += minValue;
// Actually generate physical tiles.
mWorld.insert(std::shared_ptr<Sprite>( auto insertOnlyNew = [&open, &selected](const sf::Vector2i& v) {
new Tile(generatedTiles.at(x).at(y), x, y))); if (std::find(open.begin(), open.end(), v) == open.end()
} && std::find(selected.begin(), selected.end(), v) == selected.end())
open.push_back(v);
generateAreas(area); };
mPathfinder.generatePortals(); insertOnlyNew(sf::Vector2i(current.x + 1, current.y));
} insertOnlyNew(sf::Vector2i(current.x, current.y + 1));
insertOnlyNew(sf::Vector2i(current.x - 1, current.y));
/** insertOnlyNew(sf::Vector2i(current.x, current.y - 1));
* Returns coordinates where enemies should spawn. }
*/ return selected;
std::vector<sf::Vector2f> }
Generator::getEnemySpawns(const sf::IntRect& area) {
auto compare = [](const sf::Vector2f& a, const sf::Vector2f& b) { /**
return a.x < b.x || (a.x == b.x && a.y < b.y); * Fill world with procedurally generated tiles.
}; *
std::set<sf::Vector2f, decltype(compare)> ret(compare); * This is done by generating random (simplex) noise, with a value mapped
for (int x = area.left; x < area.left + area.width; x++) { * to every integer point in area, selecting the lowest value point as start,
for (int y = area.top; y < area.top + area.height; y++) { * and building a minimum spanning tree from there.
if (mCharacterNoise.getNoise(x, y) < -0.85f) { *
sf::Vector2i tilePosition = findClosestFloor(sf::Vector2i(x, y)); * @param area Size and position of area to generate tiles for. Width and
ret.insert(sf::Vector2f(tilePosition.x * Tile::TILE_SIZE.x, * height must each be a power of two.
tilePosition.y * Tile::TILE_SIZE.y)); */
} void
} Generator::generateTiles(const sf::IntRect& area) {
} // Width and height must be a power of two.
return std::vector<sf::Vector2f>(ret.begin(), ret.end()); assert(area.width && !(area.width & (area.width - 1)));
} assert(area.height && !(area.height & (area.height - 1)));
/** sf::Vector2i start;
* Fills a rectangular area with the specified value. float minValue = std::numeric_limits<float>::max();
*
* @param[in,out] Array to set values to. // Find lowest value for tree start.
* @param area The area to fill. for (int x = area.left; x < area.left + area.width; x++)
* @param value The value to set. for (int y = area.top; y < area.top + area.height; y++) {
*/ if (mTileNoise.getNoise(x, y) + 1.0f < minValue) {
void start = sf::Vector2i(x, y);
Generator::fill(array& tiles, const sf::IntRect& area, Tile::Type value) { minValue = mTileNoise.getNoise(x, y) + 1.0f;
for (int x = area.left; x < area.left + area.width; x++) { }
for (int y = area.top; y < area.top + area.height; y++) }
tiles[x][y] = value;
} std::vector<sf::Vector2i> selected = createMinimalSpanningTree(start, 12.0f);
}
// For rooms, take minimum bounding box of spanning tree.
/**
* Counts and returns the number of walls within the area. int left = start.x;
* int right = start.x;
* @param area The area to count in. int down = start.y;
*/ int up = start.y;
int
Generator::countWalls(const sf::IntRect& area) { for (auto& s : selected) {
int count = 0; if (s.x < left) left = s.x;
for (int x = area.left; x < area.left + area.width; x++) { if (s.x > right) right = s.x;
for (int y = area.top; y < area.top + area.height; y++) if (s.y < down) down = s.y;
count += (int) (getTileType(mTileNoise.getNoise(x, y)) == if (s.y > up) up = s.y;
type::WALL); }
}
return count; // Merge new map into stored map and create tile sprites.
} for (int x = area.left; x < area.left + area.width; x++)
for (int y = area.top; y < area.top + area.height; y++) {
/** Tile::Type type = ((x >= left && x < right && y >= down && y < up)
* Finds rectangles of specific size with mTileNoise and || (mTiles[x][y] == Tile::Type::FLOOR))
* puts them into vector out. ? Tile::Type::FLOOR
* : Tile::Type::WALL;
* @param[in,out] tiles Tiles to be placed. Does not explicitly set floor values mTiles[x][y] = type;
* (keeps previous values). mWorld.insert(std::shared_ptr<Sprite>(new Tile(type, x, y)));
* @param x Position to check from (top left corner for rectangle). }
* @param y Position to check from (top left corner for rectangle). generateAreas(area);
* @param longside Length of the longer side of the rectangle. mPathfinder.generatePortals();
* @param shortside Length of the shorter side of the rectangle. }
* @param subtract Still accepts rectangle if at least this amount of
* tiles is not walls (tilecount >= longside * shortside - subtract). /**
*/ * Returns coordinates where enemies should spawn.
void *
Generator::filterWalls(array& tiles, int x, int y, int longside, * @param area Area for which enemy spawns should be returned.
int shortside, int subtract) { */
// Filter in horizontal direction. std::vector<sf::Vector2f>
if (countWalls(sf::IntRect(x, y, longside, shortside)) >= Generator::getEnemySpawns(const sf::IntRect& area) {
shortside * longside - subtract) auto compare = [](const sf::Vector2f& a, const sf::Vector2f& b) {
fill(tiles, sf::IntRect(x, y, longside, shortside), type::WALL); return a.x < b.x || (a.x == b.x && a.y < b.y);
// Filter in vertical direction. };
if (countWalls(sf::IntRect(x, y, shortside, longside)) >= std::set<sf::Vector2f, decltype(compare)> ret(compare);
shortside * longside - subtract) for (int x = area.left; x < area.left + area.width; x++) {
fill(tiles, sf::IntRect(x, y, shortside, longside), type::WALL); for (int y = area.top; y < area.top + area.height; y++) {
} if (mCharacterNoise.getNoise(x, y) < -0.85f) {
sf::Vector2i tilePosition = findClosestFloor(sf::Vector2i(x, y));
/** ret.insert(sf::Vector2f(tilePosition.x * Tile::TILE_SIZE.x,
* Inserts floor tiles into path finder, using a quadtree approach to group tilePosition.y * Tile::TILE_SIZE.y));
* tiles where possible. }
* }
* @param area The area to generate areas for. }
*/ return std::vector<sf::Vector2f>(ret.begin(), ret.end());
void }
Generator::generateAreas(const sf::IntRect& area) {
assert(area.width > 0 && area.height > 0); /**
* Inserts floor tiles into path finder, using a quadtree approach to group
int wallCount = 0; * tiles where possible.
for (int x = area.left; x < area.left + area.width; x++) *
for (int y = area.top; y < area.top + area.height; y++) * @param area The area to generate areas for.
wallCount += (int) (mTiles[x][y] == type::WALL); */
void
if (wallCount == 0) Generator::generateAreas(const sf::IntRect& area) {
mPathfinder.insertArea(sf::FloatRect(area.left, area.top, area.width, area.height)); assert(area.width > 0 && area.height > 0);
else if (wallCount == area.width * area.height)
return; int wallCount = 0;
else { for (int x = area.left; x < area.left + area.width; x++)
int halfWidth = area.width / 2; for (int y = area.top; y < area.top + area.height; y++)
int halfHeight = area.height / 2; wallCount += (int) (mTiles[x][y] == Tile::Type::WALL);
generateAreas(sf::IntRect(area.left,
area.top, halfWidth, halfHeight)); if (wallCount == 0)
generateAreas(sf::IntRect(area.left + halfWidth, mPathfinder.insertArea(sf::FloatRect(area.left, area.top, area.width, area.height));
area.top, halfWidth, halfHeight)); else if (wallCount == area.width * area.height)
generateAreas(sf::IntRect(area.left, return;
area.top + halfHeight, halfWidth, halfHeight)); else {
generateAreas(sf::IntRect(area.left + halfWidth, int halfWidth = area.width / 2;
area.top + halfHeight, halfWidth, halfHeight)); int halfHeight = area.height / 2;
} generateAreas(sf::IntRect(area.left,
} area.top, halfWidth, halfHeight));
generateAreas(sf::IntRect(area.left + halfWidth,
/** area.top, halfWidth, halfHeight));
* Defines if a perlin noise result value is converted to a wall or floor tile. generateAreas(sf::IntRect(area.left,
* area.top + halfHeight, halfWidth, halfHeight));
* @param value Perlin noise value within [-1, 1] generateAreas(sf::IntRect(area.left + halfWidth,
*/ area.top + halfHeight, halfWidth, halfHeight));
Generator::type }
Generator::getTileType(float value) { }
return (value < -0.2f)
? type::WALL /**
: type::FLOOR; * Returns a valid position (floor) for the player to spawn at.
} */
sf::Vector2f
/** Generator::getPlayerSpawn() const {
* Returns a valid position (floor) for the player to spawn at. sf::Vector2i spawn = findClosestFloor(sf::Vector2i(0, 0));
*/ return sf::Vector2f(spawn.x * Tile::TILE_SIZE.x,
sf::Vector2f spawn.y * Tile::TILE_SIZE.y);
Generator::getPlayerSpawn() const { }
sf::Vector2i spawn = findClosestFloor(sf::Vector2i(0, 0));
return sf::Vector2f(spawn.x * Tile::TILE_SIZE.x, /**
spawn.y * Tile::TILE_SIZE.y); * Finds the point array index closest to position which has a floor tile.
} *
* @warn Will fail if no floor tile has been generated yet.
/** * @position Point to start search for a floor tile from.
* Finds the point array index closest to position which has a floor tile. */
* sf::Vector2i
* @warn Will fail if no floor tile has been generated yet. Generator::findClosestFloor(const sf::Vector2i& position) const {
* @position Point to start search for a floor tile from. auto compare = [](const sf::Vector2i& a, const sf::Vector2i& b) {
*/ return a.x < b.x || (a.x == b.x && a.y < b.y);
sf::Vector2i };
Generator::findClosestFloor(const sf::Vector2i& position) const { std::map<sf::Vector2i, float, decltype(compare)> open(compare);
auto compare = [](const sf::Vector2i& a, const sf::Vector2i& b) { std::set<sf::Vector2i, decltype(compare)> closed(compare);
return a.x < b.x || (a.x == b.x && a.y < b.y); sf::Vector2i start = position;
}; auto makePair = [&start](const sf::Vector2i& point) {
std::map<sf::Vector2i, float, decltype(compare)> open(compare); return std::make_pair(point, thor::length(sf::Vector2f(point - start)));
std::set<sf::Vector2i, decltype(compare)> closed(compare); };
sf::Vector2i start = position;
auto makePair = [&start](const sf::Vector2i& point) { open.insert(makePair(start));
return std::make_pair(point, thor::length(sf::Vector2f(point - start))); while (!open.empty()) {
}; auto intComp = [](const std::pair<sf::Vector2i, float>& left,
const std::pair<sf::Vector2i, float>& right) {
open.insert(makePair(start)); return left.second < right.second;
while (!open.empty()) { };
auto intComp = [](const std::pair<sf::Vector2i, float>& left, sf::Vector2i current = std::min_element(open.begin(), open.end(), intComp)->first;
const std::pair<sf::Vector2i, float>& right) { open.erase(current);
return left.second < right.second; closed.insert(current);
}; if (mTiles.count(current.x) != 0 &&
sf::Vector2i current = std::min_element(open.begin(), open.end(), intComp)->first; mTiles.at(current.x).count(current.y) != 0 &&
open.erase(current); mTiles.at(current.x).at(current.y) == Tile::Type::FLOOR) {
closed.insert(current); return current;
if (mTiles.count(current.x) != 0 && }
mTiles.at(current.x).count(current.y) != 0 && else {
mTiles.at(current.x).at(current.y) == type::FLOOR) { if (closed.find(sf::Vector2i(current.x + 1, current.y)) == closed.end())
return current; open.insert(makePair(sf::Vector2i(current.x + 1, current.y)));
} if (closed.find(sf::Vector2i(current.x, current.y + 1)) == closed.end())
else { open.insert(makePair(sf::Vector2i(current.x, current.y + 1)));
if (closed.find(sf::Vector2i(current.x + 1, current.y)) == closed.end()) if (closed.find(sf::Vector2i(current.x - 1, current.y)) == closed.end())
open.insert(makePair(sf::Vector2i(current.x + 1, current.y))); open.insert(makePair(sf::Vector2i(current.x - 1, current.y)));
if (closed.find(sf::Vector2i(current.x, current.y + 1)) == closed.end()) if (closed.find(sf::Vector2i(current.x, current.y - 1)) == closed.end())
open.insert(makePair(sf::Vector2i(current.x, current.y + 1))); open.insert(makePair(sf::Vector2i(current.x, current.y - 1)));
if (closed.find(sf::Vector2i(current.x - 1, current.y)) == closed.end()) }
open.insert(makePair(sf::Vector2i(current.x - 1, current.y))); }
if (closed.find(sf::Vector2i(current.x, current.y - 1)) == closed.end()) // No floor tile found in the entire world.
open.insert(makePair(sf::Vector2i(current.x, current.y - 1))); assert(false);
} return sf::Vector2i();
} }
// No floor tile found in the entire world.
assert(false);
return sf::Vector2i();
}

View file

@ -1,60 +1,55 @@
/* /*
* Generator.h * Generator.h
* *
* Created on: 07.04.2013 * Created on: 07.04.2013
* Author: Felix * Author: Felix
*/ */
#ifndef DG_GENERATOR_H_ #ifndef DG_GENERATOR_H_
#define DG_GENERATOR_H_ #define DG_GENERATOR_H_
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
#include "../sprites/Tile.h" #include "../sprites/Tile.h"
#include "SimplexNoise.h" #include "SimplexNoise.h"
class World; class World;
class Pathfinder; class Pathfinder;
/** /**
* Procedurally generates tiles, chooses player and enemy spawn positions. * Procedurally generates tiles, chooses player and enemy spawn positions.
*/ */
class Generator { class Generator {
public: public:
explicit Generator(World& world, Pathfinder& pathfinder); explicit Generator(World& world, Pathfinder& pathfinder);
void generateCurrentAreaIfNeeded(const sf::Vector2f& position); void generateCurrentAreaIfNeeded(const sf::Vector2f& position);
sf::Vector2f getPlayerSpawn() const; sf::Vector2f getPlayerSpawn() const;
std::vector<sf::Vector2f> getEnemySpawns(const sf::IntRect& area); std::vector<sf::Vector2f> getEnemySpawns(const sf::IntRect& area);
private: private:
typedef Tile::Type type; typedef std::map<int, std::map<int, Tile::Type> > array;
typedef std::map<int, std::map<int, type> > array;
private:
private: void generateAreas(const sf::IntRect& area);
void generateAreas(const sf::IntRect& area); void generateTiles(const sf::IntRect& area);
void generateTiles(const sf::IntRect& area); sf::Vector2i findClosestFloor(const sf::Vector2i& position) const;
sf::Vector2i findClosestFloor(const sf::Vector2i& position) const; std::vector<sf::Vector2i> createMinimalSpanningTree(
const sf::Vector2i& start, const float limit);
static void fill(array& tiles, const sf::IntRect& area, type value);
void filterWalls(array& tiles, int x, int y, int longside, private:
int shortside, int subtract); static const int GENERATE_AREA_SIZE;
int countWalls(const sf::IntRect& area); static const float GENERATE_AREA_RANGE;
static type getTileType(float value);
World& mWorld;
private: Pathfinder& mPathfinder;
static const int GENERATE_AREA_SIZE; /// Contains values of all tiles that have yet been generated.
static const float GENERATE_AREA_RANGE; array mTiles;
/// Stores where tiles have already been generated.
World& mWorld; std::map<int, std::map<int, bool> > mGenerated;
Pathfinder& mPathfinder; /// Perlin noise used for tile generation.
/// Contains values of all tiles that have yet been generated. SimplexNoise mTileNoise;
array mTiles; /// Perlin noise used for character placement.
/// Stores where tiles have already been generated. SimplexNoise mCharacterNoise;
std::map<int, std::map<int, bool> > mGenerated; };
/// Perlin noise used for tile generation.
SimplexNoise mTileNoise; #endif /* DG_GENERATOR_H_ */
/// Perlin noise used for character placement.
SimplexNoise mCharacterNoise;
};
#endif /* DG_GENERATOR_H_ */

View file

@ -16,8 +16,8 @@
class Tile : public Rectangle { class Tile : public Rectangle {
public: public:
enum class Type : char { enum class Type : char {
FLOOR, WALL,
WALL FLOOR
}; };
public: public: