Now using a simple collision map to track entities and then determine if they're near the player for attacking.
This commit is contained in:
parent
743f906bc7
commit
ec1ed23c52
6 changed files with 72 additions and 48 deletions
33
collider.cpp
33
collider.cpp
|
@ -15,23 +15,32 @@ void SpatialHashTable::move(Point from, Point to, Entity ent) {
|
|||
insert(to, ent);
|
||||
}
|
||||
|
||||
bool SpatialHashTable::occupied(Point at) {
|
||||
return table[at];
|
||||
bool SpatialHashTable::occupied(Point at) const {
|
||||
return table.contains(at);
|
||||
}
|
||||
|
||||
std::tuple<bool, FoundList> SpatialHashTable::neighbors(Point cell) {
|
||||
FoundList result;
|
||||
|
||||
// Check the current cell and its 8 neighbors
|
||||
// BUG: this can sign underflow, assert it won't
|
||||
for (size_t x = cell.x - 1; x <= cell.x + 1; x++) {
|
||||
for (size_t y = cell.y - 1; y <= cell.y + 1; y++) {
|
||||
Point neighborCell = {x, y};
|
||||
auto it = table.find(neighborCell);
|
||||
inline void find_neighbor(const PointEntityMap &table, FoundList &result, Point at) {
|
||||
auto it = table.find(at);
|
||||
if (it != table.end()) {
|
||||
result.insert(result.end(), it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<bool, FoundList> SpatialHashTable::neighbors(Point cell, bool diag) const {
|
||||
FoundList result;
|
||||
|
||||
// just unroll the loop since we only check four directions
|
||||
// this also solves the problem that it was detecting that the cell was automatically included as a "neighbor" but it's not
|
||||
find_neighbor(table, result, {cell.x, cell.y+1}); // north
|
||||
find_neighbor(table, result, {cell.x, cell.y-1}); // south
|
||||
find_neighbor(table, result, {cell.x+1, cell.y}); // east
|
||||
find_neighbor(table, result, {cell.x-1, cell.y}); // west
|
||||
find_neighbor(table, result, {cell.x+1, cell.y-1}); // south east
|
||||
|
||||
if(diag) {
|
||||
find_neighbor(table, result, {cell.x-1, cell.y-1}); // south west
|
||||
find_neighbor(table, result, {cell.x+1, cell.y+1}); // north east
|
||||
find_neighbor(table, result, {cell.x-1, cell.y+1}); // north west
|
||||
}
|
||||
|
||||
return std::tuple(!result.empty(), result);
|
||||
|
|
11
collider.hpp
11
collider.hpp
|
@ -12,21 +12,18 @@ struct PointHash {
|
|||
};
|
||||
|
||||
typedef std::vector<DinkyECS::Entity> FoundList;
|
||||
typedef std::unordered_map<Point, DinkyECS::Entity, PointHash> PointEntityMap;
|
||||
|
||||
class SpatialHashTable {
|
||||
public:
|
||||
SpatialHashTable() {}
|
||||
|
||||
// disable copying, I think?
|
||||
SpatialHashTable(SpatialHashTable &other) = delete;
|
||||
|
||||
void insert(Point pos, DinkyECS::Entity obj);
|
||||
void move(Point from, Point to, DinkyECS::Entity ent);
|
||||
void remove(Point pos);
|
||||
bool occupied(Point pos);
|
||||
|
||||
std::tuple<bool, FoundList> neighbors(Point position);
|
||||
bool occupied(Point pos) const;
|
||||
std::tuple<bool, FoundList> neighbors(Point position, bool diag=false) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<Point, DinkyECS::Entity, PointHash> table;
|
||||
PointEntityMap table;
|
||||
};
|
||||
|
|
6
gui.cpp
6
gui.cpp
|
@ -22,6 +22,7 @@
|
|||
#include "rand.hpp"
|
||||
#include "components.hpp"
|
||||
#include "systems.hpp"
|
||||
#include "collider.hpp"
|
||||
|
||||
using std::string;
|
||||
using namespace fmt;
|
||||
|
@ -277,6 +278,9 @@ void GUI::configure_world() {
|
|||
ActionLog log{{"Welcome to the game!"}};
|
||||
$world.set<ActionLog>(log);
|
||||
|
||||
SpatialHashTable collider;
|
||||
$world.set<SpatialHashTable>(collider);
|
||||
|
||||
$world.assign<Position>(player.entity, {$game_map.place_entity(0)});
|
||||
$world.assign<Motion>(player.entity, {0, 0});
|
||||
$world.assign<Combat>(player.entity, {100, 10});
|
||||
|
@ -298,6 +302,8 @@ void GUI::configure_world() {
|
|||
$world.assign<Position>(gold, {$game_map.place_entity($game_map.room_count() - 1)});
|
||||
$world.assign<Treasure>(gold, {100});
|
||||
$world.assign<Tile>(gold, {"$"});
|
||||
|
||||
System::init_positions($world);
|
||||
}
|
||||
|
||||
void GUI::render_scene() {
|
||||
|
|
34
systems.cpp
34
systems.cpp
|
@ -3,6 +3,7 @@
|
|||
#include <string>
|
||||
#include <cmath>
|
||||
#include "rand.hpp"
|
||||
#include "collider.hpp"
|
||||
|
||||
using std::string;
|
||||
using namespace fmt;
|
||||
|
@ -24,8 +25,17 @@ void System::enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player
|
|||
game_map.clear_target(player_position.location);
|
||||
}
|
||||
|
||||
void System::init_positions(DinkyECS::World &world) {
|
||||
auto &collider = world.get<SpatialHashTable>();
|
||||
|
||||
world.system<Position>([&](const auto &ent, auto &pos) {
|
||||
collider.insert(pos.location, ent);
|
||||
});
|
||||
}
|
||||
|
||||
void System::motion(DinkyECS::World &world, Map &game_map) {
|
||||
auto &collider = world.get<SpatialHashTable>();
|
||||
|
||||
world.system<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
|
||||
// don't process entities that don't move
|
||||
if(motion.dx != 0 || motion.dy != 0) {
|
||||
|
@ -36,8 +46,10 @@ void System::motion(DinkyECS::World &world, Map &game_map) {
|
|||
motion = {0,0}; // clear it after getting it
|
||||
|
||||
if(game_map.inmap(move_to.x, move_to.y) &&
|
||||
!game_map.iswall(move_to.x,move_to.y))
|
||||
!game_map.iswall(move_to.x, move_to.y) &&
|
||||
!collider.occupied(move_to))
|
||||
{
|
||||
collider.move(position.location, move_to, ent);
|
||||
position.location = move_to;
|
||||
}
|
||||
}
|
||||
|
@ -46,25 +58,27 @@ void System::motion(DinkyECS::World &world, Map &game_map) {
|
|||
|
||||
|
||||
void System::combat(DinkyECS::World &world, Player &player) {
|
||||
const auto& collider = world.get<SpatialHashTable>();
|
||||
const auto& player_position = world.component<Position>(player.entity);
|
||||
auto& player_combat = world.component<Combat>(player.entity);
|
||||
auto& log = world.get<ActionLog>();
|
||||
|
||||
world.system<Position, Combat>([&](const auto &ent, auto &pos, auto &combat) {
|
||||
if(ent != player.entity) {
|
||||
int dx = std::abs(int(pos.location.x) - int(player_position.location.x));
|
||||
int dy = std::abs(int(pos.location.y) - int(player_position.location.y));
|
||||
// this is guaranteed to not return the given position
|
||||
auto [found, nearby] = collider.neighbors(player_position.location);
|
||||
|
||||
if(dx <= 1 && dy <= 1) {
|
||||
if(player_combat.hp > -1) {
|
||||
int dmg = Random::uniform<int>(1, combat.damage);
|
||||
if(found) {
|
||||
for(auto entity : nearby) {
|
||||
int attack = Random::uniform<int>(0,1);
|
||||
if(attack) {
|
||||
const auto& enemy_dmg = world.component<Combat>(entity);
|
||||
int dmg = Random::uniform<int>(1, enemy_dmg.damage);
|
||||
player_combat.hp -= dmg;
|
||||
|
||||
log.log(format("HIT! You took {} damage.", dmg));
|
||||
} else {
|
||||
log.log("You dodged! Run!");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
void System::draw_entities(DinkyECS::World &world, Map &game_map, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y) {
|
||||
|
|
|
@ -12,4 +12,5 @@ namespace System {
|
|||
void enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player);
|
||||
void draw_map(DinkyECS::World &world, Map &game_map, ftxui::Canvas &canvas, size_t view_x, size_t view_y);
|
||||
void draw_entities(DinkyECS::World &world, Map &game_map, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y);
|
||||
void init_positions(DinkyECS::World &world);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ TEST_CASE("confirm basic collision operations", "[collision]") {
|
|||
}
|
||||
|
||||
{ // found
|
||||
auto [found, nearby] = coltable.neighbors({10,10});
|
||||
auto [found, nearby] = coltable.neighbors({10,10}, true);
|
||||
|
||||
REQUIRE(found);
|
||||
REQUIRE(nearby[0] == player);
|
||||
|
@ -31,7 +31,7 @@ TEST_CASE("confirm basic collision operations", "[collision]") {
|
|||
|
||||
{ // removed
|
||||
coltable.remove({11,11});
|
||||
auto [found, nearby] = coltable.neighbors({10,10});
|
||||
auto [found, nearby] = coltable.neighbors({10,10}, true);
|
||||
REQUIRE(!found);
|
||||
REQUIRE(nearby.empty());
|
||||
}
|
||||
|
@ -40,13 +40,13 @@ TEST_CASE("confirm basic collision operations", "[collision]") {
|
|||
|
||||
{ // moving
|
||||
coltable.move({11,11}, {12, 12}, player);
|
||||
auto [found, nearby] = coltable.neighbors({10,10});
|
||||
auto [found, nearby] = coltable.neighbors({10,10}, true);
|
||||
REQUIRE(!found);
|
||||
REQUIRE(nearby.empty());
|
||||
}
|
||||
|
||||
{ // find it after move
|
||||
auto [found, nearby] = coltable.neighbors({11,11});
|
||||
auto [found, nearby] = coltable.neighbors({11,11}, true);
|
||||
REQUIRE(found);
|
||||
REQUIRE(nearby[0] == player);
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ TEST_CASE("confirm basic collision operations", "[collision]") {
|
|||
}
|
||||
|
||||
|
||||
|
||||
TEST_CASE("confirm multiple entities moving", "[collision]") {
|
||||
DinkyECS::World world;
|
||||
Entity player = world.entity();
|
||||
|
@ -74,22 +73,20 @@ TEST_CASE("confirm multiple entities moving", "[collision]") {
|
|||
coltable.insert({21,21}, e1);
|
||||
|
||||
{ // find e3 and e2
|
||||
auto [found, nearby] = coltable.neighbors({11, 11});
|
||||
auto [found, nearby] = coltable.neighbors({11, 11}, true);
|
||||
REQUIRE(found);
|
||||
REQUIRE(nearby.size() == 3);
|
||||
REQUIRE(nearby.size() == 2);
|
||||
// BUG: replace this with std::find/std::search
|
||||
REQUIRE(nearby[0] == e2);
|
||||
REQUIRE(nearby[1] == e3);
|
||||
REQUIRE(nearby[2] == player);
|
||||
REQUIRE(nearby[0] == e3);
|
||||
REQUIRE(nearby[1] == e2);
|
||||
}
|
||||
|
||||
coltable.move({11,11}, {20,20}, player);
|
||||
{ // should only find the e1
|
||||
auto [found, nearby] = coltable.neighbors({20,20});
|
||||
auto [found, nearby] = coltable.neighbors({20,20}, true);
|
||||
REQUIRE(found);
|
||||
REQUIRE(nearby.size() == 2);
|
||||
REQUIRE(nearby.size() == 1);
|
||||
// BUG: replace this with std::find/std::search
|
||||
REQUIRE(nearby[0] == player);
|
||||
REQUIRE(nearby[1] == e1);
|
||||
REQUIRE(nearby[0] == e1);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue