From 7e27ffad94868236dddabdc47e295467c6a6adcc Mon Sep 17 00:00:00 2001 From: shweet Date: Fri, 13 Nov 2026 00:21:29 -0500 Subject: [PATCH] Crumbs from eaten food --- src/items.cpp | 141 ++++++++++++++++++++++++++++++++++++-------------- src/play.cpp | 4 ++ src/play.h | 4 ++ 3 files changed, 109 insertions(+), 40 deletions(-) diff --git a/src/items.cpp b/src/items.cpp index 95399d2..b097aa1 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -1,6 +1,15 @@ #include "play.h" +constexpr float CRUMB_LIFE = 1.5f; +constexpr float CRUMB_SHRINK = 0.3f; +constexpr float CRUMB_SIZE_MAX = 0.35f; +constexpr float CRUMB_SIZE_MIN = 0.15f; +constexpr float CRUMB_SPEED_X = 4; +constexpr float CRUMB_SPEED_Y_MAX = 7; +constexpr float CRUMB_SPEED_Y_MIN = 2; +constexpr float CRUMB_SPIN = 15; constexpr float HOLD_SPIN = 0.1f; +constexpr int CRUMB_COUNT = 6; constexpr float ROTATION_GROUND_DAMPING = 0.85f; constexpr float ROTATION_MAX = 90; constexpr float ROTATION_RETURN_SPEED = 120; @@ -14,6 +23,77 @@ void Items::add(Play& play, int id, Vec2 position, Vec2 velocity, float rotation discovered[id] = true; } +// Bursts slices of an item's graphic from where it is, to fall and bounce like items until they shrink away. +void Items::crumbs_spawn(ItemEntity& source) +{ + for (int i = 0; i < CRUMB_COUNT; i++) + { + ItemEntity& crumb = crumbs.emplace_back(source); + float fraction = random_get(CRUMB_SIZE_MIN, CRUMB_SIZE_MAX); + for (LayerChange& layer : crumb.actor.layers) + { + if (!layer.isRegion) + continue; + Vec2 size = layer.size * fraction; + layer.crop = {layer.crop.x + random_get(0, layer.size.x - size.x), + layer.crop.y + random_get(0, layer.size.y - size.y)}; + layer.size = size; + layer.pivot = size / 2; + } + crumb.velocity = {random_get(-CRUMB_SPEED_X, CRUMB_SPEED_X), -random_get(CRUMB_SPEED_Y_MIN, CRUMB_SPEED_Y_MAX)}; + crumb.angularVelocity = random_get(-CRUMB_SPIN, CRUMB_SPIN); + crumb.life = (int)(CRUMB_LIFE * TICK_RATE); + } +} + +// Moves an item or crumb a tick under gravity, bouncing off the bounds and settling on the ground. True if it bounced. +static bool motion_update(Play& play, ItemEntity& entity) +{ + Area& area = play.character->area; + Rect bounds = play.bounds; + Vec2& position = entity.actor.position; + Vec2& velocity = entity.velocity; + float& rotation = *entity.actor.rotation; + float gravity = play.character->catalog.items[entity.id].gravity; + gravity = gravity != 0 ? gravity : area.gravity; + float right = bounds.x + bounds.w; + float bottom = bounds.y + bounds.h; + bool isBounced = false; + position = position + velocity; + rotation += entity.angularVelocity; + if (position.x <= bounds.x || position.x >= right) + { + position.x = position.x <= bounds.x ? bounds.x + 1 : right - 1; + velocity.x = -velocity.x * area.friction; + entity.angularVelocity = -entity.angularVelocity * area.friction; + rotation = -rotation; + isBounced = true; + } + if (position.y <= bounds.y || position.y >= bottom) + { + if (position.y >= bottom && velocity.y <= gravity) + { + position.y = bottom; + velocity = {velocity.x * area.friction, 0}; + rotation = std::remainder(rotation, 360.0f); + float step = ROTATION_RETURN_SPEED * TICK; + rotation = std::abs(rotation) <= step ? 0 : rotation - std::copysign(step, rotation); + entity.angularVelocity *= ROTATION_GROUND_DAMPING; + } + else + { + velocity.y = -velocity.y; + entity.angularVelocity *= area.friction; + isBounced = true; + } + velocity.y *= area.friction; + } + velocity.x *= area.airResistance; + velocity.y += gravity; + position = {std::clamp(position.x, bounds.x, right), std::clamp(position.y, bounds.y, bottom)}; + return isBounced; +} + // Forgets which item is being eaten, and lets go of the held one unless asked to keep it. void Items::drop(Hold hold) { @@ -175,6 +255,7 @@ void Items::update(Play& play, Pointer pointer) stats.digestionRate = std::clamp(stats.digestionRate + item.digestionBonus, traits.digestionRateMin, traits.digestionRateMax); stats.totalConsumed++; + crumbs_spawn(entity); removed = held; drop(); feeding.animation = body.actor.animation; @@ -224,20 +305,11 @@ void Items::update(Play& play, Pointer pointer) int moved = -1; bool isTargetLocked = pointer.target >= 0 && pointer.target < (int)list.size(); - float right = bounds.x + bounds.w; - float bottom = bounds.y + bounds.h; for (int i = 0; i < (int)list.size(); i++) { ItemEntity& entity = list[i]; Vec2& position = entity.actor.position; - Vec2& velocity = entity.velocity; - float& rotation = *entity.actor.rotation; - float gravity = character.catalog.items[entity.id].gravity; - gravity = gravity != 0 ? gravity : area.gravity; entity.actor.update(audio); - position = position + velocity; - rotation += entity.angularVelocity; - if (is_point_in_rect(entity.actor.bounds_get(), pointer.position) && !pointer.isCaptured && (!isTargetLocked || i == pointer.target)) { @@ -269,39 +341,28 @@ void Items::update(Play& play, Pointer pointer) } } - if (i != held) + if (i == held) { - if (position.x <= bounds.x || position.x >= right) - { - position.x = position.x <= bounds.x ? bounds.x + 1 : right - 1; - velocity.x = -velocity.x * area.friction; - entity.angularVelocity = -entity.angularVelocity * area.friction; - rotation = -rotation; - audio.play(character.sounds[SOUND_BOUNCE]); - } - if (position.y <= bounds.y || position.y >= bottom) - { - if (position.y >= bottom && velocity.y <= gravity) - { - position.y = bottom; - velocity = {velocity.x * area.friction, 0}; - rotation = std::remainder(rotation, 360.0f); - float step = ROTATION_RETURN_SPEED * TICK; - rotation = std::abs(rotation) <= step ? 0 : rotation - std::copysign(step, rotation); - entity.angularVelocity *= ROTATION_GROUND_DAMPING; - } - else - { - velocity.y = -velocity.y; - entity.angularVelocity *= area.friction; - audio.play(character.sounds[SOUND_BOUNCE]); - } - velocity.y *= area.friction; - } - velocity.x *= area.airResistance; - velocity.y += gravity; + *entity.actor.rotation += entity.angularVelocity; + position = {std::clamp(position.x, bounds.x, bounds.x + bounds.w), + std::clamp(position.y, bounds.y, bounds.y + bounds.h)}; } - position = {std::clamp(position.x, bounds.x, right), std::clamp(position.y, bounds.y, bottom)}; + else if (motion_update(play, entity)) + audio.play(character.sounds[SOUND_BOUNCE]); + } + + for (int i = 0; i < (int)crumbs.size(); i++) + { + ItemEntity& crumb = crumbs[i]; + if (--crumb.life <= 0) + { + crumbs.erase(crumbs.begin() + i--); + continue; + } + motion_update(play, crumb); + float shrink = std::min(1.0f, crumb.life / (CRUMB_LIFE * CRUMB_SHRINK * TICK_RATE)); + for (LayerChange& layer : crumb.actor.layers) + layer.scale = Vec2{shrink - 1, shrink - 1} * PERCENT; } if (moved >= 0 && moved < (int)list.size() - 1) diff --git a/src/play.cpp b/src/play.cpp index 8434ba2..be22cec 100644 --- a/src/play.cpp +++ b/src/play.cpp @@ -273,8 +273,12 @@ void Play::render(Renderer& renderer, Vec2 screenSize) area.render(renderer, view); body.actor.render(renderer, view); if (!text.isScene) + { for (ItemEntity& entity : items.list) entity.actor.render(renderer, view); + for (ItemEntity& crumb : items.crumbs) + crumb.actor.render(renderer, view); + } if (autofeed.isActive) autoCursor.render(renderer, view); for (Actor& particle : particles) diff --git a/src/play.h b/src/play.h index 6b3d81a..d56ab80 100644 --- a/src/play.h +++ b/src/play.h @@ -50,12 +50,14 @@ struct Pointer bool isRightClicked = false; }; +// An item in the world, or a crumb of one (which has a life, in ticks). struct ItemEntity { Actor actor; Vec2 velocity; float angularVelocity = 0; int id = 0; + int life = 0; }; struct Toast @@ -116,6 +118,7 @@ struct Feeding class Items { public: + std::vector crumbs; std::vector list; std::vector spawnQueue; std::vector discovered; @@ -130,6 +133,7 @@ public: void update(Play& play, Pointer pointer); void add(Play& play, int id, Vec2 position, Vec2 velocity = {}, float rotation = 0); void drop(Hold hold = HOLD_RELEASE); + void crumbs_spawn(ItemEntity& source); void spawn_queue_add(Play& play, int id); void spawn_queue_add(Play& play); int spawn_delay_get(Play& play);