diff --git a/assets/config.json b/assets/config.json index c8c51a5..25a64d5 100644 --- a/assets/config.json +++ b/assets/config.json @@ -4,7 +4,8 @@ "pickup": "assets/sounds/pickup.ogg", "ui_click": "assets/sounds/ui_click.ogg", "ui_hover": "assets/sounds/ui_hover.ogg", - "walk": "assets/sounds/walk.ogg" + "walk": "assets/sounds/walk.ogg", + "test_story": "assets/sounds/test_story.ogg" }, "sprites": { "spider_bot": @@ -81,6 +82,11 @@ {"path": "assets/scenes/win_scene.png", "frame_width": 1920, "frame_height": 1080 + }, + "test_story": + {"path": "assets/stories/test_storyboard.png", + "frame_width": 1280, + "frame_height": 720 } }, "worldgen": { diff --git a/assets/sounds/test_story.ogg b/assets/sounds/test_story.ogg new file mode 100644 index 0000000..f9fb660 Binary files /dev/null and b/assets/sounds/test_story.ogg differ diff --git a/assets/stories.json b/assets/stories.json new file mode 100644 index 0000000..9f4d7bc --- /dev/null +++ b/assets/stories.json @@ -0,0 +1,26 @@ +{ + "intro_story": + {"_type": "Storyboard", + "image": "test_story", + "audio": "test_story", + "layout": [ + "[a|b|c1]", + "[d|e|c2]", + "[g|h|i]" + ], + "beats": [ + ["00:00", "a","pan", "60"], + ["00:01", "b","shake", "30"], + ["00:5", "g","pan", "60"], + ["00:6", "h","pan", "60"], + ["00:7", "h","bounce", "60"], + ["00:8", "c1","pan", "60"], + ["00:9", "c2","pan", "60"], + ["00:10", "d","bounce", "60"], + ["00:11", "e","shake", "60"], + ["00:12", "i","pan", "60"], + ["00:13", "i","shake", "60"], + ["00:14", "i","bounce", "60"] + ] + } +} diff --git a/assets/stories/test_storyboard.png b/assets/stories/test_storyboard.png new file mode 100644 index 0000000..2c049ad Binary files /dev/null and b/assets/stories/test_storyboard.png differ diff --git a/src/gui/fsm.cpp b/src/gui/fsm.cpp index f55f95e..974129c 100644 --- a/src/gui/fsm.cpp +++ b/src/gui/fsm.cpp @@ -31,6 +31,7 @@ namespace gui { void FSM::event(Event ev, std::any data) { switch($state) { FSM_STATE(State, START, ev); + FSM_STATE(State, INTRO, ev); FSM_STATE(State, START_SCENE, ev); FSM_STATE(State, DEATH_SCENE, ev); FSM_STATE(State, WIN_SCENE, ev); @@ -58,9 +59,23 @@ namespace gui { $debug_ui.init(cell); $status_ui.init(); run_systems(); + $story = std::make_shared("intro_story"); + $story->init(); + state(State::INTRO); + } - show_scene("STARTING"); - state(State::START_SCENE); + void FSM::INTRO(Event ev) { + dbc::check($story != nullptr, "you forgot the stroy"); + + if($story->playing()) { + if(ev == game::Event::MOUSE_CLICK) { + $story->reset(); + } + } else { + $story = nullptr; + show_scene("STARTING"); + state(State::START_SCENE); + } } void FSM::START_SCENE(Event ev) { @@ -407,6 +422,8 @@ namespace gui { void FSM::draw_gui() { if($cur_scene != nullptr) { $cur_scene->render($window); + } else if($story != nullptr) { + $story->render($window); } else { $main_ui.render(); $status_ui.render($window); @@ -422,6 +439,8 @@ namespace gui { void FSM::update() { if($cur_scene != nullptr) { $cur_scene->update(); + } else if($story != nullptr) { + $story->update(); } } diff --git a/src/gui/fsm.hpp b/src/gui/fsm.hpp index cff645e..4e39d74 100644 --- a/src/gui/fsm.hpp +++ b/src/gui/fsm.hpp @@ -12,10 +12,12 @@ #include "gui/dnd_loot.hpp" #include "events.hpp" #include "gui/scene_ui.hpp" +#include "gui/storyboard.hpp" namespace gui { enum class State { START=__LINE__, + INTRO=__LINE__, START_SCENE=__LINE__, DEATH_SCENE=__LINE__, WIN_SCENE=__LINE__, @@ -47,12 +49,14 @@ namespace gui { }; std::shared_ptr $cur_scene = nullptr; + std::shared_ptr $story = nullptr; FSM(); void event(game::Event ev, std::any data={}); void START(game::Event ev); + void INTRO(game::Event ev); void START_SCENE(game::Event ev); void WIN_SCENE(game::Event ev); void DEATH_SCENE(game::Event ev); diff --git a/src/gui/storyboard.cpp b/src/gui/storyboard.cpp new file mode 100644 index 0000000..b48bb8c --- /dev/null +++ b/src/gui/storyboard.cpp @@ -0,0 +1,109 @@ +#include "gui/storyboard.hpp" +#include "game/components.hpp" +#include "game/sound.hpp" +#include "game/config.hpp" +#include +#include +#include +#include + +namespace storyboard { + UI::UI(const std::string& story_name) : + $view_texture({SCREEN_WIDTH, SCREEN_HEIGHT}), + $view_sprite($view_texture.getTexture()) + { + $view_sprite.setPosition({0, 0}); + auto config = settings::get("stories"); + $story = components::convert(config[story_name]); + $audio = sound::get_sound_pair($story.audio).sound; + $camera.from_story($story); + } + + void UI::init() { + $ui.position(0,0, SCREEN_WIDTH, SCREEN_HEIGHT); + + $ui.set($ui.MAIN, {$ui.$parser, guecs::THEME.TRANSPARENT}); + + auto& background = $ui.get($ui.MAIN); + background.set_sprite($story.image, true); + + for(auto& line : $story.layout) { + $layout.append(line); + } + + $ui.layout($layout); + $audio->play(); + } + + void UI::render(sf::RenderWindow &window) { + track_audio(); + $view_texture.clear(); + + $camera.render($view_texture); + $ui.render($view_texture); + // $ui.debug_layout($view_texture); + + $view_texture.display(); + window.draw($view_sprite); + } + + bool UI::playing() { + return $audio->getStatus() == sf::SoundSource::Status::Playing; + } + + sf::Time parse_time_code(const std::string& time) { + std::chrono::seconds out{}; + + std::istringstream is{time}; + is >> std::chrono::parse("%M:%S", out); + dbc::check(!is.fail(), $F("Time parse failed: {}", time)); + + return sf::Time(out); + } + + void UI::update() { + $camera.update(); + } + + void UI::track_audio() { + auto& [timecode, cell_name, form, _] = $story.beats[cur_beat % $story.beats.size()]; + auto track_head = $audio->getPlayingOffset(); + + auto next_beat = parse_time_code(timecode); + + if(track_head >= next_beat) { + if($moving) return; + $moving = true; // prevent motion until next tick + + // get the original zoom target as from + auto& from_cell = $ui.cell_for($zoom_target); + $camera.position(from_cell.mid_x, from_cell.mid_y); + + $zoom_target = cell_name; + $camera.style(timecode); + + // get the new target from the cell names + zoom($zoom_target); + $camera.play(); + cur_beat++; + } else { + $moving = false; + } + } + + bool UI::mouse(float, float, guecs::Modifiers) { + $audio->stop(); + return true; + } + + void UI::zoom(const std::string &cell_name) { + auto& cell = $ui.cell_for(cell_name); + + $camera.resize(float(cell.w)); + $camera.move(float(cell.mid_x), float(cell.mid_y)); + } + + void UI::reset() { + $camera.reset($view_texture); + } +} diff --git a/src/gui/storyboard.hpp b/src/gui/storyboard.hpp new file mode 100644 index 0000000..4dfec3c --- /dev/null +++ b/src/gui/storyboard.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "constants.hpp" +#include +#include "graphics/camera.hpp" +#include +#include "game/components.hpp" + +namespace storyboard { + + struct UI { + guecs::UI $ui; + sf::RenderTexture $view_texture; + sf::Sprite $view_sprite; + cinematic::Camera $camera{{SCREEN_WIDTH, SCREEN_HEIGHT}, "story"}; + std::shared_ptr $audio; + std::string $zoom_target = "a"; + bool $moving = false; + int cur_beat = 0; + components::Storyboard $story; + std::string $layout; + + UI(const std::string& story_name); + + void init(); + void update(); + void render(sf::RenderWindow &window); + bool mouse(float x, float y, guecs::Modifiers mods); + void zoom(const std::string &cell_name); + void reset(); + void track_audio(); + bool playing(); + void config_camera(cinematic::Camera &camera); + }; + +} diff --git a/src/main.cpp b/src/main.cpp index 88ece2e..7ea502d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,6 +34,7 @@ int main(int argc, char* argv[]) { // BUG: need to sort out how to deal with this in the FSM if(main.in_state(gui::State::IDLE) || main.in_state(gui::State::START_SCENE) + || main.in_state(gui::State::INTRO) || main.in_state(gui::State::WIN_SCENE) || main.in_state(gui::State::DEATH_SCENE) || main.in_state(gui::State::NEXT_LEVEL_SCENE) diff --git a/src/meson.build b/src/meson.build index 7148c99..345eadc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -21,6 +21,7 @@ sources = files( 'gui/overlay_ui.cpp', 'gui/body_ui.cpp', 'gui/scene_ui.cpp', + 'gui/storyboard.cpp', # graphics 'graphics/animation.cpp',