Now using a simple collision map to track entities and then determine if they're near the player for attacking.

main
Zed A. Shaw 11 months ago
parent 743f906bc7
commit ec1ed23c52
  1. 35
      collider.cpp
  2. 11
      collider.hpp
  3. 6
      gui.cpp
  4. 44
      systems.cpp
  5. 1
      systems.hpp
  6. 25
      tests/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) {
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;
// 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);
if (it != table.end()) {
result.insert(result.end(), it->second);
}
}
// 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);

@ -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;
};

@ -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() {

@ -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));
if(dx <= 1 && dy <= 1) {
if(player_combat.hp > -1) {
int dmg = Random::uniform<int>(1, combat.damage);
player_combat.hp -= dmg;
log.log(format("HIT! You took {} damage.", dmg));
}
}
// this is guaranteed to not return the given position
auto [found, nearby] = collider.neighbors(player_position.location);
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);
}
}