Crumbs from eaten food

This commit is contained in:
shweet committed 2026-11-13 00:21:29 -05:00
1 parent 8b343d5a4a
commit 7e27ffad94
3 files changed
+105 -36

No files matched your search

+97 -36
View File
@@ -1,6 +1,15 @@
#include "play.h" #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 float HOLD_SPIN = 0.1f;
constexpr int CRUMB_COUNT = 6;
constexpr float ROTATION_GROUND_DAMPING = 0.85f; constexpr float ROTATION_GROUND_DAMPING = 0.85f;
constexpr float ROTATION_MAX = 90; constexpr float ROTATION_MAX = 90;
constexpr float ROTATION_RETURN_SPEED = 120; 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; 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. // Forgets which item is being eaten, and lets go of the held one unless asked to keep it.
void Items::drop(Hold hold) void Items::drop(Hold hold)
{ {
@@ -175,6 +255,7 @@ void Items::update(Play& play, Pointer pointer)
stats.digestionRate = std::clamp(stats.digestionRate + item.digestionBonus, stats.digestionRate = std::clamp(stats.digestionRate + item.digestionBonus,
traits.digestionRateMin, traits.digestionRateMax); traits.digestionRateMin, traits.digestionRateMax);
stats.totalConsumed++; stats.totalConsumed++;
crumbs_spawn(entity);
removed = held; removed = held;
drop(); drop();
feeding.animation = body.actor.animation; feeding.animation = body.actor.animation;
@@ -224,20 +305,11 @@ void Items::update(Play& play, Pointer pointer)
int moved = -1; int moved = -1;
bool isTargetLocked = pointer.target >= 0 && pointer.target < (int)list.size(); 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++) for (int i = 0; i < (int)list.size(); i++)
{ {
ItemEntity& entity = list[i]; ItemEntity& entity = list[i];
Vec2& position = entity.actor.position; 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); entity.actor.update(audio);
position = position + velocity;
rotation += entity.angularVelocity;
if (is_point_in_rect(entity.actor.bounds_get(), pointer.position) && !pointer.isCaptured && if (is_point_in_rect(entity.actor.bounds_get(), pointer.position) && !pointer.isCaptured &&
(!isTargetLocked || i == pointer.target)) (!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) *entity.actor.rotation += entity.angularVelocity;
{ position = {std::clamp(position.x, bounds.x, bounds.x + bounds.w),
position.x = position.x <= bounds.x ? bounds.x + 1 : right - 1; std::clamp(position.y, bounds.y, bounds.y + bounds.h)};
velocity.x = -velocity.x * area.friction; }
entity.angularVelocity = -entity.angularVelocity * area.friction; else if (motion_update(play, entity))
rotation = -rotation;
audio.play(character.sounds[SOUND_BOUNCE]); audio.play(character.sounds[SOUND_BOUNCE]);
} }
if (position.y <= bounds.y || position.y >= bottom)
for (int i = 0; i < (int)crumbs.size(); i++)
{ {
if (position.y >= bottom && velocity.y <= gravity) ItemEntity& crumb = crumbs[i];
if (--crumb.life <= 0)
{ {
position.y = bottom; crumbs.erase(crumbs.begin() + i--);
velocity = {velocity.x * area.friction, 0}; continue;
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 motion_update(play, crumb);
{ float shrink = std::min(1.0f, crumb.life / (CRUMB_LIFE * CRUMB_SHRINK * TICK_RATE));
velocity.y = -velocity.y; for (LayerChange& layer : crumb.actor.layers)
entity.angularVelocity *= area.friction; layer.scale = Vec2{shrink - 1, shrink - 1} * PERCENT;
audio.play(character.sounds[SOUND_BOUNCE]);
}
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)};
} }
if (moved >= 0 && moved < (int)list.size() - 1) if (moved >= 0 && moved < (int)list.size() - 1)
+4
View File
@@ -273,8 +273,12 @@ void Play::render(Renderer& renderer, Vec2 screenSize)
area.render(renderer, view); area.render(renderer, view);
body.actor.render(renderer, view); body.actor.render(renderer, view);
if (!text.isScene) if (!text.isScene)
{
for (ItemEntity& entity : items.list) for (ItemEntity& entity : items.list)
entity.actor.render(renderer, view); entity.actor.render(renderer, view);
for (ItemEntity& crumb : items.crumbs)
crumb.actor.render(renderer, view);
}
if (autofeed.isActive) if (autofeed.isActive)
autoCursor.render(renderer, view); autoCursor.render(renderer, view);
for (Actor& particle : particles) for (Actor& particle : particles)
+4
View File
@@ -50,12 +50,14 @@ struct Pointer
bool isRightClicked = false; bool isRightClicked = false;
}; };
// An item in the world, or a crumb of one (which has a life, in ticks).
struct ItemEntity struct ItemEntity
{ {
Actor actor; Actor actor;
Vec2 velocity; Vec2 velocity;
float angularVelocity = 0; float angularVelocity = 0;
int id = 0; int id = 0;
int life = 0;
}; };
struct Toast struct Toast
@@ -116,6 +118,7 @@ struct Feeding
class Items class Items
{ {
public: public:
std::vector<ItemEntity> crumbs;
std::vector<ItemEntity> list; std::vector<ItemEntity> list;
std::vector<int> spawnQueue; std::vector<int> spawnQueue;
std::vector<bool> discovered; std::vector<bool> discovered;
@@ -130,6 +133,7 @@ public:
void update(Play& play, Pointer pointer); void update(Play& play, Pointer pointer);
void add(Play& play, int id, Vec2 position, Vec2 velocity = {}, float rotation = 0); void add(Play& play, int id, Vec2 position, Vec2 velocity = {}, float rotation = 0);
void drop(Hold hold = HOLD_RELEASE); 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 id);
void spawn_queue_add(Play& play); void spawn_queue_add(Play& play);
int spawn_delay_get(Play& play); int spawn_delay_get(Play& play);