From 5b57fb2033a965f264ef6a994cd883a4b26677a6 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Mon, 3 Nov 2025 00:25:48 -0500 Subject: [PATCH] Now have a simple camera system that I can configure in json for different motion effects. --- assets/cameras.json | 74 +++++++++++++++++++++++++++ boss/fight.cpp | 2 +- boss/ui.cpp | 16 +++--- boss/ui.hpp | 3 +- camera.cpp | 56 +++++++++++++++++++++ camera.hpp | 20 ++++++++ main.cpp | 2 + meson.build | 3 +- tests/animation2.cpp | 117 ------------------------------------------- tests/camera.cpp | 6 +++ tools/arena.cpp | 2 + 11 files changed, 173 insertions(+), 128 deletions(-) create mode 100644 assets/cameras.json create mode 100644 camera.cpp delete mode 100644 tests/animation2.cpp create mode 100644 tests/camera.cpp diff --git a/assets/cameras.json b/assets/cameras.json new file mode 100644 index 0000000..9a0115a --- /dev/null +++ b/assets/cameras.json @@ -0,0 +1,74 @@ +{ + "pan": { + "_type": "Animation", + "easing": 2, + "motion": 7, + "ease_rate": 0.05, + "min_x": 0, + "min_y": 0, + "max_x": 150.0, + "max_y": 0.0, + "simple": true, + "frames": 1, + "speed": 0.01, + "scaled": false, + "stationary": true, + "toggled": false, + "flipped": false, + "looped": false + }, + "shake": { + "_type": "Animation", + "easing": 6, + "motion": 1, + "ease_rate": 0.05, + "min_x": -10.0, + "min_y": -10.0, + "max_x": 10.0, + "max_y": 10.0, + "simple": true, + "frames": 1, + "speed": 0.01, + "scaled": false, + "stationary": true, + "toggled": false, + "flipped": false, + "looped": false + }, + "dolly": { + "_type": "Animation", + "easing": 2, + "motion": 0, + "ease_rate": 0.05, + "min_x": 0.8, + "min_y": 0.8, + "max_x": 1.3, + "max_y": 1.3, + "simple": true, + "frames": 1, + "speed": 0.01, + "scaled": true, + "stationary": true, + "toggled": false, + "flipped": false, + "looped": false + }, + "bounce": { + "_type": "Animation", + "easing": 3, + "motion": 2, + "ease_rate": 0.05, + "min_x": -10, + "min_y": -10, + "max_x": 10, + "max_y": 10, + "simple": true, + "frames": 1, + "speed": 0.01, + "scaled": false, + "stationary": true, + "toggled": false, + "flipped": false, + "looped": false + } +} diff --git a/boss/fight.cpp b/boss/fight.cpp index 61c2e9c..c287a79 100644 --- a/boss/fight.cpp +++ b/boss/fight.cpp @@ -87,7 +87,7 @@ namespace boss { const std::string& player_pos = run % 10 < 5 ? "player1" : "player2"; $ui.move_actor("player", player_pos); $ui.zoom(player_pos); - $ui.$zoom_anim.play(); + $ui.$camera.play(); int attack_id = std::any_cast(data); boss::System::combat(attack_id); state(State::PLAYER_TURN); diff --git a/boss/ui.cpp b/boss/ui.cpp index 865c4b7..afe82e4 100644 --- a/boss/ui.cpp +++ b/boss/ui.cpp @@ -12,10 +12,10 @@ namespace boss { $combat_ui(true), $arena(scene), $view_texture({BOSS_VIEW_WIDTH, BOSS_VIEW_HEIGHT}), - $view_sprite($view_texture.getTexture()), - $zoom_anim(animation::load("test_zoom")) + $view_sprite($view_texture.getTexture()) { $view_sprite.setPosition({BOSS_VIEW_X, BOSS_VIEW_Y}); + $camera.style("bounce"); } void UI::init() { @@ -45,7 +45,7 @@ namespace boss { $actions.render(window); $combat_ui.render(window); - if($zoom_anim.playing) { + if($camera.playing()) { zoom("player2"); } @@ -80,12 +80,12 @@ namespace boss { if(cell_name == "") { sf::View zoom{{BOSS_VIEW_WIDTH/2,BOSS_VIEW_HEIGHT/2}, {BOSS_VIEW_WIDTH, BOSS_VIEW_HEIGHT}}; $view_texture.setView(zoom); - } else if($zoom_anim.playing) { + } else if($camera.playing()) { auto& cell = $arena.$ui.cell_for(cell_name); - sf::Vector2f pos{float(cell.x/2), float(cell.y/2)}; - sf::View zoom; - $zoom_anim.apply(zoom, pos, {BOSS_VIEW_WIDTH/2, BOSS_VIEW_HEIGHT/2}); - $view_texture.setView(zoom); + + $camera.resize({BOSS_VIEW_WIDTH/2, BOSS_VIEW_HEIGHT/2}); + $camera.move($view_texture, + {float(cell.x/2), float(cell.y/2)}); } $view_sprite.setPosition({BOSS_VIEW_X, BOSS_VIEW_Y}); diff --git a/boss/ui.hpp b/boss/ui.hpp index 6a63eb9..66ecc39 100644 --- a/boss/ui.hpp +++ b/boss/ui.hpp @@ -3,6 +3,7 @@ #include #include "gui/combat_ui.hpp" #include "scene.hpp" +#include "camera.hpp" namespace components { struct Animation; @@ -18,7 +19,7 @@ namespace boss { guecs::UI $actions; sf::RenderTexture $view_texture; sf::Sprite $view_sprite; - components::Animation $zoom_anim; + cinematic::Camera $camera; UI(components::AnimatedScene &scene, DinkyECS::Entity boss_id); diff --git a/camera.cpp b/camera.cpp new file mode 100644 index 0000000..0b7cf09 --- /dev/null +++ b/camera.cpp @@ -0,0 +1,56 @@ +#include "camera.hpp" +#include +#include "animation.hpp" +#include +#include "components.hpp" +#include "config.hpp" + +namespace cinematic { + using components::Animation, std::string; + + struct CameraManager { + std::unordered_map animations; + }; + + static CameraManager MGR; + static bool initialized = false; + + void init() { + if(!initialized) { + auto cameras = settings::get("cameras"); + for(auto &[name, data] : cameras.json().items()) { + auto anim = components::convert(data); + MGR.animations.try_emplace(name, anim); + } + + initialized = true; + } + } + + Camera::Camera() : + anim(MGR.animations.at("pan")) + { + } + + void Camera::resize(sf::Vector2f to) { + size = to; + } + + void Camera::style(const std::string &name) { + anim = MGR.animations.at(name); + } + + void Camera::move(sf::RenderTexture& target, sf::Vector2f pos) { + sf::View zoom; + anim.apply(zoom, pos, size); + target.setView(zoom); + } + + bool Camera::playing() { + return anim.playing; + } + + void Camera::play() { + anim.play(); + } +} diff --git a/camera.hpp b/camera.hpp index 6f70f09..38d5757 100644 --- a/camera.hpp +++ b/camera.hpp @@ -1 +1,21 @@ #pragma once +#include "components.hpp" +#include + +namespace cinematic { + struct Camera { + components::Animation anim; + sf::View view; + sf::Vector2f size; + + Camera(); + + void resize(sf::Vector2f size); + void move(sf::RenderTexture& target, sf::Vector2f pos); + bool playing(); + void play(); + void style(const std::string &name); + }; + + void init(); +} diff --git a/main.cpp b/main.cpp index e30fb33..decf45e 100644 --- a/main.cpp +++ b/main.cpp @@ -8,6 +8,7 @@ #include "shaders.hpp" #include "backend.hpp" #include "game_level.hpp" +#include "camera.hpp" int main(int argc, char* argv[]) { try { @@ -16,6 +17,7 @@ int main(int argc, char* argv[]) { guecs::init(&backend); ai::init("ai"); animation::init(); + cinematic::init(); GameDB::init(); if(DEBUG_BUILD) sound::mute(true); diff --git a/meson.build b/meson.build index 260c1ab..46e53a7 100644 --- a/meson.build +++ b/meson.build @@ -89,6 +89,7 @@ sources = [ 'boss/fight.cpp', 'boss/system.cpp', 'boss/ui.cpp', + 'camera.cpp', 'combat.cpp', 'components.cpp', 'config.cpp', @@ -132,9 +133,9 @@ sources = [ executable('runtests', sources + [ 'tests/ai.cpp', 'tests/animation.cpp', - 'tests/animation2.cpp', 'tests/base.cpp', 'tests/battle.cpp', + 'tests/camera.cpp', 'tests/components.cpp', 'tests/config.cpp', 'tests/dbc.cpp', diff --git a/tests/animation2.cpp b/tests/animation2.cpp deleted file mode 100644 index 3d96f42..0000000 --- a/tests/animation2.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include -#include -#include -#include -#include -#include "rand.hpp" -#include -#include -#include "components.hpp" -#include -#include "stats.hpp" -#include "simplefsm.hpp" -#include "textures.hpp" -#include "animation.hpp" - -using namespace std::chrono_literals; -using components::Animation; -using TheClock = std::chrono::steady_clock; -using TimeDelta = TheClock::duration; -using TimePoint = std::chrono::time_point; -const TimeDelta MIN_TICK = 200ms; - -struct AnimationState { - Animation anim; - TimeDelta wait = 1s; - sf::Vector2f scale{1.0, 1.0}; - sf::Vector2f pos{0.0, 0.0}; - sf::IntRect rect{{0, 0}, {300, 300}}; - - void step() { - anim.step(scale, pos, rect); - } - - void apply(textures::SpriteTexture& st) { - anim.apply(*st.sprite, pos); - } -}; - -struct AnimationQueue { - std::vector active; - TimePoint last_tick; - TimeDelta wait_for = MIN_TICK; - size_t in_queue = 0; - size_t chunk = 1000; - - TimeDelta delta() { - return TheClock::now() - last_tick; - } - - void tick() { - last_tick = TheClock::now(); - } - - size_t add(Animation& anim, int initial_wait) { - active.emplace_back(anim, TheClock::duration(initial_wait)); - return active.size() - 1; - } - - AnimationState& get(size_t id) { - return active.at(id); - } - - void render() { - wait_for = MIN_TICK; - auto dt = delta(); - - for(size_t i = 0; i < chunk; i++) { - in_queue++; - auto& a = active[in_queue % active.size()]; - - if(!a.anim.playing) continue; - - if(a.wait < wait_for) { - wait_for = a.wait; - } - - if(a.wait < dt) { - // fmt::println("play animation: {} total ", active.size()); - a.step(); - } - } - } -}; - -TEST_CASE("simple coroutine animation test", "[coro]") { - AnimationQueue queue; - Animation anim; - anim.play(); - - for(int i = 0; i < 100; i++) { - int time = Random::uniform(16, 32); - size_t id = queue.add(anim, time); - auto& what = queue.get(id); - - REQUIRE(what.wait == TheClock::duration(time)); - } - - dbc::check(queue.active.size() > 0, "zero size queue after adding is impossible."); - queue.tick(); - Stats stats; - - for(int i = 0; i < 10000; i++) { - auto start = stats.time_start(); - auto delta = queue.delta(); - - queue.render(); - - if(delta > queue.wait_for) { - queue.add(anim, Random::uniform(100, 5000)); - queue.tick(); - } - - stats.sample_time(start); - } - - fmt::print("stdev: {}, mean: {}\r", stats.stddev(), stats.mean()); -} diff --git a/tests/camera.cpp b/tests/camera.cpp new file mode 100644 index 0000000..dea5f16 --- /dev/null +++ b/tests/camera.cpp @@ -0,0 +1,6 @@ +#include +#include + +TEST_CASE("view based camera system", "[camera]") { + REQUIRE(1 == 1); +} diff --git a/tools/arena.cpp b/tools/arena.cpp index b5fe7f0..6a0dabd 100644 --- a/tools/arena.cpp +++ b/tools/arena.cpp @@ -13,6 +13,7 @@ #include "events.hpp" #include "constants.hpp" #include "gui/event_router.hpp" +#include "camera.hpp" void craft_weapon() { auto world = GameDB::current_world(); @@ -28,6 +29,7 @@ int main(int, char*[]) { ai::init("ai"); animation::init(); GameDB::init(); + cinematic::init(); sf::RenderWindow window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Bossfight Testing Arena"); window.setVerticalSyncEnabled(VSYNC);