diff --git a/dinkyecs.hpp b/dinkyecs.hpp index 1716a9c..399115b 100644 --- a/dinkyecs.hpp +++ b/dinkyecs.hpp @@ -8,7 +8,6 @@ #include #include - namespace DinkyECS { typedef unsigned long Entity; diff --git a/gui.cpp b/gui.cpp index b8534a1..4299156 100644 --- a/gui.cpp +++ b/gui.cpp @@ -12,17 +12,13 @@ #include #include -#include -#include -#include - #include #include "dbc.hpp" #include "gui.hpp" #include "rand.hpp" #include "systems.hpp" -#include "collider.hpp" #include "events.hpp" +#include "render.hpp" using std::string; using namespace fmt; @@ -30,49 +26,34 @@ using namespace std::chrono_literals; using namespace ftxui; using namespace Components; -std::array VALUES{ - sf::Color{1, 4, 2}, // black - sf::Color{9, 29, 16}, // dark dark - sf::Color{14, 50, 26}, // dark mid - sf::Color{0, 109, 44}, // dark light - sf::Color{63, 171, 92}, // mid - sf::Color{161, 217, 155}, // light dark - sf::Color{199, 233, 192}, // light mid - sf::Color{229, 245, 224}, // light light - sf::Color{255, 255, 255}, // white - sf::Color::Transparent, // white -}; - -sf::Color GUI::color(int val) { - return VALUES[size_t(val)]; -} - -sf::Color GUI::color(Value val) { - return VALUES[size_t(val)]; -} GUI::GUI(DinkyECS::World &world, Map& game_map) : - $window(sf::VideoMode(VIDEO_X,VIDEO_Y), "Roguish"), + $game_map(game_map), + $log({{"Welcome to the game!"}}), + $view_port{0,0}, $screen(SCREEN_X, SCREEN_Y), $map_screen(0,0), - $view_port{0,0}, - $map_font_size(BASE_MAP_FONT_SIZE), - $line_spacing(0), - $sounds("./assets"), - $log({{"Welcome to the game!"}}), $world(world), - $game_map(game_map) + $sounds("./assets"), + $renderer($canvas, $map_screen, $screen) { // this needs a config file soon - $font.loadFromFile("./assets/text.otf"); + $sounds.load("hit", "hit.wav"); resize_map(BASE_MAP_FONT_SIZE); +} - $sounds.load("hit", "hit.wav"); +void GUI::resize_map(int new_size) { + if($renderer.resize_map(new_size)) { + auto bounds = $renderer.$base_glyph.bounds; + $view_port = { + size_t(std::ceil((VIDEO_X - GAME_MAP_POS) / bounds.width)), + size_t(std::ceil(VIDEO_Y / bounds.height)) + }; - $ui_text.setFont($font); - $ui_text.setPosition(0,0); - $ui_text.setCharacterSize(UI_FONT_SIZE); - $ui_text.setFillColor(color(Value::LIGHT_LIGHT)); + // set canvas to best size + $canvas = Canvas($view_port.x * 2, $view_port.y * 4); + $map_screen = Screen($view_port.x, $view_port.y); + } } void GUI::create_renderer() { @@ -142,13 +123,15 @@ void GUI::handle_world_events() { } bool GUI::handle_ui_events() { - sf::Event event; bool event_happened = false; + sf::Event event; auto player = $world.get_the(); + auto& window = $renderer.$window; + int map_font_size = $renderer.$map_font_size; - while($window.pollEvent(event)) { + while(window.pollEvent(event)) { if(event.type == sf::Event::Closed) { - $window.close(); + window.close(); } else if(event.type == sf::Event::KeyPressed) { auto& player_motion = $world.get(player.entity); @@ -165,9 +148,9 @@ bool GUI::handle_ui_events() { player_motion.dy = 1; event_happened = true; } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Equal)) { - resize_map($map_font_size + 10); + resize_map(map_font_size + 10); } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Hyphen)) { - resize_map($map_font_size - 10); + resize_map(map_font_size - 10); } } } @@ -176,21 +159,6 @@ bool GUI::handle_ui_events() { } -sf::Sprite &GUI::get_text_sprite(wchar_t tile) { - if(!$sprites.contains(tile)) { - sf::Glyph glyph = $font.getGlyph(tile, $map_font_size, false); - // WARNING! we actually have to do this here because SFML caches - // the glyphs on the font texture, so this gets loaded each time - // we get a new glyph from the font. - $font_texture = $font.getTexture($map_font_size); - sf::Sprite sprite($font_texture); - sprite.setTextureRect(glyph.textureRect); - $sprites[tile] = sprite; - } - - return $sprites[tile]; -} - void GUI::run_systems() { auto player = $world.get_the(); System::enemy_pathing($world, $game_map, player); @@ -199,123 +167,19 @@ void GUI::run_systems() { System::death($world); } -void GUI::resize_map(int new_size) { - if(MIN_FONT_SIZE < new_size && new_size < MAX_FONT_SIZE) { - $sprites.clear(); // need to reset the sprites for the new size - $map_font_size = new_size; - $base_glyph = $font.getGlyph(L'█', $map_font_size, false); - auto bounds = $base_glyph.bounds; - $line_spacing = $font.getLineSpacing($map_font_size); - $view_port = { - size_t(std::ceil((VIDEO_X - GAME_MAP_POS) / bounds.width)), - size_t(std::ceil(VIDEO_Y / bounds.height)) - }; - // set canvas to best size - $canvas = Canvas($view_port.x * 2, $view_port.y * 4); - $map_screen = Screen($view_port.x, $view_port.y); - } -} - -void GUI::draw_screen(bool clear, float map_off_x, float map_off_y) { - if(clear) $window.clear(); - std::string screenout = $screen.ToString(); - std::wstring main_screen_utf8 = $converter.from_bytes(screenout); - $ui_text.setString(main_screen_utf8); - $window.draw($ui_text); - - std::string map_screenout = $map_screen.ToString(); - std::wstring map_screen_utf8 = $converter.from_bytes(map_screenout); - - float y = 0.0f; - float x = GAME_MAP_POS; - // make a copy so we don't modify the cached one - auto bg_sprite = get_text_sprite(L'█'); - auto bg_bounds = bg_sprite.getLocalBounds(); - bg_sprite.setColor(sf::Color(20,20,20)); - auto add_sprite = get_text_sprite(L'!'); - bool has_add = false; - - for(size_t i = 0; i < map_screen_utf8.size(); i++) { - wchar_t tile = map_screen_utf8[i]; - - if(tile == L'\n') { - // don't bother processing newlines, just skip - y += $line_spacing; - x = GAME_MAP_POS; - } else if(tile == L'\r') { - continue; // skip these, just windows junk - } else { - // it's a visual cell - bg_sprite.setPosition({x+map_off_x, y+map_off_y}); - sf::Sprite &sprite = get_text_sprite(tile); - - // should look into caching all this instead of calcing it each time - auto sp_bounds = sprite.getLocalBounds(); - - // calculate where to center the sprite, but only if it's smaller - auto width_delta = bg_bounds.width > sp_bounds.width ? (bg_bounds.width - sp_bounds.width) / 2 : 0; - auto height_delta = bg_bounds.height > sp_bounds.width ? (bg_bounds.height - sp_bounds.height) / 2 : 0; - - // TODO: need to center it inside the bg_sprite - sprite.setPosition({x+width_delta+map_off_x, y+height_delta+map_off_y}); - - // get the entity combat and make them light gray if dead - if(tile == L'█') { - sprite.setColor(sf::Color(80,80,80)); - } else if(tile == L'☺') { - sprite.setColor(sf::Color::Blue); - } else if(tile == L'Ω') { - sprite.setColor(sf::Color::Red); - // HACK: just playing with adding multiple characters for drawing - add_sprite.setColor(sf::Color::Red); - add_sprite.setPosition({x-3,y-3}); - has_add = true; - } else if(tile == L'#') { - sprite.setColor(sf::Color(5,5,5)); - } else { - sprite.setColor(color(Value::MID)); - } - - // now draw the background sprite and sprite - // TODO: this can become a standard sprite description - $window.draw(bg_sprite); - $window.draw(sprite); - if(has_add) { - $window.draw(add_sprite); - has_add = false; - } - // next cell - x += $base_glyph.advance; - } - } - - $window.display(); -} - -void GUI::shake() { - for(int i = 0; i < 10; ++i) { - int x = Random::uniform(-10,10); - int y = Random::uniform(-10,10); - // add x/y back to draw screen - draw_screen(true, x, y); - std::this_thread::sleep_for(1ms); - } -} - void GUI::render_scene() { $screen.Clear(); $map_screen.Clear(); Render($map_screen, $map_view->Render()); Render($screen, $document->Render()); - - draw_screen(); + $renderer.draw_screen(); } int GUI::main() { create_renderer(); run_systems(); - while($window.isOpen()) { + while($renderer.$window.isOpen()) { render_scene(); if(handle_ui_events()) { diff --git a/gui.hpp b/gui.hpp index f233d70..e727b9b 100644 --- a/gui.hpp +++ b/gui.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -14,27 +13,13 @@ #include "dinkyecs.hpp" #include "components.hpp" #include "sound.hpp" +#include "render.hpp" using std::string; using ftxui::Canvas, ftxui::Component, ftxui::Screen; -constexpr int MIN_FONT_SIZE = 20; -constexpr int MAX_FONT_SIZE = 140; -constexpr int GAME_MAP_X = 90; -constexpr int GAME_MAP_Y = 90; -constexpr int GAME_MAP_POS = 600; constexpr int SCREEN_X = 40; constexpr int SCREEN_Y = 30; -constexpr int VIDEO_X = 1600; -constexpr int VIDEO_Y = 900; -constexpr int UI_FONT_SIZE=30; -constexpr int BASE_MAP_FONT_SIZE=90; - -enum class Value { - BLACK=0, DARK_DARK, DARK_MID, - DARK_LIGHT, MID, LIGHT_DARK, LIGHT_MID, - LIGHT_LIGHT, WHITE, TRANSPARENT -}; struct ActionLog { std::deque messages; @@ -49,42 +34,30 @@ struct ActionLog { class GUI { string $status_text = "NOT DEAD"; + Canvas $canvas; Component $document; Component $map_view; - Canvas $canvas; - sf::Font $font; - sf::Text $ui_text; - std::wstring_convert> $converter; - sf::RenderWindow $window; + Map& $game_map; + ActionLog $log; + Point $view_port; Screen $screen; Screen $map_screen; - sf::Texture $font_texture; - std::unordered_map $sprites; - Point $view_port; - int $map_font_size; - sf::Glyph $base_glyph; - float $line_spacing; - SoundManager $sounds; - ActionLog $log; DinkyECS::World& $world; - Map& $game_map; + SoundManager $sounds; + SFMLRender $renderer; public: GUI(DinkyECS::World& world, Map& game_map); // disable copying GUI(GUI &gui) = delete; - sf::Color color(Value val); - sf::Color color(int val); + void resize_map(int new_size); void create_renderer(); void render_scene(); bool handle_ui_events(); void handle_world_events(); void draw_screen(bool clear=true, float map_off_x=0.0f, float map_off_y=0.0f); - void shake(); void run_systems(); - void resize_map(int new_size); - sf::Sprite &get_text_sprite(wchar_t tile); int main(); }; diff --git a/main.cpp b/main.cpp index 9d836e0..fbe700f 100644 --- a/main.cpp +++ b/main.cpp @@ -5,6 +5,7 @@ #include "components.hpp" #include "dbc.hpp" #include "collider.hpp" +#include "render.hpp" /* * This needs to be turned into a real world generator diff --git a/meson.build b/meson.build index f66a786..6dc59e2 100644 --- a/meson.build +++ b/meson.build @@ -19,6 +19,7 @@ runtests = executable('runtests', [ 'rand.cpp', 'sound.cpp', 'collider.cpp', + 'render.cpp', 'tests/fsm.cpp', 'tests/dbc.cpp', 'tests/map.cpp', @@ -38,6 +39,7 @@ roguish = executable('roguish', [ 'collider.cpp', 'combat.cpp', 'systems.cpp', + 'render.cpp', ], dependencies: dependencies) diff --git a/render.cpp b/render.cpp new file mode 100644 index 0000000..dd716fa --- /dev/null +++ b/render.cpp @@ -0,0 +1,144 @@ +#include "render.hpp" +#include + +std::array VALUES{ + sf::Color{1, 4, 2}, // black + sf::Color{9, 29, 16}, // dark dark + sf::Color{14, 50, 26}, // dark mid + sf::Color{0, 109, 44}, // dark light + sf::Color{63, 171, 92}, // mid + sf::Color{161, 217, 155}, // light dark + sf::Color{199, 233, 192}, // light mid + sf::Color{229, 245, 224}, // light light + sf::Color{255, 255, 255}, // white + sf::Color::Transparent, // white +}; + +sf::Color SFMLRender::color(int val) { + return VALUES[size_t(val)]; +} + +sf::Color SFMLRender::color(Value val) { + return VALUES[size_t(val)]; +} + +SFMLRender::SFMLRender(Canvas &canvas, Screen &map_screen, Screen &screen) : + $window(sf::VideoMode(VIDEO_X,VIDEO_Y), "Roguish"), + $map_font_size(BASE_MAP_FONT_SIZE), + $line_spacing(0), + $canvas(canvas), + $map_screen(map_screen), + $screen(screen) +{ + $font.loadFromFile("./assets/text.otf"); + $ui_text.setFont($font); + $ui_text.setPosition(0,0); + $ui_text.setCharacterSize(UI_FONT_SIZE); + $ui_text.setFillColor(color(Value::LIGHT_LIGHT)); +} + +sf::Sprite &SFMLRender::get_text_sprite(wchar_t tile) { + if(!$sprites.contains(tile)) { + sf::Glyph glyph = $font.getGlyph(tile, $map_font_size, false); + // WARNING! we actually have to do this here because SFML caches + // the glyphs on the font texture, so this gets loaded each time + // we get a new glyph from the font. + $font_texture = $font.getTexture($map_font_size); + sf::Sprite sprite($font_texture); + sprite.setTextureRect(glyph.textureRect); + $sprites[tile] = sprite; + } + + return $sprites[tile]; +} + + +bool SFMLRender::resize_map(int new_size) { + if(MIN_FONT_SIZE < new_size && new_size < MAX_FONT_SIZE) { + $sprites.clear(); // need to reset the sprites for the new size + $map_font_size = new_size; + $base_glyph = $font.getGlyph(L'█', $map_font_size, false); + $line_spacing = $font.getLineSpacing($map_font_size); + return true; + } else { + // something else here + return false; + } +} + + +void SFMLRender::draw_screen(bool clear, float map_off_x, float map_off_y) { + if(clear) $window.clear(); + std::string screenout = $screen.ToString(); + std::wstring main_screen_utf8 = $converter.from_bytes(screenout); + $ui_text.setString(main_screen_utf8); + $window.draw($ui_text); + + std::string map_screenout = $map_screen.ToString(); + std::wstring map_screen_utf8 = $converter.from_bytes(map_screenout); + + float y = 0.0f; + float x = GAME_MAP_POS; + // make a copy so we don't modify the cached one + auto bg_sprite = get_text_sprite(L'█'); + auto bg_bounds = bg_sprite.getLocalBounds(); + bg_sprite.setColor(sf::Color(20,20,20)); + auto add_sprite = get_text_sprite(L'!'); + bool has_add = false; + + for(size_t i = 0; i < map_screen_utf8.size(); i++) { + wchar_t tile = map_screen_utf8[i]; + + if(tile == L'\n') { + // don't bother processing newlines, just skip + y += $line_spacing; + x = GAME_MAP_POS; + } else if(tile == L'\r') { + continue; // skip these, just windows junk + } else { + // it's a visual cell + bg_sprite.setPosition({x+map_off_x, y+map_off_y}); + sf::Sprite &sprite = get_text_sprite(tile); + + // should look into caching all this instead of calcing it each time + auto sp_bounds = sprite.getLocalBounds(); + + // calculate where to center the sprite, but only if it's smaller + auto width_delta = bg_bounds.width > sp_bounds.width ? (bg_bounds.width - sp_bounds.width) / 2 : 0; + auto height_delta = bg_bounds.height > sp_bounds.width ? (bg_bounds.height - sp_bounds.height) / 2 : 0; + + // TODO: need to center it inside the bg_sprite + sprite.setPosition({x+width_delta+map_off_x, y+height_delta+map_off_y}); + + // get the entity combat and make them light gray if dead + if(tile == L'█') { + sprite.setColor(sf::Color(80,80,80)); + } else if(tile == L'☺') { + sprite.setColor(sf::Color::Blue); + } else if(tile == L'Ω') { + sprite.setColor(sf::Color::Red); + // HACK: just playing with adding multiple characters for drawing + add_sprite.setColor(sf::Color::Red); + add_sprite.setPosition({x-3,y-3}); + has_add = true; + } else if(tile == L'#') { + sprite.setColor(sf::Color(5,5,5)); + } else { + sprite.setColor(color(Value::MID)); + } + + // now draw the background sprite and sprite + // TODO: this can become a standard sprite description + $window.draw(bg_sprite); + $window.draw(sprite); + if(has_add) { + $window.draw(add_sprite); + has_add = false; + } + // next cell + x += $base_glyph.advance; + } + } + + $window.display(); +} diff --git a/render.hpp b/render.hpp new file mode 100644 index 0000000..5b73d73 --- /dev/null +++ b/render.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "point.hpp" +#include + +using ftxui::Canvas, ftxui::Screen; + +constexpr int VIDEO_X = 1600; +constexpr int VIDEO_Y = 900; +constexpr int MIN_FONT_SIZE = 20; +constexpr int MAX_FONT_SIZE = 140; +constexpr int GAME_MAP_X = 90; +constexpr int GAME_MAP_Y = 90; +constexpr int GAME_MAP_POS = 600; +constexpr int UI_FONT_SIZE=30; +constexpr int BASE_MAP_FONT_SIZE=90; + +enum class Value { + BLACK=0, DARK_DARK, DARK_MID, + DARK_LIGHT, MID, LIGHT_DARK, LIGHT_MID, + LIGHT_LIGHT, WHITE, TRANSPARENT +}; + +struct SFMLRender { + sf::RenderWindow $window; + int $map_font_size; + float $line_spacing; + std::unordered_map $sprites; + sf::Font $font; + sf::Texture $font_texture; + sf::Glyph $base_glyph; + Canvas& $canvas; + Screen& $map_screen; + Screen& $screen; + sf::Text $ui_text; + std::wstring_convert> $converter; + + SFMLRender(Canvas &canvas, Screen &map_screen, Screen &screen); + + // disable copy + SFMLRender(SFMLRender &other) = delete; + + sf::Color color(int val); + sf::Color color(Value val); + sf::Sprite &get_text_sprite(wchar_t tile); + bool resize_map(int new_size); + void draw_screen(bool clear=true, float map_off_x=0.0f, float map_off_y=0.0f); +}; diff --git a/sound.cpp b/sound.cpp index 571e6f2..a504feb 100644 --- a/sound.cpp +++ b/sound.cpp @@ -16,7 +16,7 @@ void SoundManager::load(const std::string name, const std::string sound_path) { dbc::check(fs::exists(full_path), format("sound file {} does not exist", sound_path)); // create the buffer and keep in the buffer map - std::shared_ptr pair = std::make_shared(); + SoundPair* pair = new SoundPair(); $sounds[name] = pair; bool good = pair->buffer.loadFromFile(full_path.string()); diff --git a/sound.hpp b/sound.hpp index 23bff94..3963635 100644 --- a/sound.hpp +++ b/sound.hpp @@ -12,7 +12,7 @@ struct SoundPair { struct SoundManager { std::filesystem::path $base_path; - std::unordered_map > $sounds; + std::unordered_map $sounds; SoundManager(std::string base_path);