feat: documentation and cleanup pass over all objects
This commit is contained in:
parent
7ff9756dcc
commit
872f3f3fbf
|
@ -45,7 +45,7 @@ void Character::act() {
|
||||||
void Character::draw() {
|
void Character::draw() {
|
||||||
if(this->health > 0) {
|
if(this->health > 0) {
|
||||||
Render::draw(this->location, this->data->sprite);
|
Render::draw(this->location, this->data->sprite);
|
||||||
} // else { // draw gore pile
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Character::deal_damage(int damage) {
|
bool Character::deal_damage(int damage) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ private:
|
||||||
void set_character(Character *character);
|
void set_character(Character *character);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Character's stats as a struct that's easilly made into a flyweight to share around
|
||||||
struct CharacterData {
|
struct CharacterData {
|
||||||
int health{1};
|
int health{1};
|
||||||
int damage{1};
|
int damage{1};
|
||||||
|
|
|
@ -12,16 +12,20 @@ InputHandler::~InputHandler() {
|
||||||
Input::remove_handler(this);
|
Input::remove_handler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEventHandler::KeyEventHandler(std::set<SDL_Scancode> code, Listener listener, bool allow_repeat)
|
KeyEventHandler::KeyEventHandler(std::set<SDL_Scancode> codes, Listener listener, bool allow_repeat)
|
||||||
: scancode{code}
|
: scancodes{codes}
|
||||||
, listener{listener}
|
, listener{listener}
|
||||||
, allow_repeat{allow_repeat}
|
, allow_repeat{allow_repeat}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void KeyEventHandler::process_event(SDL_Event event) {
|
void KeyEventHandler::process_event(SDL_Event event) {
|
||||||
|
// check if this event is an event related to keys
|
||||||
if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
|
if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
|
||||||
|
// check if it is an allowed event (either non-repeated or repetition is allowed)
|
||||||
&& (event.key.repeat == 0 || this->allow_repeat)
|
&& (event.key.repeat == 0 || this->allow_repeat)
|
||||||
&& this->scancode.contains(event.key.keysym.scancode)) {
|
// check if it is one of our keys
|
||||||
|
&& this->scancodes.contains(event.key.keysym.scancode)) {
|
||||||
|
// trigger event, notifying it if the key was pressed or released
|
||||||
this->listener(event.type == SDL_KEYDOWN);
|
this->listener(event.type == SDL_KEYDOWN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,25 +7,35 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
namespace rogue {
|
namespace rogue {
|
||||||
|
// Interface representing a handler for SDL os events that can be used to handle input
|
||||||
|
// Uses RAII to register and deregister with the static input service
|
||||||
struct InputHandler {
|
struct InputHandler {
|
||||||
InputHandler();
|
InputHandler();
|
||||||
virtual ~InputHandler();
|
virtual ~InputHandler();
|
||||||
virtual void process_event(SDL_Event event) = 0;
|
virtual void process_event(SDL_Event event) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct KeyEventHandler : public InputHandler {
|
// implementation of the InputHandler interface that filters for SDL_KeyEvents that represent a given key or keys being pressed
|
||||||
typedef std::function<void(bool down)> Listener;
|
//
|
||||||
KeyEventHandler(std::set<SDL_Scancode> code, Listener listener, bool allow_repeat = true);
|
class KeyEventHandler : public InputHandler {
|
||||||
|
public:
|
||||||
|
typedef std::function<void(bool down)> Listener; // defines the interface required for listening for key events
|
||||||
|
// from this constructor until the object is destroyed, these settings remain the same, if you need to modify them, recreate the object
|
||||||
|
KeyEventHandler(std::set<SDL_Scancode> codes, Listener listener, bool allow_repeat = true);
|
||||||
|
// handle events passed in from the OS through SDL
|
||||||
virtual void process_event(SDL_Event event) override;
|
virtual void process_event(SDL_Event event) override;
|
||||||
std::set<SDL_Scancode> scancode{};
|
private:
|
||||||
Listener listener{};
|
std::set<SDL_Scancode> scancodes{}; // scancodes to listen for
|
||||||
bool allow_repeat{false};
|
Listener listener{}; // function to notify when event is triggered
|
||||||
|
bool allow_repeat{false}; // whether or not to allow key repeat (multiple down events before an up on the same key)
|
||||||
};
|
};
|
||||||
|
|
||||||
class Input {
|
class Input {
|
||||||
public:
|
public:
|
||||||
|
// register an input handler, registered handlers will be notified when Input::process_event is called
|
||||||
static void add_handler(InputHandler *handler);
|
static void add_handler(InputHandler *handler);
|
||||||
static void remove_handler(InputHandler *handler);
|
static void remove_handler(InputHandler *handler);
|
||||||
|
// notify registered InputHandlers of an event
|
||||||
static void process_event(SDL_Event event);
|
static void process_event(SDL_Event event);
|
||||||
private:
|
private:
|
||||||
static std::set<InputHandler*> handlers;
|
static std::set<InputHandler*> handlers;
|
||||||
|
|
|
@ -94,7 +94,7 @@ void Render::provide_render_data(RenderData &data) {
|
||||||
Render::data = &data;
|
Render::data = &data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Render::clear(Tile camera_center, unsigned fov) {
|
void Render::start_frame(Tile camera_center, unsigned fov) {
|
||||||
assert_render_initialized();
|
assert_render_initialized();
|
||||||
Render::camera_width = fov;
|
Render::camera_width = fov;
|
||||||
SDL_SetRenderDrawColor(Render::data->renderer, 0u, 0u, 0u, 255u);
|
SDL_SetRenderDrawColor(Render::data->renderer, 0u, 0u, 0u, 255u);
|
||||||
|
@ -126,7 +126,7 @@ void Render::draw(Tile world_space_tile, Sprite sprite) {
|
||||||
|
|
||||||
void Render::draw_menu(Sprite menu) {
|
void Render::draw_menu(Sprite menu) {
|
||||||
assert_render_initialized();
|
assert_render_initialized();
|
||||||
Render::clear({0, 0});
|
Render::start_frame({0, 0});
|
||||||
SDL_Rect const rect {
|
SDL_Rect const rect {
|
||||||
.x = window_size.x/2 - window_size.y/2,
|
.x = window_size.x/2 - window_size.y/2,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
|
@ -148,7 +148,7 @@ void Render::draw_healthbar(int current, int max, SDL_Rect area, int border) {
|
||||||
SDL_RenderFillRect(Render::data->renderer, &area);
|
SDL_RenderFillRect(Render::data->renderer, &area);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Render::present() {
|
void Render::finish_frame() {
|
||||||
assert_render_initialized();
|
assert_render_initialized();
|
||||||
SDL_RenderPresent(Render::data->renderer);
|
SDL_RenderPresent(Render::data->renderer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
namespace rogue {
|
namespace rogue {
|
||||||
// Contains all data required to render the game world
|
// Contains all data required to render the game world
|
||||||
|
// IMPORTANT: store this in stack memory within the scope that encompasses the game loop (probably main(..)).
|
||||||
|
// When this is destroyed, rendering becomes impossible until a new one is provided to Render
|
||||||
class RenderData {
|
class RenderData {
|
||||||
friend class RenderDataSetup;
|
friend class RenderDataSetup;
|
||||||
friend class Render;
|
friend class Render;
|
||||||
|
@ -23,45 +25,65 @@ private: RenderData() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global interface for rendering
|
// Global interface for rendering
|
||||||
|
// Serves as a service locator for render data with a simplified interface
|
||||||
class Render {
|
class Render {
|
||||||
private:
|
private:
|
||||||
friend class RenderData;
|
friend class RenderData;
|
||||||
|
// called by render data when it's constructor is called, de-initializes Renderer
|
||||||
|
// once this is called rendering is no longer possible
|
||||||
static void clear_render_data();
|
static void clear_render_data();
|
||||||
public:
|
public:
|
||||||
|
// provides the render data struct to the renderer, enabling it's use
|
||||||
static void provide_render_data(RenderData &data);
|
static void provide_render_data(RenderData &data);
|
||||||
|
|
||||||
static void clear(Tile camera_center, unsigned fov = 10);
|
// clear the screen and set the camera location for the next frame
|
||||||
|
static void start_frame(Tile camera_center, unsigned fov = 10);
|
||||||
|
// draw a sprite to the screen using it's sprite index
|
||||||
static void draw(Tile world_space_tile, Sprite sprite);
|
static void draw(Tile world_space_tile, Sprite sprite);
|
||||||
|
// draw a menu image that fills screen
|
||||||
static void draw_menu(Sprite menu);
|
static void draw_menu(Sprite menu);
|
||||||
|
// draw a healthbar
|
||||||
static void draw_healthbar(int current, int max, SDL_Rect area={0,0,500,50}, int border=10);
|
static void draw_healthbar(int current, int max, SDL_Rect area={0,0,500,50}, int border=10);
|
||||||
static void present();
|
// finish the frame and swap the buffers
|
||||||
|
static void finish_frame();
|
||||||
private:
|
private:
|
||||||
|
// the tile at the center of the camera, set at the start of a frame
|
||||||
static Tile camera_center;
|
static Tile camera_center;
|
||||||
|
// number of tiles to render horizontally
|
||||||
static unsigned camera_width;
|
static unsigned camera_width;
|
||||||
|
// the world offset to apply to sprites when drawing them to put them relative to the camera
|
||||||
static Tile camera_offset;
|
static Tile camera_offset;
|
||||||
static SDL_Point window_size;
|
static SDL_Point window_size; // size of the window, computed at the start of the frame
|
||||||
static int tile_screen_width;
|
static int tile_screen_width; // width of a tile on the screen in pixels
|
||||||
static int half_tile_screen_width;
|
static int half_tile_screen_width; // half of 'tile_screen_width', pre-computed at frame start
|
||||||
static RenderData *data;
|
static RenderData *data; // render data provided to the renderer, rendering is only possible while this is set
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render configuration, builder for RenderData
|
// Render configuration, builder for RenderData
|
||||||
class RenderDataSetup {
|
class RenderDataSetup {
|
||||||
public:
|
public:
|
||||||
RenderDataSetup() = default;
|
RenderDataSetup() = default;
|
||||||
|
// window has to be set up before anything else
|
||||||
RenderDataSetup &with_window(char const *name, Uint32 flags);
|
RenderDataSetup &with_window(char const *name, Uint32 flags);
|
||||||
|
// renderer has to be set up before images can be loaded
|
||||||
RenderDataSetup &with_renderer(Uint32 flags);
|
RenderDataSetup &with_renderer(Uint32 flags);
|
||||||
|
// set the resource path, required to load resources
|
||||||
RenderDataSetup &with_resource_path(std::filesystem::path base_path);
|
RenderDataSetup &with_resource_path(std::filesystem::path base_path);
|
||||||
|
// load a sprite and register it
|
||||||
RenderDataSetup &with_sprite(std::filesystem::path sprite_path);
|
RenderDataSetup &with_sprite(std::filesystem::path sprite_path);
|
||||||
|
// load a menu image and register it
|
||||||
RenderDataSetup &with_menu(std::filesystem::path sprite_path);
|
RenderDataSetup &with_menu(std::filesystem::path sprite_path);
|
||||||
|
// construct render-data that can be provided to Render
|
||||||
RenderData build();
|
RenderData build();
|
||||||
private:
|
private:
|
||||||
|
// helper function for loading SDL textures from filepaths
|
||||||
SDL_Texture *load_texture(std::filesystem::path file_path);
|
SDL_Texture *load_texture(std::filesystem::path file_path);
|
||||||
private:
|
private:
|
||||||
unsigned fov{10};
|
// the base path to load images from
|
||||||
std::optional<std::filesystem::path> resource_base_path;
|
std::optional<std::filesystem::path> resource_base_path;
|
||||||
SDL_Window *window{nullptr};
|
SDL_Window *window{nullptr}; // window created for the application
|
||||||
SDL_Renderer *renderer{nullptr};
|
SDL_Renderer *renderer{nullptr}; // renderer created from the window
|
||||||
|
// images loaded from resource_path
|
||||||
std::vector<SDL_Texture*> sprites{};
|
std::vector<SDL_Texture*> sprites{};
|
||||||
std::vector<SDL_Texture*> menus{};
|
std::vector<SDL_Texture*> menus{};
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,23 +3,29 @@
|
||||||
|
|
||||||
#include "SDL2/SDL_rect.h"
|
#include "SDL2/SDL_rect.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// commonly reused typedefs and macros
|
||||||
|
//
|
||||||
|
|
||||||
|
// bitmask of the four cardinal directions
|
||||||
|
// would be an enum, but C++ typechecking doesn't like that, so this results in more readable code overall
|
||||||
typedef unsigned short Directions;
|
typedef unsigned short Directions;
|
||||||
#define NORTH 0x1u
|
#define NORTH 0x1u
|
||||||
#define EAST (0x1u << 1)
|
#define EAST (0x1u << 1)
|
||||||
#define SOUTH (0x1u << 2)
|
#define SOUTH (0x1u << 2)
|
||||||
#define WEST (0x1u << 3)
|
#define WEST (0x1u << 3)
|
||||||
|
|
||||||
|
// simple shorthand for wrapping limiting a number by wrapping
|
||||||
|
// useful for generating random numbers within a range
|
||||||
#define WRAP(M_x, M_min, M_max) (((M_x - M_min) % (M_max - M_min)) + M_min)
|
#define WRAP(M_x, M_min, M_max) (((M_x - M_min) % (M_max - M_min)) + M_min)
|
||||||
|
|
||||||
#define casel(M_switch, M_case)\
|
// typedefs that help with differentiating between chunk coordinates and tile coordinates
|
||||||
case M_case:\
|
|
||||||
M_switch##_case_##M_case
|
|
||||||
|
|
||||||
typedef SDL_Point Tile;
|
typedef SDL_Point Tile;
|
||||||
typedef SDL_Point Chunk;
|
typedef SDL_Point Chunk;
|
||||||
|
// represents a sprite index
|
||||||
typedef unsigned Sprite;
|
typedef unsigned Sprite;
|
||||||
|
|
||||||
|
// define a comparison function for SDL_Point to make code slightly more readable
|
||||||
static inline bool operator==(SDL_Point const &lhs, SDL_Point const &rhs) {
|
static inline bool operator==(SDL_Point const &lhs, SDL_Point const &rhs) {
|
||||||
return lhs.x == rhs.x && lhs.y == rhs.y;
|
return lhs.x == rhs.x && lhs.y == rhs.y;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,14 +56,14 @@ void World::act_if_requested() {
|
||||||
|
|
||||||
void World::act() {
|
void World::act() {
|
||||||
this->player.act();
|
this->player.act();
|
||||||
this->boss.act();
|
|
||||||
for(Character &character : this->characters) {
|
for(Character &character : this->characters) {
|
||||||
character.act();
|
character.act();
|
||||||
}
|
}
|
||||||
|
this->boss.act();
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::render() {
|
void World::render() {
|
||||||
Render::clear(this->player.location, 20);
|
Render::start_frame(this->player.location, 20);
|
||||||
for(size_t i{0}; i < this->rooms.size(); ++i) {
|
for(size_t i{0}; i < this->rooms.size(); ++i) {
|
||||||
this->rooms[i].draw({
|
this->rooms[i].draw({
|
||||||
.x = int(i % this->shear * this->chunk_size),
|
.x = int(i % this->shear * this->chunk_size),
|
||||||
|
|
|
@ -27,53 +27,81 @@ struct Room {
|
||||||
bool tile_is_wall(Tile local_tile) const;
|
bool tile_is_wall(Tile local_tile) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct World {
|
// represents the current state of the game environment and actors
|
||||||
|
class World {
|
||||||
friend class WorldGenerator;
|
friend class WorldGenerator;
|
||||||
|
public:
|
||||||
World();
|
World();
|
||||||
~World() = default;
|
void act_if_requested(); // will call 'act' when 'turn_requested' is set
|
||||||
void act_if_requested();
|
void render(); // render the world, includes ui, environment and characters
|
||||||
void act();
|
Room &get_room(Chunk chunk); // returns the room at the given chunk
|
||||||
void render();
|
Character *get_player(); // returns the player character instance
|
||||||
Room &get_room(Chunk chunk);
|
Character *get_boss(); // returns the boss character instance
|
||||||
Character *get_player();
|
Chunk get_chunk(Tile tile); // returns the chunk at the given world-space tile
|
||||||
Character *get_boss();
|
TileData query_tile(Tile tile); // returns data related to the given world-space tile
|
||||||
Chunk get_chunk(Tile tile);
|
void on_input(bool pressed); // registered with the 'direction_pressed' key event handler
|
||||||
TileData query_tile(Tile tile);
|
|
||||||
void on_input(bool);
|
|
||||||
private:
|
private:
|
||||||
int chunk_size{1};
|
void act();
|
||||||
unsigned shear{0};
|
private:
|
||||||
bool turn_requested{false};
|
int chunk_size{1}; // size of chunks
|
||||||
Character player{};
|
unsigned shear{0}; // number of chunks before the next row (width of the map in chunks)
|
||||||
Character boss{};
|
bool turn_requested{false}; // flag for requesting a turn, consumed by 'act_if_requested' set by 'on_input'
|
||||||
std::vector<Room> rooms{};
|
// this flag exists (instead of just calling 'act' from 'on_input' to avoid moving the world
|
||||||
std::vector<Character> characters{};
|
// before the player's input is registered by the PlayerCharacterLogic
|
||||||
KeyEventHandler direction_pressed;
|
Character player{}; // player character, should be instantiated by world generator
|
||||||
|
Character boss{}; // boss character, should be instantiated by world generator
|
||||||
|
std::vector<Room> rooms{}; // all of the rooms in the map in order, this is a 2d array that wraps at 'shear'
|
||||||
|
std::vector<Character> characters{}; // all of the characters in the map, including dead ones
|
||||||
|
KeyEventHandler direction_pressed; // triggered when the player presses any direction, calls 'on_input'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Builder/factory for configuring and generating game environments and enemies
|
||||||
class WorldGenerator {
|
class WorldGenerator {
|
||||||
public:
|
public:
|
||||||
WorldGenerator() = default;
|
WorldGenerator() = default;
|
||||||
|
// set the chunk size, this also limits the size of rooms,
|
||||||
|
// chunks are always squares with a size of side_length * side_length
|
||||||
WorldGenerator &with_chunk_size(unsigned side_length);
|
WorldGenerator &with_chunk_size(unsigned side_length);
|
||||||
|
// set the size of the world,
|
||||||
|
// the world is always a square with a size of side_length * side_length
|
||||||
WorldGenerator &with_world_size(unsigned side_length);
|
WorldGenerator &with_world_size(unsigned side_length);
|
||||||
|
// set the minimum and maximum side length for rooms,
|
||||||
|
// rooms are generated with random sizes for both width and height
|
||||||
WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length);
|
WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length);
|
||||||
|
// set reusable factory object for the player
|
||||||
WorldGenerator &with_player(Character::Spawner spawner);
|
WorldGenerator &with_player(Character::Spawner spawner);
|
||||||
|
// add an enemy that can be spawned in rooms,
|
||||||
|
// pass in a factory object and the inverse of the frequency
|
||||||
|
// (think of it as "1 in every N rooms will have this enemy")
|
||||||
WorldGenerator &with_enemy(Character::Spawner spawner, int inv_frequency);
|
WorldGenerator &with_enemy(Character::Spawner spawner, int inv_frequency);
|
||||||
|
// set the factory object used to spawn the boss
|
||||||
WorldGenerator &with_boss(Character::Spawner spawner);
|
WorldGenerator &with_boss(Character::Spawner spawner);
|
||||||
|
// set the chance that a room will have a connection in inverse frequency
|
||||||
|
// keep in mind this is rolled per room pair that isn't already connected, so 2 times per room
|
||||||
WorldGenerator &with_connecting_chance(unsigned num);
|
WorldGenerator &with_connecting_chance(unsigned num);
|
||||||
|
// generates a new world using the current configuration, requires all settings to be set
|
||||||
std::unique_ptr<World> generate();
|
std::unique_ptr<World> generate();
|
||||||
private:
|
private:
|
||||||
|
// set up for Recursive Backtracking Maze Generation
|
||||||
void generate_world(std::unique_ptr<World> &world, Chunk start);
|
void generate_world(std::unique_ptr<World> &world, Chunk start);
|
||||||
|
// Recursive Backtracking Maze Generation
|
||||||
void generate_room(std::unique_ptr<World> &world, Chunk const &last);
|
void generate_room(std::unique_ptr<World> &world, Chunk const &last);
|
||||||
|
// spawn enemies in a generated room
|
||||||
|
// generates a boss enemy for the first room it's called on.
|
||||||
void add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room);
|
void add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room);
|
||||||
|
// add the player to a room (does not have to be generated, as the player always spawns at the center)
|
||||||
void setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk);
|
void setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk);
|
||||||
private:
|
private:
|
||||||
|
// Factory objects for enemies
|
||||||
Character::Spawner player_spawner{};
|
Character::Spawner player_spawner{};
|
||||||
Character::Spawner boss_spawner{};
|
Character::Spawner boss_spawner{};
|
||||||
|
// factory object, inverse frequency
|
||||||
std::vector<std::pair<Character::Spawner, int>> enemy_spawners{};
|
std::vector<std::pair<Character::Spawner, int>> enemy_spawners{};
|
||||||
|
// sizes
|
||||||
int chunk_side_length{0}, world_side_length{0};
|
int chunk_side_length{0}, world_side_length{0};
|
||||||
int max_room_size{0}, min_room_size{0};
|
int max_room_size{0}, min_room_size{0};
|
||||||
unsigned connecting_chance{1};
|
unsigned connecting_chance{1};
|
||||||
|
// set by 'generate_enemies' when the boss is generated
|
||||||
bool boss_generated{false};
|
bool boss_generated{false};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,18 +10,25 @@ Tile CritteLogic::move() {
|
||||||
if(this->is_aware_of_player) {
|
if(this->is_aware_of_player) {
|
||||||
// cache query_tile call for repeated use
|
// cache query_tile call for repeated use
|
||||||
std::function<TileData(Tile)> query_tile{std::bind(&World::query_tile, this->character->world, std::placeholders::_1)};
|
std::function<TileData(Tile)> query_tile{std::bind(&World::query_tile, this->character->world, std::placeholders::_1)};
|
||||||
|
// this could be a few quick vector math operations, but i'm minimizing the C++ features i'm using with SDL structs
|
||||||
|
// difference between the player's location and ours
|
||||||
Tile difference{
|
Tile difference{
|
||||||
player->location.x - location.x,
|
player->location.x - location.x,
|
||||||
player->location.y - location.y
|
player->location.y - location.y
|
||||||
};
|
};
|
||||||
|
// absolute difference, useful for comparing them
|
||||||
Tile abs{std::abs(difference.x), std::abs(difference.y)};
|
Tile abs{std::abs(difference.x), std::abs(difference.y)};
|
||||||
Tile direction{
|
// signs of the difference
|
||||||
|
Tile signs{
|
||||||
difference.x == 0 ? 0 : difference.x / std::abs(difference.x),
|
difference.x == 0 ? 0 : difference.x / std::abs(difference.x),
|
||||||
difference.y == 0 ? 0 : difference.y / std::abs(difference.y)
|
difference.y == 0 ? 0 : difference.y / std::abs(difference.y)
|
||||||
};
|
};
|
||||||
Tile x_motion{location.x + direction.x, location.y};
|
// theoretical motion along x
|
||||||
Tile y_motion{location.x, location.y + direction.y};
|
Tile x_motion{location.x + signs.x, location.y};
|
||||||
if(abs.x > abs.y) {
|
// theoretical motion along y
|
||||||
|
Tile y_motion{location.x, location.y + signs.y};
|
||||||
|
// choose either x or y motion depending on which is possible and faster
|
||||||
|
if(abs.x >= abs.y) {
|
||||||
if(!query_tile(x_motion).is_wall)
|
if(!query_tile(x_motion).is_wall)
|
||||||
return x_motion;
|
return x_motion;
|
||||||
else if(!query_tile(y_motion).is_wall) {
|
else if(!query_tile(y_motion).is_wall) {
|
||||||
|
|
53
src/main.cpp
53
src/main.cpp
|
@ -37,17 +37,23 @@ CharacterData const critte_data{
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
// Game state, simple way of managing which menu/screen the player is currently on
|
||||||
GameState game_state{GameState::MainMenu};
|
GameState game_state{GameState::MainMenu};
|
||||||
|
// World generator, can be used to generate new rooms when the player restarts the game
|
||||||
WorldGenerator generator{WorldGenerator()
|
WorldGenerator generator{WorldGenerator()
|
||||||
.with_world_size(6)
|
.with_world_size(6)
|
||||||
|
// world generation
|
||||||
.with_chunk_size(8)
|
.with_chunk_size(8)
|
||||||
.with_room_size(5, 8)
|
.with_room_size(5, 8)
|
||||||
.with_connecting_chance(5)
|
.with_connecting_chance(5)
|
||||||
|
// setup character spawners/character factories
|
||||||
.with_player(Character::make_factory<PlayerCharacterLogic>(player_data))
|
.with_player(Character::make_factory<PlayerCharacterLogic>(player_data))
|
||||||
|
// enemies and boss all use the same "AI" with different data
|
||||||
.with_enemy(Character::make_factory<CritteLogic>(critte_data), 2)
|
.with_enemy(Character::make_factory<CritteLogic>(critte_data), 2)
|
||||||
.with_enemy(Character::make_factory<CritteLogic>(critte_data), 3)
|
.with_enemy(Character::make_factory<CritteLogic>(critte_data), 3)
|
||||||
.with_enemy(Character::make_factory<CritteLogic>(critte_data), 10)
|
.with_enemy(Character::make_factory<CritteLogic>(critte_data), 10)
|
||||||
.with_boss(Character::make_factory<CritteLogic>(boss_data))};
|
.with_boss(Character::make_factory<CritteLogic>(boss_data))};
|
||||||
|
// renderdata, passed to Render but kept here so that RAII will call the destructor when main pops off the stack
|
||||||
RenderData data{RenderDataSetup()
|
RenderData data{RenderDataSetup()
|
||||||
// create window and renderer, allowing for texture creation
|
// create window and renderer, allowing for texture creation
|
||||||
.with_window("roguelike", SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_FULLSCREEN)
|
.with_window("roguelike", SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_FULLSCREEN)
|
||||||
|
@ -65,45 +71,37 @@ int main(int argc, char *argv[]) {
|
||||||
.with_menu("escape.png")
|
.with_menu("escape.png")
|
||||||
.build()
|
.build()
|
||||||
};
|
};
|
||||||
|
// again, provided to the renderer here, but ownership remains local
|
||||||
|
Render::provide_render_data(data);
|
||||||
// use <functional> features to capture game_state and listen for input within main()
|
// use <functional> features to capture game_state and listen for input within main()
|
||||||
|
// the victory and death screens only allow jumping back with return/enter
|
||||||
KeyEventHandler return_to_main_input{{SDL_SCANCODE_RETURN},
|
KeyEventHandler return_to_main_input{{SDL_SCANCODE_RETURN},
|
||||||
[&game_state](bool down) {
|
[&game_state](bool down) {
|
||||||
if(!down) return;
|
if(!down) {
|
||||||
switch(game_state) {
|
return;
|
||||||
case GameState::Game: // do nothing, let the input be handled by the world/characters
|
} else if(game_state == GameState::GameOver || game_state == GameState::Escape) {
|
||||||
case GameState::MainMenu: // main menu starts with direction input not enter
|
game_state = GameState::MainMenu;
|
||||||
break;
|
|
||||||
case GameState::GameOver:
|
|
||||||
game_state = GameState::MainMenu;
|
|
||||||
break;
|
|
||||||
case GameState::Escape:
|
|
||||||
game_state = GameState::MainMenu;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
// The game is started by pressing one of the input directions as a soft tutorial
|
||||||
KeyEventHandler start_game_input{{
|
KeyEventHandler start_game_input{{
|
||||||
SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L,
|
SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L,
|
||||||
SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN
|
SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN
|
||||||
},
|
},
|
||||||
[&game_state](bool down) {
|
[&game_state](bool down) {
|
||||||
if(!down) return;
|
if(!down) {
|
||||||
switch(game_state) {
|
return;
|
||||||
case GameState::Game:
|
} else if(game_state == GameState::MainMenu) {
|
||||||
case GameState::GameOver:
|
game_state = GameState::Game;
|
||||||
case GameState::Escape:
|
|
||||||
break;
|
|
||||||
case GameState::MainMenu:
|
|
||||||
game_state = GameState::Game;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
Render::provide_render_data(data);
|
// create an empty world, filled in when the game is started by the player
|
||||||
std::unique_ptr<World> world{nullptr};
|
std::unique_ptr<World> world{nullptr};
|
||||||
SDL_Event evt;
|
// main loop, continues until broken out of
|
||||||
for(;;) {
|
for(;;) {
|
||||||
// DOOM-style menus
|
// DOOM-style menus
|
||||||
switch(game_state) {
|
switch(game_state) {
|
||||||
|
@ -115,6 +113,7 @@ int main(int argc, char *argv[]) {
|
||||||
if(world == nullptr) {
|
if(world == nullptr) {
|
||||||
world.reset(generator.generate().release());
|
world.reset(generator.generate().release());
|
||||||
}
|
}
|
||||||
|
// tick characters (if requested) and render
|
||||||
world->act_if_requested();
|
world->act_if_requested();
|
||||||
world->render();
|
world->render();
|
||||||
// game end conditions (player or boss death)
|
// game end conditions (player or boss death)
|
||||||
|
@ -133,14 +132,16 @@ int main(int argc, char *argv[]) {
|
||||||
Render::draw_menu(2);
|
Render::draw_menu(2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Render::present();
|
Render::finish_frame();
|
||||||
|
// receive input
|
||||||
|
SDL_Event evt;
|
||||||
if(SDL_WaitEvent(&evt)) {
|
if(SDL_WaitEvent(&evt)) {
|
||||||
if(evt.type == SDL_QUIT) {
|
if(evt.type == SDL_QUIT) { // shortcirquit for quitting when requested
|
||||||
goto break_main_loop;
|
break; // quit by breaking out of the main loop
|
||||||
} else {
|
} else {
|
||||||
Input::process_event(evt);
|
Input::process_event(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} break_main_loop:
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue