DinkyECS is now controlling the game and can handle multiple enemies easily. Next is to clean this up so it's not just one gross pile of code in the gui.
This commit is contained in:
parent
86c98c43c2
commit
33327154ad
9 changed files with 195 additions and 149 deletions
|
@ -87,6 +87,12 @@ namespace DinkyECS {
|
|||
system<CompA, CompB>(cb);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename CompA>
|
||||
std::function<void()> runner(std::function<void(const Entity&, CompA&)> cb) {
|
||||
return [&]{
|
||||
system<CompA>(cb);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
37
entity.cpp
37
entity.cpp
|
@ -1,37 +0,0 @@
|
|||
#include "entity.hpp"
|
||||
|
||||
void Entity::move(Point loc) {
|
||||
location = loc;
|
||||
}
|
||||
|
||||
void Entity::event(EntityEvent ev) {
|
||||
switch($state) {
|
||||
FSM_STATE(EntityState, START, ev);
|
||||
FSM_STATE(EntityState, HUNTING, ev);
|
||||
FSM_STATE(EntityState, DEAD, ev);
|
||||
}
|
||||
}
|
||||
|
||||
void Entity::START(EntityEvent ev) {
|
||||
state(EntityState::HUNTING);
|
||||
}
|
||||
|
||||
void Entity::HUNTING(EntityEvent ev) {
|
||||
switch(ev) {
|
||||
case EntityEvent::HIT:
|
||||
hp -= damage;
|
||||
break;
|
||||
default:
|
||||
state(EntityState::HUNTING);
|
||||
}
|
||||
|
||||
if(hp <= 0) {
|
||||
state(EntityState::DEAD);
|
||||
} else {
|
||||
state(EntityState::HUNTING);
|
||||
}
|
||||
}
|
||||
|
||||
void Entity::DEAD(EntityEvent ev) {
|
||||
state(EntityState::DEAD);
|
||||
}
|
35
entity.hpp
35
entity.hpp
|
@ -1,35 +0,0 @@
|
|||
#pragma once
|
||||
#include "fsm.hpp"
|
||||
#include "map.hpp"
|
||||
|
||||
enum class EntityState {
|
||||
START, HUNTING, DEAD
|
||||
};
|
||||
|
||||
enum class EntityEvent {
|
||||
GO, HIT
|
||||
};
|
||||
|
||||
|
||||
struct Entity : public DeadSimpleFSM<EntityState, EntityEvent> {
|
||||
Point location{0,0};
|
||||
int hp = 20;
|
||||
int damage = 10;
|
||||
|
||||
Entity() {
|
||||
}
|
||||
|
||||
Entity(Point loc) : location(loc) {
|
||||
}
|
||||
|
||||
// disable copy
|
||||
Entity(Entity &e) = delete;
|
||||
|
||||
void move(Point loc);
|
||||
void event(EntityEvent ev);
|
||||
|
||||
// states
|
||||
void START(EntityEvent ev);
|
||||
void HUNTING(EntityEvent ev);
|
||||
void DEAD(EntityEvent ev);
|
||||
};
|
147
gui.cpp
147
gui.cpp
|
@ -25,6 +25,32 @@ using namespace fmt;
|
|||
using namespace std::chrono_literals;
|
||||
using namespace ftxui;
|
||||
|
||||
struct Player {
|
||||
DinkyECS::Entity entity;
|
||||
};
|
||||
|
||||
struct Position {
|
||||
Point location;
|
||||
};
|
||||
|
||||
struct Motion {
|
||||
int dx;
|
||||
int dy;
|
||||
};
|
||||
|
||||
struct Combat {
|
||||
int hp;
|
||||
int damage;
|
||||
};
|
||||
|
||||
struct Treasure {
|
||||
int amount;
|
||||
};
|
||||
|
||||
struct Tile {
|
||||
std::string chr = "!";
|
||||
};
|
||||
|
||||
std::array<sf::Color, 10> VALUES{
|
||||
sf::Color{1, 4, 2}, // black
|
||||
sf::Color{9, 29, 16}, // dark dark
|
||||
|
@ -69,22 +95,18 @@ GUI::GUI() : $game_map(GAME_MAP_X, GAME_MAP_Y),
|
|||
$map_text.setFillColor(color(Value::MID));
|
||||
|
||||
$game_map.generate();
|
||||
$player.location = $game_map.place_entity(0);
|
||||
$enemy.location = $game_map.place_entity(1);
|
||||
$goal = $game_map.place_entity($game_map.room_count() - 1);
|
||||
}
|
||||
|
||||
void GUI::create_renderer() {
|
||||
$map_view = Renderer([&] {
|
||||
auto player = $world.get<Player>();
|
||||
|
||||
$map_view = Renderer([&, player] {
|
||||
const auto& player_position = $world.component<Position>(player.entity);
|
||||
Matrix &walls = $game_map.walls();
|
||||
$game_map.set_target($player.location);
|
||||
$game_map.set_target(player_position.location);
|
||||
$game_map.make_paths();
|
||||
Matrix &paths = $game_map.paths();
|
||||
|
||||
if($player.in_state(EntityState::DEAD)) {
|
||||
$status_text = "DEAD!";
|
||||
}
|
||||
|
||||
for(size_t x = 0; x < walls[0].size(); ++x) {
|
||||
for(size_t y = 0; y < walls.size(); ++y) {
|
||||
string tile = walls[y][x] == 1 ? WALL_TILE : format("{}", paths[y][x]);
|
||||
|
@ -99,18 +121,19 @@ void GUI::create_renderer() {
|
|||
}
|
||||
}
|
||||
|
||||
$canvas.DrawText($enemy.location.x*2, $enemy.location.y*4, ENEMY_TILE);
|
||||
$canvas.DrawText($player.location.x*2, $player.location.y*4, PLAYER_TILE);
|
||||
$canvas.DrawText($goal.x*2, $goal.y*4, "$");
|
||||
$world.system<Position, Tile>([&](const auto &ent, auto &pos, auto &tile) {
|
||||
$canvas.DrawText(pos.location.x*2, pos.location.y*4, tile.chr);
|
||||
});
|
||||
|
||||
return canvas($canvas);
|
||||
});
|
||||
|
||||
$document = Renderer([&]{
|
||||
$document = Renderer([&, player]{
|
||||
const auto& player_combat = $world.component<Combat>(player.entity);
|
||||
return hbox({
|
||||
hflow(
|
||||
vbox(
|
||||
text(format("HP: {}", $player.hp)) | border,
|
||||
text(format("HP: {}", player_combat.hp)) | border,
|
||||
text($status_text) | border
|
||||
) | xflex_grow
|
||||
),
|
||||
|
@ -122,46 +145,61 @@ void GUI::create_renderer() {
|
|||
|
||||
void GUI::handle_events() {
|
||||
sf::Event event;
|
||||
auto player = $world.get<Player>();
|
||||
auto& player_motion = $world.component<Motion>(player.entity);
|
||||
|
||||
while($window.pollEvent(event)) {
|
||||
if(event.type == sf::Event::Closed) {
|
||||
$window.close();
|
||||
} else if(event.type == sf::Event::KeyPressed) {
|
||||
size_t x = $player.location.x;
|
||||
size_t y = $player.location.y;
|
||||
|
||||
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
|
||||
x -= 1;
|
||||
player_motion.dx = -1;
|
||||
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
|
||||
x += 1;
|
||||
player_motion.dx = 1;
|
||||
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
|
||||
y -= 1;
|
||||
player_motion.dy = -1;
|
||||
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) {
|
||||
y += 1;
|
||||
player_motion.dy = 1;
|
||||
}
|
||||
|
||||
if($game_map.inmap(x,y) && !$game_map.iswall(x,y)) {
|
||||
$game_map.clear_target($player.location);
|
||||
$player.move({x, y});
|
||||
} else {
|
||||
$shake_it = true;
|
||||
$hit_sound.play();
|
||||
}
|
||||
// COMPOSE system? You create a bunch of callbacks and then combine them into
|
||||
// a single run over the data?
|
||||
|
||||
// move $enemy here
|
||||
// BUG: when the enemy has no path it goes through walls, which means
|
||||
// this neighbors function is not working right. Probably updating
|
||||
// enemy.location in an out parameter isn't the best idea.
|
||||
bool found = $game_map.neighbors($enemy.location, true);
|
||||
if(!found) {
|
||||
$status_text = "ENEMY STUCK!";
|
||||
// move enemies system
|
||||
$world.system<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
|
||||
if(ent != player.entity) {
|
||||
Point out = position.location;
|
||||
$game_map.neighbors(out, false);
|
||||
motion = { int(out.x - position.location.x), int(out.y - position.location.y)};
|
||||
}
|
||||
});
|
||||
|
||||
if($enemy.location.x == $player.location.x && $enemy.location.y == $player.location.y) {
|
||||
$player.event(EntityEvent::HIT);
|
||||
// motion system
|
||||
$world.system<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
|
||||
Point move_to = {
|
||||
position.location.x + motion.dx,
|
||||
position.location.y + motion.dy
|
||||
};
|
||||
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.clear_target(position.location);
|
||||
position.location = move_to;
|
||||
}
|
||||
});
|
||||
|
||||
// combat system
|
||||
auto combatSystem = [&]() {
|
||||
const auto& player_position = $world.component<Position>(player.entity);
|
||||
$world.system<Position, Combat>([&](const auto &ent, auto &pos, auto &combat) {
|
||||
if(ent != player.entity && pos.location.x == player_position.location.x &&
|
||||
pos.location.y == player_position.location.y) {
|
||||
$burn_baby_burn = true;
|
||||
} else if($goal.x == $player.location.x && $goal.y == $player.location.y) {
|
||||
$status_text = "YOU WIN!";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
combatSystem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,6 +225,7 @@ void GUI::draw_screen(bool clear, float map_off_x, float map_off_y) {
|
|||
}
|
||||
|
||||
void GUI::shake() {
|
||||
$hit_sound.play();
|
||||
for(int i = 0; i < 10; ++i) {
|
||||
int x = Random::uniform<int>(-10,10);
|
||||
int y = Random::uniform<int>(-10,10);
|
||||
|
@ -196,6 +235,35 @@ void GUI::shake() {
|
|||
}
|
||||
}
|
||||
|
||||
void GUI::configure_world() {
|
||||
dbc::check($game_map.room_count() > 1, "not enough rooms in map.");
|
||||
// configure a player as a fact of the world
|
||||
Player player{$world.entity()};
|
||||
$world.set<Player>(player);
|
||||
|
||||
$world.assign<Position>(player.entity, {$game_map.place_entity(0)});
|
||||
$world.assign<Motion>(player.entity, {0, 0});
|
||||
$world.assign<Combat>(player.entity, {100, 10});
|
||||
$world.assign<Tile>(player.entity, {PLAYER_TILE});
|
||||
|
||||
auto enemy = $world.entity();
|
||||
$world.assign<Position>(enemy, {$game_map.place_entity(1)});
|
||||
$world.assign<Motion>(enemy, {0,0});
|
||||
$world.assign<Combat>(enemy, {20, 10});
|
||||
$world.assign<Tile>(enemy, {ENEMY_TILE});
|
||||
|
||||
auto enemy2 = $world.entity();
|
||||
$world.assign<Position>(enemy2, {$game_map.place_entity(2)});
|
||||
$world.assign<Motion>(enemy2, {0,0});
|
||||
$world.assign<Combat>(enemy2, {20, 10});
|
||||
$world.assign<Tile>(enemy2, {"*"});
|
||||
|
||||
auto gold = $world.entity();
|
||||
$world.assign<Position>(gold, {$game_map.place_entity($game_map.room_count() - 1)});
|
||||
$world.assign<Treasure>(gold, {100});
|
||||
$world.assign<Tile>(gold, {"$"});
|
||||
}
|
||||
|
||||
void GUI::render_scene() {
|
||||
Render($map_screen, $map_view->Render());
|
||||
Render($screen, $document->Render());
|
||||
|
@ -222,6 +290,7 @@ void GUI::render_scene() {
|
|||
}
|
||||
|
||||
int GUI::main() {
|
||||
configure_world();
|
||||
create_renderer();
|
||||
|
||||
while($window.isOpen()) {
|
||||
|
|
7
gui.hpp
7
gui.hpp
|
@ -10,8 +10,8 @@
|
|||
#include <ftxui/dom/canvas.hpp>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include "entity.hpp"
|
||||
#include "map.hpp"
|
||||
#include "dinkyecs.hpp"
|
||||
|
||||
using std::string;
|
||||
using ftxui::Canvas, ftxui::Component, ftxui::Screen;
|
||||
|
@ -42,9 +42,6 @@ class GUI {
|
|||
sf::Sound $hit_sound;
|
||||
bool $show_paths = false;
|
||||
string $status_text = "NOT DEAD";
|
||||
Entity $player;
|
||||
Entity $enemy;
|
||||
Point $goal;
|
||||
Component $document;
|
||||
Component $map_view;
|
||||
Canvas $canvas;
|
||||
|
@ -57,6 +54,7 @@ class GUI {
|
|||
sf::RenderWindow $window;
|
||||
Screen $screen;
|
||||
Screen $map_screen;
|
||||
DinkyECS::World $world;
|
||||
|
||||
public:
|
||||
GUI();
|
||||
|
@ -71,6 +69,7 @@ public:
|
|||
void draw_screen(bool clear=true, float map_off_x=0.0f, float map_off_y=0.0f);
|
||||
void shake();
|
||||
void burn();
|
||||
void configure_world();
|
||||
|
||||
int main();
|
||||
};
|
||||
|
|
4
map.cpp
4
map.cpp
|
@ -271,11 +271,11 @@ bool Map::walk(Point &src, Point &target) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void Map::set_target(Point &at, int value) {
|
||||
void Map::set_target(const Point &at, int value) {
|
||||
$input_map[at.y][at.x] = 0;
|
||||
}
|
||||
|
||||
void Map::clear_target(Point &at) {
|
||||
void Map::clear_target(const Point &at) {
|
||||
$input_map[at.y][at.x] = 1;
|
||||
}
|
||||
|
||||
|
|
4
map.hpp
4
map.hpp
|
@ -75,8 +75,8 @@ public:
|
|||
void place_rooms(Room &root);
|
||||
void make_paths();
|
||||
void partition_map(Room &cur, int depth);
|
||||
void set_target(Point &at, int value=0);
|
||||
void clear_target(Point &at);
|
||||
void set_target(const Point &at, int value=0);
|
||||
void clear_target(const Point &at);
|
||||
bool walk(Point &src, Point &target);
|
||||
void set_door(Room &room, int value);
|
||||
void dump();
|
||||
|
|
|
@ -17,7 +17,6 @@ dependencies = [catch2, fmt,
|
|||
runtests = executable('runtests', [
|
||||
'dbc.cpp',
|
||||
'map.cpp',
|
||||
'entity.cpp',
|
||||
'rand.cpp',
|
||||
'tests/fsm.cpp',
|
||||
'tests/dbc.cpp',
|
||||
|
@ -31,7 +30,6 @@ roguish = executable('roguish', [
|
|||
'map.cpp',
|
||||
'gui.cpp',
|
||||
'rand.cpp',
|
||||
'entity.cpp',
|
||||
],
|
||||
dependencies: dependencies)
|
||||
|
||||
|
|
|
@ -4,7 +4,12 @@
|
|||
#include <fmt/core.h>
|
||||
|
||||
using namespace fmt;
|
||||
using DinkyECS::Entity, DinkyECS::World;
|
||||
using DinkyECS::Entity;
|
||||
|
||||
struct Player {
|
||||
std::string name;
|
||||
Entity eid;
|
||||
};
|
||||
|
||||
struct Position {
|
||||
double x, y;
|
||||
|
@ -18,63 +23,104 @@ struct Gravity {
|
|||
double level;
|
||||
};
|
||||
|
||||
/*
|
||||
* Using a function catches instances where I'm not copying
|
||||
* the data into the world.
|
||||
*/
|
||||
void configure(DinkyECS::World &world, Entity &test) {
|
||||
println("---Configuring the base system.");
|
||||
Entity test2 = world.entity();
|
||||
|
||||
world.assign<Position>(test, {10,20});
|
||||
world.assign<Velocity>(test, {1,2});
|
||||
|
||||
world.assign<Position>(test2, {1,1});
|
||||
world.assign<Velocity>(test2, {10,20});
|
||||
|
||||
println("---- Setting up the player as a fact in the system.");
|
||||
|
||||
auto player_eid = world.entity();
|
||||
Player player_info{"Zed", player_eid};
|
||||
// just set some player info as a fact with the entity id
|
||||
world.set<Player>(player_info);
|
||||
|
||||
world.assign<Velocity>(player_eid, {0,0});
|
||||
world.assign<Position>(player_eid, {0,0});
|
||||
|
||||
auto enemy = world.entity();
|
||||
world.assign<Velocity>(enemy, {0,0});
|
||||
world.assign<Position>(enemy, {0,0});
|
||||
|
||||
println("--- Creating facts (singletons)");
|
||||
world.set<Gravity>({0.9});
|
||||
}
|
||||
|
||||
int main() {
|
||||
World me;
|
||||
DinkyECS::World world;
|
||||
Entity test = world.entity();
|
||||
|
||||
Entity test = me.entity();
|
||||
Entity test2 = me.entity();
|
||||
configure(world, test);
|
||||
|
||||
me.assign<Position>(test, {10,20});
|
||||
me.assign<Velocity>(test, {1,2});
|
||||
|
||||
me.assign<Position>(test2, {1,1});
|
||||
me.assign<Velocity>(test2, {10,20});
|
||||
|
||||
Position &pos = me.component<Position>(test);
|
||||
Position &pos = world.component<Position>(test);
|
||||
println("GOT POS x={}, y={}", pos.x, pos.y);
|
||||
|
||||
Velocity &vel = me.component<Velocity>(test);
|
||||
Velocity &vel = world.component<Velocity>(test);
|
||||
println("GOT VELOCITY x={}, y={}", vel.x, vel.y);
|
||||
|
||||
println("--- Position only system:");
|
||||
me.system<Position>([](const auto &ent, auto &pos) {
|
||||
world.system<Position>([](const auto &ent, auto &pos) {
|
||||
println("entity={}, pos.x={}, pos.y={}", ent, pos.x, pos.y);
|
||||
});
|
||||
|
||||
println("--- Velocity only system:");
|
||||
me.system<Velocity>([](const auto &, auto &vel) {
|
||||
world.system<Velocity>([](const auto &, auto &vel) {
|
||||
println("vel.x={}, vel.y={}", vel.x, vel.y);
|
||||
});
|
||||
|
||||
println("--- Manually get the velocity in position system:");
|
||||
me.system<Position>([&](const auto &ent, auto &pos) {
|
||||
Velocity &vel = me.component<Velocity>(ent);
|
||||
world.system<Position>([&](const auto &ent, auto &pos) {
|
||||
Velocity &vel = world.component<Velocity>(ent);
|
||||
println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y);
|
||||
});
|
||||
|
||||
println("--- Creating facts (singletons)");
|
||||
me.set<Gravity>({0.9});
|
||||
|
||||
println("--- Query only entities with Position and Velocity:");
|
||||
me.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
|
||||
Gravity &grav = me.get<Gravity>();
|
||||
world.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
|
||||
Gravity &grav = world.get<Gravity>();
|
||||
println("grav={}, entity={}, vel.x, vel.y, pos.x={}, pos.y={}", grav.level, ent, vel.x, vel.y, pos.x, pos.y);
|
||||
});
|
||||
|
||||
// now remove Velocity
|
||||
me.remove<Velocity>(test);
|
||||
world.remove<Velocity>(test);
|
||||
|
||||
println("--- After remove test, should only result in test2:");
|
||||
me.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
|
||||
world.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
|
||||
println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y);
|
||||
});
|
||||
|
||||
println("--- Create a stored system you can save for later.");
|
||||
auto movementSystem = me.runner<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
|
||||
auto movementSystem = world.runner<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
|
||||
println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y);
|
||||
});
|
||||
|
||||
movementSystem();
|
||||
|
||||
// how to create an identified entity like the player
|
||||
|
||||
|
||||
// to avoid repeatedly getting the player just make a closure with it
|
||||
// QUESTION: could I just capture it and not have the double function wrapping?
|
||||
auto playerVsEnemies = [&]() {
|
||||
auto& player = world.get<Player>(); // grabbed it
|
||||
world.system<Position>([&](const auto &ent, auto &pos) {
|
||||
if(player.eid != ent) {
|
||||
println("{} is enemy attacking player {}", ent, player.name);
|
||||
} else {
|
||||
println("{} is player", player.name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
playerVsEnemies();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue