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

main
Zed A. Shaw 4 weeks 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);
}
}

Loading…
Cancel
Save