Rooms are now automatically connected by pathfinding algorithm.
This commit is contained in:
parent
9db73a7b93
commit
56da38792f
4 changed files with 125 additions and 28 deletions
|
@ -67,9 +67,8 @@ Generator::generateCurrentAreaIfNeeded(const Vector2f& playerPosition) {
|
||||||
generateTiles(area);
|
generateTiles(area);
|
||||||
for (const auto& enemyPosition : getEnemySpawns(area)) {
|
for (const auto& enemyPosition : getEnemySpawns(area)) {
|
||||||
float distance = thor::length(enemyPosition - playerPosition);
|
float distance = thor::length(enemyPosition - playerPosition);
|
||||||
if (distance > Character::VISION_DISTANCE) {
|
if (distance > Character::VISION_DISTANCE)
|
||||||
mWorld.insertCharacter(std::shared_ptr<Enemy>(new Enemy(mWorld, mPathfinder, enemyPosition)));
|
mWorld.insertCharacter(std::shared_ptr<Enemy>(new Enemy(mWorld, mPathfinder, enemyPosition)));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) {
|
if (mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) {
|
||||||
|
@ -88,14 +87,12 @@ Generator::generateCurrentAreaIfNeeded(const Vector2f& playerPosition) {
|
||||||
/**
|
/**
|
||||||
* Generates a minimum spanning tree on mTileNoise, starting from start with
|
* Generates a minimum spanning tree on mTileNoise, starting from start with
|
||||||
* a maximum total node weight of limit.
|
* a maximum total node weight of limit.
|
||||||
*
|
*/
|
||||||
* FIXME: Some nodes are selected more than once.
|
|
||||||
*/
|
|
||||||
std::vector<Vector2i>
|
std::vector<Vector2i>
|
||||||
Generator::createMinimalSpanningTree(const Vector2i& start,
|
Generator::createMinimalSpanningTree(const Vector2i& start,
|
||||||
const float limit) {
|
const float limit) {
|
||||||
std::vector<Vector2i> open;
|
std::vector<Vector2i> open;
|
||||||
std::vector<Vector2i> selected;
|
std::vector<Vector2i> selected;
|
||||||
open.push_back(start);
|
open.push_back(start);
|
||||||
float totalWeight = 0.0f;
|
float totalWeight = 0.0f;
|
||||||
|
|
||||||
|
@ -103,9 +100,9 @@ Generator::createMinimalSpanningTree(const Vector2i& start,
|
||||||
Vector2i current;
|
Vector2i current;
|
||||||
float minValue = std::numeric_limits<float>::max();
|
float minValue = std::numeric_limits<float>::max();
|
||||||
for (auto& o : open) {
|
for (auto& o : open) {
|
||||||
if (mTileNoise.getNoise(o.x, o.y) + 1.0f < minValue) {
|
if (mTileNoise.getNoise(o) + 1.0f < minValue) {
|
||||||
current = o;
|
current = o;
|
||||||
minValue = mTileNoise.getNoise(o.x, o.y) + 1.0f;
|
minValue = mTileNoise.getNoise(o) + 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::remove(open.begin(), open.end(), current);
|
std::remove(open.begin(), open.end(), current);
|
||||||
|
@ -125,6 +122,79 @@ Generator::createMinimalSpanningTree(const Vector2i& start,
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates paths that connect different rooms.
|
||||||
|
*
|
||||||
|
* Using basically Dijkstra on infinite graph/A* without destination node.
|
||||||
|
*
|
||||||
|
* @param start Tile to start path generation from (must be floor).
|
||||||
|
* @param limit Maximum weight of each path.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Generator::connectRooms(const Vector2i& start, float limit) {
|
||||||
|
std::set<Vector2i> open;
|
||||||
|
std::set<Vector2i> closed;
|
||||||
|
std::map<Vector2i, Vector2i> previous;
|
||||||
|
std::set<Vector2i> destinations;
|
||||||
|
std::map<Vector2i, float> distance;
|
||||||
|
// Compares vectors using distance values.
|
||||||
|
auto comp = [&distance](const Vector2i& lhs, const Vector2i& rhs) {
|
||||||
|
return distance[lhs] < distance[rhs];
|
||||||
|
};
|
||||||
|
auto process = [&open, &closed, &previous, &distance, this](const Vector2i& point, const Vector2i& current) {
|
||||||
|
// Update previous nodes if shorter path is found.
|
||||||
|
if (distance.count(point) == 0) {
|
||||||
|
distance[point] = distance[current] + mTileNoise.getNoise(point) + 1;
|
||||||
|
previous[point] = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert into open
|
||||||
|
if (closed.count(point) == 0)
|
||||||
|
open.insert(point);
|
||||||
|
};
|
||||||
|
|
||||||
|
open.insert(start);
|
||||||
|
distance[start] = 0;
|
||||||
|
while (!open.empty()) {
|
||||||
|
// Select node with lowest distance that is in open
|
||||||
|
Vector2i current = *std::min_element(open.begin(), open.end(), comp);
|
||||||
|
open.erase(current);
|
||||||
|
closed.insert(current);
|
||||||
|
// Take all floors right after wall tiles.
|
||||||
|
if (mTiles[previous[current].x][previous[current].y] == Tile::Type::WALL &&
|
||||||
|
mTiles[current.x][current.y] == Tile::Type::FLOOR) {
|
||||||
|
destinations.insert(current);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (distance.at(current) < limit) {
|
||||||
|
process(Vector2i(current.x + 1, current.y), current);
|
||||||
|
process(Vector2i(current.x, current.y + 1), current);
|
||||||
|
process(Vector2i(current.x - 1, current.y), current);
|
||||||
|
process(Vector2i(current.x, current.y - 1), current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// take min length paths and set tiles
|
||||||
|
float totalValue = 0.0f;
|
||||||
|
while (totalValue < limit && !destinations.empty()) {
|
||||||
|
std::vector<Vector2i> path;
|
||||||
|
float pathValue = 0;
|
||||||
|
Vector2i current = *destinations.begin();
|
||||||
|
destinations.erase(destinations.begin());
|
||||||
|
while (current != start) {
|
||||||
|
path.push_back(previous[current]);
|
||||||
|
pathValue += mTileNoise.getNoise(current);
|
||||||
|
current = previous[current];
|
||||||
|
};
|
||||||
|
path.push_back(start);
|
||||||
|
mPaths.push_back(path);
|
||||||
|
for (const auto& p : path) {
|
||||||
|
mTiles[p.x][p.y] = Tile::Type::FLOOR;
|
||||||
|
Tile::setTile(p, Tile::Type::FLOOR, mWorld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill world with procedurally generated tiles.
|
* Fill world with procedurally generated tiles.
|
||||||
*
|
*
|
||||||
|
@ -170,15 +240,16 @@ Generator::generateTiles(const sf::IntRect& area) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge new map into stored map and create tile sprites.
|
// Merge new map into stored map and create tile sprites.
|
||||||
|
for (int x = left; x < right; x++)
|
||||||
|
for (int y = down; y < up; y++)
|
||||||
|
mTiles[x][y] = Tile::Type::FLOOR;
|
||||||
|
connectRooms(start, 5.0f);
|
||||||
|
|
||||||
for (int x = area.left; x < area.left + area.width; x++)
|
for (int x = area.left; x < area.left + area.width; x++)
|
||||||
for (int y = area.top; y < area.top + area.height; y++) {
|
for (int y = area.top; y < area.top + area.height; y++)
|
||||||
Tile::Type type = ((x >= left && x < right && y >= down && y < up)
|
mWorld.insert(std::shared_ptr<Sprite>(
|
||||||
|| (mTiles[x][y] == Tile::Type::FLOOR))
|
new Tile(Vector2i(x, y), mTiles[x][y])));
|
||||||
? Tile::Type::FLOOR
|
|
||||||
: Tile::Type::WALL;
|
|
||||||
mTiles[x][y] = type;
|
|
||||||
mWorld.insert(std::shared_ptr<Sprite>(new Tile(type, x, y)));
|
|
||||||
}
|
|
||||||
generateAreas(area);
|
generateAreas(area);
|
||||||
mPathfinder.generatePortals();
|
mPathfinder.generatePortals();
|
||||||
}
|
}
|
||||||
|
@ -254,23 +325,18 @@ Generator::getPlayerSpawn() const {
|
||||||
*
|
*
|
||||||
* @warn Will fail if no floor tile has been generated yet.
|
* @warn Will fail if no floor tile has been generated yet.
|
||||||
* @position Point to start search for a floor tile from.
|
* @position Point to start search for a floor tile from.
|
||||||
*/
|
*/
|
||||||
Vector2i
|
Vector2i
|
||||||
Generator::findClosestFloor(const Vector2i& position) const {
|
Generator::findClosestFloor(const Vector2i& start) const {
|
||||||
std::map<Vector2i, float> open;
|
std::map<Vector2i, float> open;
|
||||||
std::set<Vector2i> closed;
|
std::set<Vector2i> closed;
|
||||||
Vector2i start = position;
|
|
||||||
auto makePair = [&start](const Vector2i& point) {
|
auto makePair = [&start](const Vector2i& point) {
|
||||||
return std::make_pair(point, thor::length(Vector2f(point - start)));
|
return std::make_pair(point, thor::length(Vector2f(point - start)));
|
||||||
};
|
};
|
||||||
|
|
||||||
open.insert(makePair(start));
|
open.insert(makePair(start));
|
||||||
while (!open.empty()) {
|
while (!open.empty()) {
|
||||||
auto intComp = [](const std::pair<Vector2i, float>& left,
|
Vector2i current = std::min_element(open.begin(), open.end())->first;
|
||||||
const std::pair<Vector2i, float>& right) {
|
|
||||||
return left.second < right.second;
|
|
||||||
};
|
|
||||||
Vector2i current = std::min_element(open.begin(), open.end(), intComp)->first;
|
|
||||||
open.erase(current);
|
open.erase(current);
|
||||||
closed.insert(current);
|
closed.insert(current);
|
||||||
if (mTiles.count(current.x) != 0 &&
|
if (mTiles.count(current.x) != 0 &&
|
||||||
|
@ -293,3 +359,22 @@ Generator::findClosestFloor(const Vector2i& position) const {
|
||||||
assert(false);
|
assert(false);
|
||||||
return Vector2i();
|
return Vector2i();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
/**
|
||||||
|
* Debug only: Draws paths generated by connectRooms.
|
||||||
|
*
|
||||||
|
* mPaths is only required for this function.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
Generator::draw(sf::RenderTarget& target, sf::RenderStates states) const {
|
||||||
|
for (auto& p : mPaths) {
|
||||||
|
for (auto&q : p) {
|
||||||
|
sf::RectangleShape rect(Vector2f(Tile::TILE_SIZE));
|
||||||
|
rect.setPosition(Vector2f(q.x * Tile::TILE_SIZE.x, q.y * Tile::TILE_SIZE.y) - Vector2f(Tile::TILE_SIZE / 2));
|
||||||
|
rect.setFillColor(sf::Color(150, 127, 0, 96));
|
||||||
|
target.draw(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* NDEBUG */
|
||||||
|
|
|
@ -20,7 +20,7 @@ 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 sf::Drawable {
|
||||||
public:
|
public:
|
||||||
explicit Generator(World& world, Pathfinder& pathfinder);
|
explicit Generator(World& world, Pathfinder& pathfinder);
|
||||||
void generateCurrentAreaIfNeeded(const Vector2f& position);
|
void generateCurrentAreaIfNeeded(const Vector2f& position);
|
||||||
|
@ -32,10 +32,12 @@ private:
|
||||||
|
|
||||||
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);
|
||||||
Vector2i findClosestFloor(const Vector2i& position) const;
|
Vector2i findClosestFloor(const Vector2i& start) const;
|
||||||
std::vector<Vector2i> createMinimalSpanningTree(
|
std::vector<Vector2i> createMinimalSpanningTree(
|
||||||
const Vector2i& start, const float limit);
|
const Vector2i& start, const float limit);
|
||||||
|
void connectRooms(const Vector2i& start, float limit);
|
||||||
|
void draw(sf::RenderTarget& target, sf::RenderStates states) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int GENERATE_AREA_SIZE;
|
static const int GENERATE_AREA_SIZE;
|
||||||
|
@ -51,6 +53,8 @@ private:
|
||||||
SimplexNoise mTileNoise;
|
SimplexNoise mTileNoise;
|
||||||
/// Perlin noise used for character placement.
|
/// Perlin noise used for character placement.
|
||||||
SimplexNoise mCharacterNoise;
|
SimplexNoise mCharacterNoise;
|
||||||
|
/// Used only for debug drawing.
|
||||||
|
std::vector<std::vector<Vector2i> > mPaths;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* DG_GENERATOR_H_ */
|
#endif /* DG_GENERATOR_H_ */
|
||||||
|
|
|
@ -35,6 +35,11 @@ SimplexNoise::getNoise(int x, int y) {
|
||||||
return mCache.at(x).at(y);
|
return mCache.at(x).at(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float
|
||||||
|
SimplexNoise::getNoise(const Vector2i& v) {
|
||||||
|
return getNoise(v.x, v.y);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Floor implementation that is faster than std implementation by
|
* Floor implementation that is faster than std implementation by
|
||||||
* ignoring some checks and does not consider some border conditions.
|
* ignoring some checks and does not consider some border conditions.
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
#include "../util/Vector.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caching simplex noise generator.
|
* Caching simplex noise generator.
|
||||||
*
|
*
|
||||||
|
@ -22,6 +24,7 @@ class SimplexNoise {
|
||||||
public:
|
public:
|
||||||
SimplexNoise();
|
SimplexNoise();
|
||||||
float getNoise(int x, int y);
|
float getNoise(int x, int y);
|
||||||
|
float getNoise(const Vector2i& v);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Reference in a new issue