From 71e3c97cf0932c88e8906930c54dec7579eac5b4 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sun, 19 Oct 2025 00:47:28 -0400 Subject: [PATCH] Arena works better now and I can give a list of sprites to work as fixtures in a scene. --- assets/bosses.json | 10 ++-- assets/enemies.json | 10 ++-- boss/ui.cpp | 37 ++++++++------ boss/ui.hpp | 12 +++-- components.cpp | 1 + components.hpp | 16 +++--- systems.cpp | 2 +- tests/animation2.cpp | 117 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 169 insertions(+), 36 deletions(-) create mode 100644 tests/animation2.cpp diff --git a/assets/bosses.json b/assets/bosses.json index bdfe8ac..19314af 100644 --- a/assets/bosses.json +++ b/assets/bosses.json @@ -14,11 +14,15 @@ "boss": { "start_pos": "boss5", "scale": 0.6, - "mid_cell": true - } + "mid_cell": true, + "sprite": "rat_king_boss" + }, + "fixtures": [ + {"name": "torch_crappy", "scale": 0.8, "cell": "torch1"}, + {"name": "torch_crappy", "scale": 0.8, "cell": "torch2"} + ] }, {"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false}, - {"_type": "Sprite", "name": "rat_king_boss", "width": 720, "height": 720, "scale": 0.8, "stationary": true}, {"_type": "Sound", "attack": "Marmot_Scream_1", "death": "Creature_Death_1"} ] } diff --git a/assets/enemies.json b/assets/enemies.json index 6b209e6..b9f7c83 100644 --- a/assets/enemies.json +++ b/assets/enemies.json @@ -23,7 +23,7 @@ {"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}, {"_type": "Personality", "hearing_distance": 5, "tough": false}, - {"_type": "Sprite", "name": "gold_savior", "width": 256, "height": 256, "width": 256, "height": 256, "scale": 1.0}, + {"_type": "Sprite", "name": "gold_savior", "scale": 1.0}, {"_type": "Sound", "attack": "Sword_Hit_2", "death": "Humanoid_Death_1"} ] }, @@ -38,7 +38,7 @@ {"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}, {"_type": "Personality", "hearing_distance": 5, "tough": true}, - {"_type": "Sprite", "name": "armored_knight", "width": 256, "height": 256, "width": 256, "height": 256, "scale": 1.0}, + {"_type": "Sprite", "name": "armored_knight", "scale": 1.0}, {"_type": "Sound", "attack": "Sword_Hit_2", "death": "Humanoid_Death_1"} ] }, @@ -53,7 +53,7 @@ {"_type": "Motion", "dx": 0, "dy": 0, "random": true}, {"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}, {"_type": "Personality", "hearing_distance": 5, "tough": true}, - {"_type": "Sprite", "name": "axe_ranger", "width": 256, "height": 256, "scale": 1.0}, + {"_type": "Sprite", "name": "axe_ranger", "scale": 1.0}, {"_type": "Sound", "attack": "Sword_Hit_2", "death": "Ranger_1"} ] }, @@ -68,7 +68,7 @@ {"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}, {"_type": "Personality", "hearing_distance": 5, "tough": false}, - {"_type": "Sprite", "name": "rat_with_sword", "width": 256, "height": 256, "scale": 1.0}, + {"_type": "Sprite", "name": "rat_with_sword", "scale": 1.0}, {"_type": "Sound", "attack": "Small_Rat", "death": "Creature_Death_1"} ] }, @@ -83,7 +83,7 @@ {"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}, {"_type": "Personality", "hearing_distance": 5, "tough": true}, - {"_type": "Sprite", "name": "hairy_spider", "width": 256, "height": 256, "scale": 1.0}, + {"_type": "Sprite", "name": "hairy_spider", "scale": 1.0}, {"_type": "Sound", "attack": "Spider_1", "death": "Spider_2"} ] } diff --git a/boss/ui.cpp b/boss/ui.cpp index c924101..26db949 100644 --- a/boss/ui.cpp +++ b/boss/ui.cpp @@ -15,8 +15,8 @@ namespace boss { $scene(world->get($boss_id)), $combat_ui(true) { - auto& sprite = $world->get($boss_id); - $boss_sprite = textures::get_sprite(sprite.name); + std::string sprite_name = $scene.boss["sprite"]; + $boss_sprite = textures::get_sprite(sprite_name); // floor is std::optional if($scene.floor) { @@ -25,14 +25,21 @@ namespace boss { $player_sprite = textures::get_sprite($scene.player["sprite"]); - dbc::check(animation::has(sprite.name), "add boss animation to animations.json"); - $boss_anim = animation::load(sprite.name); + dbc::check(animation::has(sprite_name), "add boss animation to animations.json"); + $boss_anim = animation::load(sprite_name); - $torch_left = textures::get_sprite("torch_crappy"); - $torch_left_anim = animation::load("torch_crappy"); - $torch_right.texture = $torch_left.texture; - $torch_right.sprite = std::make_shared(*$torch_right.texture); - $torch_right_anim = animation::load("torch_crappy"); + for(auto& fixture : $scene.fixtures) { + std::string name = fixture["name"]; + auto st = textures::get_sprite(name); + // clone the sprite so it can be positioned + st.sprite = std::make_shared(*st.texture); + + auto anim = animation::load(name); + float scale = fixture["scale"]; + std::string cell = fixture["cell"]; + + $fixtures.emplace_back(st, anim, cell, scale); + } } void UI::init() { @@ -57,8 +64,9 @@ namespace boss { position_sprite($floor_sprite, $scene.floor_pos, 1.0, false); } - position_sprite($torch_left, "torch1", 1.0, false); - position_sprite($torch_right, "torch2", 1.0, false); + for(auto& fixture : $fixtures) { + position_sprite(fixture.st, fixture.cell, fixture.scale, false); + } $arena.init(); @@ -103,10 +111,9 @@ namespace boss { window.draw(*$boss_sprite.sprite); window.draw(*$player_sprite.sprite); - window.draw(*$torch_left.sprite); - window.draw(*$torch_right.sprite); - - // $arena.debug_layout(window); + for(auto& fixture : $fixtures) { + window.draw(*fixture.st.sprite); + } } bool UI::mouse(float x, float y, Modifiers mods) { diff --git a/boss/ui.hpp b/boss/ui.hpp index 3aef996..02d8584 100644 --- a/boss/ui.hpp +++ b/boss/ui.hpp @@ -7,6 +7,13 @@ #include "gui/combat_ui.hpp" #include "components.hpp" +struct AnimatedFixture { + textures::SpriteTexture st; + components::Animation anim; + std::string cell; + float scale; +}; + namespace boss { using std::shared_ptr; using namespace DinkyECS; @@ -25,10 +32,7 @@ namespace boss { components::Animation $boss_anim; sf::Vector2f $boss_pos; - SpriteTexture $torch_left; - SpriteTexture $torch_right; - components::Animation $torch_left_anim; - components::Animation $torch_right_anim; + std::vector $fixtures; UI(shared_ptr world, Entity boss_id); diff --git a/components.cpp b/components.cpp index db32b01..c8d6c83 100644 --- a/components.cpp +++ b/components.cpp @@ -1,4 +1,5 @@ #include "components.hpp" + #include "point.hpp" #include "easings.hpp" diff --git a/components.hpp b/components.hpp index 1ee3351..971a046 100644 --- a/components.hpp +++ b/components.hpp @@ -76,12 +76,18 @@ namespace components { int hp = 10; }; + struct Sprite { + string name; + float scale; + }; + struct BossFight { std::string background; std::optional floor; std::string floor_pos; json player; json boss; + json fixtures; }; struct Combat { @@ -105,12 +111,6 @@ namespace components { std::vector events; }; - struct Sprite { - string name; - int width; - int height; - }; - struct Sound { std::string attack; std::string death; @@ -149,8 +149,8 @@ namespace components { using ComponentMap = std::unordered_map; ENROLL_COMPONENT(Tile, display, foreground, background); - ENROLL_COMPONENT(BossFight, background, floor, floor_pos, player, boss); - ENROLL_COMPONENT(Sprite, name, width, height); + ENROLL_COMPONENT(BossFight, background, floor, floor_pos, player, boss, fixtures); + ENROLL_COMPONENT(Sprite, name, scale); ENROLL_COMPONENT(Curative, hp); ENROLL_COMPONENT(LightSource, strength, radius); ENROLL_COMPONENT(Position, location.x, location.y); diff --git a/systems.cpp b/systems.cpp index bb3631d..9e5beaf 100644 --- a/systems.cpp +++ b/systems.cpp @@ -669,7 +669,7 @@ void System::spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy) { auto effect = ritual.element == FIRE ? "burning_animation" : "lightning_animation"; auto effect_id = world.entity(); - world.set(effect_id, {effect, 256, 256}); + world.set(effect_id, {effect, 1.0f}); world.set(effect_id, {true}); auto shader = shaders::get(ritual.element == FIRE ? "flame" : "lightning"); diff --git a/tests/animation2.cpp b/tests/animation2.cpp new file mode 100644 index 0000000..d654c20 --- /dev/null +++ b/tests/animation2.cpp @@ -0,0 +1,117 @@ +#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) { + animation::apply(anim, *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()); +}