feat: input and player logic
This commit is contained in:
parent
7b6855321f
commit
e75acb9747
|
@ -1,26 +1,39 @@
|
|||
#include "character.h"
|
||||
#include "core/renderer.h"
|
||||
#include "world.h"
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace rogue {
|
||||
Character::Character(World &world, Tile location, CharacterData stats)
|
||||
CharacterLogic::~CharacterLogic() {}
|
||||
|
||||
void CharacterLogic::set_character(Character *character) {
|
||||
this->character = character;
|
||||
}
|
||||
|
||||
Character::Character(Tile location, CharacterLogic *logic, CharacterData stats)
|
||||
: data{stats}
|
||||
, logic{logic}
|
||||
, health{stats.health}
|
||||
, location{location}
|
||||
, world{world}
|
||||
{}
|
||||
, world{nullptr} {
|
||||
this->logic->set_character(this);
|
||||
}
|
||||
|
||||
void Character::act() {
|
||||
assert(this->world != nullptr && "World generation did not initialize character properly");
|
||||
if(this->health < 0) {
|
||||
return;
|
||||
}
|
||||
this->logic->set_character(this);
|
||||
// get motion from logic
|
||||
Tile target{this->data.logic(*this)};
|
||||
if(target == this->location) {
|
||||
Tile target{this->logic->move()};
|
||||
if(target == this->location
|
||||
|| std::abs(target.x - this->location.x) + std::abs(target.y - this->location.y) != 1) {
|
||||
return;
|
||||
}
|
||||
// check resulting tile
|
||||
TileData tile{world.query_tile(target)};
|
||||
TileData tile{world->query_tile(target)};
|
||||
// if character, deal damage
|
||||
if(tile.character != nullptr) {
|
||||
tile.character->deal_damage(this->data.damage);
|
||||
|
@ -38,7 +51,8 @@ void Character::draw() {
|
|||
bool Character::deal_damage(int damage) {
|
||||
return (this->health -= damage) <= 0;
|
||||
}
|
||||
Tile null_character_logic_function(Character &character) {
|
||||
return character.location;
|
||||
|
||||
Tile NullCharacterLogic::move() {
|
||||
return this->character->location;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,34 +2,46 @@
|
|||
#define ROGUE_CHARACTER_H
|
||||
|
||||
#include "core/roguedefs.h"
|
||||
#include <memory>
|
||||
|
||||
namespace rogue {
|
||||
class World;
|
||||
class Character;
|
||||
|
||||
typedef Tile(&CharacterLogicFunction)(Character &character);
|
||||
struct CharacterLogic {
|
||||
virtual ~CharacterLogic();
|
||||
|
||||
virtual Tile move() = 0;
|
||||
protected:
|
||||
Character *character{nullptr};
|
||||
private:
|
||||
friend class Character;
|
||||
void set_character(Character *character);
|
||||
};
|
||||
|
||||
struct CharacterData {
|
||||
int health{1};
|
||||
int damage{1};
|
||||
Sprite sprite{0};
|
||||
CharacterLogicFunction logic;
|
||||
};
|
||||
|
||||
struct Character {
|
||||
Character(World &world, Tile location, CharacterData stats);
|
||||
Character(Tile location, CharacterLogic *logic, CharacterData stats);
|
||||
|
||||
void act();
|
||||
void draw();
|
||||
bool deal_damage(int damage);
|
||||
|
||||
CharacterData const data;
|
||||
std::shared_ptr<CharacterLogic> logic{nullptr};
|
||||
int health{1};
|
||||
Tile location{0, 0};
|
||||
World &world;
|
||||
World *world{nullptr};
|
||||
};
|
||||
|
||||
extern Tile null_character_logic_function(Character &character);
|
||||
struct NullCharacterLogic : public CharacterLogic {
|
||||
virtual Tile move() override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !ROGUE_CHARACTER_H
|
||||
|
|
40
src/core/input.cpp
Normal file
40
src/core/input.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#include "input.h"
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL2/SDL_log.h>
|
||||
#include <SDL2/SDL_scancode.h>
|
||||
|
||||
namespace rogue {
|
||||
InputHandler::InputHandler() {
|
||||
Input::add_handler(this);
|
||||
}
|
||||
|
||||
InputHandler::~InputHandler() {
|
||||
Input::remove_handler(this);
|
||||
}
|
||||
|
||||
KeyEventHandler::KeyEventHandler(std::set<SDL_Scancode> code, Listener listener)
|
||||
: scancode{code}
|
||||
, listener{listener} {}
|
||||
|
||||
void KeyEventHandler::process_event(SDL_Event event) {
|
||||
if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && this->scancode.contains(event.key.keysym.scancode)) {
|
||||
this->listener(event.type == SDL_KEYDOWN);
|
||||
}
|
||||
}
|
||||
|
||||
void Input::add_handler(InputHandler *handler) {
|
||||
Input::handlers.insert(handler);
|
||||
}
|
||||
|
||||
void Input::remove_handler(InputHandler *handler) {
|
||||
Input::handlers.erase(handler);
|
||||
}
|
||||
|
||||
void Input::process_event(SDL_Event event) {
|
||||
for(InputHandler *handler : Input::handlers) {
|
||||
handler->process_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<InputHandler*> Input::handlers{};
|
||||
}
|
34
src/core/input.h
Normal file
34
src/core/input.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#ifndef ROGUE_INPUT_H
|
||||
#define ROGUE_INPUT_H
|
||||
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL2/SDL_scancode.h>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
|
||||
namespace rogue {
|
||||
struct InputHandler {
|
||||
InputHandler();
|
||||
virtual ~InputHandler();
|
||||
virtual void process_event(SDL_Event event) = 0;
|
||||
};
|
||||
|
||||
struct KeyEventHandler : public InputHandler {
|
||||
typedef std::function<void(bool down)> Listener;
|
||||
KeyEventHandler(std::set<SDL_Scancode> code, Listener listener);
|
||||
virtual void process_event(SDL_Event event) override;
|
||||
std::set<SDL_Scancode> scancode{};
|
||||
Listener listener{};
|
||||
};
|
||||
|
||||
class Input {
|
||||
public:
|
||||
static void add_handler(InputHandler *handler);
|
||||
static void remove_handler(InputHandler *handler);
|
||||
static void process_event(SDL_Event event);
|
||||
private:
|
||||
static std::set<InputHandler*> handlers;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !ROGUE_INPUT_H
|
|
@ -4,6 +4,7 @@
|
|||
#include "core/roguedefs.h"
|
||||
#include <SDL2/SDL_log.h>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
namespace rogue {
|
||||
void Room::draw(Tile world_pivot) {
|
||||
|
@ -36,6 +37,14 @@ bool Room::tile_is_wall(Tile local_tile) const {
|
|||
return is_outside_room && is_outside_hallways;
|
||||
}
|
||||
|
||||
World::World()
|
||||
: direction_pressed{{
|
||||
SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L,
|
||||
SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN
|
||||
},
|
||||
std::bind(&World::on_input, this, std::placeholders::_1)
|
||||
} {}
|
||||
|
||||
void World::act() {
|
||||
for(Character &character : this->characters) {
|
||||
character.act();
|
||||
|
@ -43,7 +52,7 @@ void World::act() {
|
|||
}
|
||||
|
||||
void World::render() {
|
||||
Render::clear({this->chunk_size/2, this->chunk_size/2});
|
||||
Render::clear(this->player->location);
|
||||
for(size_t i{0}; i < this->rooms.size(); ++i) {
|
||||
this->rooms[i].draw({
|
||||
.x = int(i % this->shear * this->chunk_size),
|
||||
|
@ -74,6 +83,12 @@ TileData World::query_tile(Tile tile) {
|
|||
return out;
|
||||
}
|
||||
|
||||
void World::on_input(bool pressed) {
|
||||
if(!pressed) {
|
||||
this->act();
|
||||
}
|
||||
}
|
||||
|
||||
WorldGenerator &WorldGenerator::with_chunk_size(unsigned side_length) {
|
||||
assert(this->chunk_side_length == 0);
|
||||
assert(side_length > 0);
|
||||
|
@ -97,22 +112,25 @@ WorldGenerator &WorldGenerator::with_room_size(unsigned min_side_length, unsigne
|
|||
return *this;
|
||||
}
|
||||
|
||||
World WorldGenerator::generate() {
|
||||
WorldGenerator &WorldGenerator::with_player(Character character) {
|
||||
this->player.emplace(character);
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::unique_ptr<World> WorldGenerator::generate() {
|
||||
assert(this->chunk_side_length > 0 && "Chunk size is required to generate world");
|
||||
assert(this->min_room_size > 0 && "Room size is required to generate world");
|
||||
assert(this->min_room_size < this->max_room_size && "Room size is required to generate world");
|
||||
assert(this->world_side_length > 0 && "World size is required to generate world");
|
||||
World world{};
|
||||
world.chunk_size = this->chunk_side_length;
|
||||
world.shear = this->world_side_length;
|
||||
world.characters.push_back(Character(world, {this->chunk_side_length/2, this->chunk_side_length/2}, CharacterData {
|
||||
.health = 1,
|
||||
.damage = 1,
|
||||
.sprite = 0,
|
||||
.logic = null_character_logic_function
|
||||
}));
|
||||
world.rooms.reserve(this->world_side_length * this->world_side_length);
|
||||
world.rooms.push_back(Room {
|
||||
assert(this->player.has_value() && "World cannot be generated without a player");
|
||||
std::unique_ptr<World> world{new World()};
|
||||
world->characters.push_back(player.value());
|
||||
world->player = &world->characters[0];
|
||||
world->player->world = world.get();
|
||||
world->chunk_size = this->chunk_side_length;
|
||||
world->shear = this->world_side_length;
|
||||
world->rooms.reserve(this->world_side_length * this->world_side_length);
|
||||
world->rooms.push_back(Room {
|
||||
.hallway_paths = Directions(Directions::NORTH | Directions::EAST),
|
||||
.chunk_size = this->chunk_side_length,
|
||||
.rect = {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#ifndef ROGUE_WORLD_H
|
||||
#define ROGUE_WORLD_H
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <SDL2/SDL_rect.h>
|
||||
#include "character.h"
|
||||
#include "core/input.h"
|
||||
#include "core/roguedefs.h"
|
||||
|
||||
namespace rogue {
|
||||
|
@ -26,18 +28,21 @@ struct Room {
|
|||
|
||||
struct World {
|
||||
friend class WorldGenerator;
|
||||
World();
|
||||
~World() = default;
|
||||
void act();
|
||||
void render();
|
||||
Room &get_room(Chunk chunk);
|
||||
TileData query_tile(Tile tile);
|
||||
void on_input(bool);
|
||||
private:
|
||||
int chunk_size{1};
|
||||
unsigned shear{0};
|
||||
Character *player{nullptr};
|
||||
std::vector<Room> rooms{};
|
||||
std::vector<Character> characters{};
|
||||
KeyEventHandler direction_pressed;
|
||||
private:
|
||||
World() = default;
|
||||
};
|
||||
|
||||
class WorldGenerator {
|
||||
|
@ -46,8 +51,10 @@ public:
|
|||
WorldGenerator &with_chunk_size(unsigned side_length);
|
||||
WorldGenerator &with_world_size(unsigned side_length);
|
||||
WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length);
|
||||
World generate();
|
||||
WorldGenerator &with_player(Character character);
|
||||
std::unique_ptr<World> generate();
|
||||
private:
|
||||
std::optional<Character> player;
|
||||
int chunk_side_length{0}, world_side_length{0};
|
||||
int max_room_size{0}, min_room_size{0};
|
||||
};
|
||||
|
|
18
src/main.cpp
18
src/main.cpp
|
@ -1,14 +1,26 @@
|
|||
#include <SDL2/SDL.h>
|
||||
#include <memory>
|
||||
#include "core/character.h"
|
||||
#include "core/input.h"
|
||||
#include "core/renderer.h"
|
||||
#include "core/world.h"
|
||||
#include "player_logic.h"
|
||||
|
||||
using namespace rogue;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
World world{WorldGenerator()
|
||||
std::unique_ptr<World> world{WorldGenerator()
|
||||
.with_world_size(5)
|
||||
.with_chunk_size(9)
|
||||
.with_room_size(2, 5)
|
||||
.with_player(Character({9/2, 9/2},
|
||||
new PlayerCharacterLogic(),
|
||||
CharacterData {
|
||||
.health = 1,
|
||||
.damage = 1,
|
||||
.sprite = 0
|
||||
}
|
||||
))
|
||||
.generate()
|
||||
};
|
||||
RenderData data{RenderDataSetup()
|
||||
|
@ -22,11 +34,13 @@ int main(int argc, char *argv[]) {
|
|||
Render::provide_render_data(data);
|
||||
SDL_Event evt;
|
||||
for(;;) {
|
||||
world.render();
|
||||
world->render();
|
||||
Render::present();
|
||||
if(SDL_WaitEvent(&evt)) {
|
||||
if(evt.type == SDL_QUIT) {
|
||||
goto main_loop;
|
||||
} else {
|
||||
Input::process_event(evt);
|
||||
}
|
||||
}
|
||||
} main_loop:
|
||||
|
|
46
src/player_logic.cpp
Normal file
46
src/player_logic.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include "player_logic.h"
|
||||
#include <SDL2/SDL_log.h>
|
||||
|
||||
namespace rogue {
|
||||
PlayerCharacterLogic::PlayerCharacterLogic()
|
||||
: up{{SDL_SCANCODE_K, SDL_SCANCODE_UP}, std::bind(&PlayerCharacterLogic::on_up, this, std::placeholders::_1)}
|
||||
, down{{SDL_SCANCODE_J, SDL_SCANCODE_DOWN}, std::bind(&PlayerCharacterLogic::on_down, this, std::placeholders::_1)}
|
||||
, right{{SDL_SCANCODE_L, SDL_SCANCODE_RIGHT}, std::bind(&PlayerCharacterLogic::on_right, this, std::placeholders::_1)}
|
||||
, left{{SDL_SCANCODE_H, SDL_SCANCODE_LEFT}, std::bind(&PlayerCharacterLogic::on_left, this, std::placeholders::_1)}
|
||||
{}
|
||||
|
||||
Tile PlayerCharacterLogic::move() {
|
||||
return {
|
||||
this->character->location.x + input_x,
|
||||
this->character->location.y + input_y
|
||||
};
|
||||
}
|
||||
|
||||
void PlayerCharacterLogic::on_up(bool pressed) {
|
||||
if(pressed) {
|
||||
input_x = 0;
|
||||
input_y = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCharacterLogic::on_down(bool pressed) {
|
||||
if(pressed) {
|
||||
input_x = 0;
|
||||
input_y = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCharacterLogic::on_left(bool pressed) {
|
||||
if(pressed) {
|
||||
input_x = -1;
|
||||
input_y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCharacterLogic::on_right(bool pressed) {
|
||||
if(pressed) {
|
||||
input_x = 1;
|
||||
input_y = 0;
|
||||
}
|
||||
}
|
||||
};
|
27
src/player_logic.h
Normal file
27
src/player_logic.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef PLAYER_LOGIC_H
|
||||
#define PLAYER_LOGIC_H
|
||||
|
||||
#include "core/character.h"
|
||||
#include "core/input.h"
|
||||
#include "core/roguedefs.h"
|
||||
#include <functional>
|
||||
|
||||
namespace rogue {
|
||||
class PlayerCharacterLogic : public CharacterLogic {
|
||||
public:
|
||||
PlayerCharacterLogic();
|
||||
private:
|
||||
virtual Tile move() override;
|
||||
void on_up(bool pressed);
|
||||
void on_down(bool pressed);
|
||||
void on_left(bool pressed);
|
||||
void on_right(bool pressed);
|
||||
int input_x{0}, input_y{0};
|
||||
KeyEventHandler up;
|
||||
KeyEventHandler down;
|
||||
KeyEventHandler right;
|
||||
KeyEventHandler left;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !PLAYER_LOGIC_H
|
Loading…
Reference in a new issue