From 423128724982b00edc01d48b247cb063ff7d02bf Mon Sep 17 00:00:00 2001 From: M Stoeckl Date: Mon, 24 Jun 2024 19:33:59 -0400 Subject: [PATCH] Smooth badguy and other object motion when frame prediction is on This commit extrapolates the drawn position for badguys and some other moving objects, making their motion look smoother when both the frame rate is high and frame prediction is on. This is already being done for Tux, but that is not enough to always produce the illusion of smooth motion. Without this commit one can at high frame rates see choppy motion on carried objects when Tux is running, and on quickly moving badguys, like MrIceBlock. --- src/badguy/badguy.cpp | 19 ++++++++++++------- src/badguy/crusher.cpp | 9 +++++---- src/badguy/mrbomb.cpp | 2 ++ src/badguy/mriceblock.cpp | 2 ++ src/badguy/rcrystallo.cpp | 3 ++- src/badguy/snail.cpp | 2 ++ src/object/player.cpp | 1 + src/object/rock.cpp | 9 +++++++++ src/object/rock.hpp | 1 + src/object/smoke_cloud.cpp | 3 ++- src/object/sprite_particle.cpp | 7 ++++--- 11 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/badguy/badguy.cpp b/src/badguy/badguy.cpp index a3ee560d57c..1de57d6aa51 100644 --- a/src/badguy/badguy.cpp +++ b/src/badguy/badguy.cpp @@ -31,6 +31,7 @@ #include "object/water_drop.hpp" #include "sprite/sprite.hpp" #include "sprite/sprite_manager.hpp" +#include "supertux/constants.hpp" #include "supertux/level.hpp" #include "supertux/sector.hpp" #include "supertux/tile.hpp" @@ -141,10 +142,13 @@ BadGuy::draw(DrawingContext& context) { if (!m_sprite.get()) return; + Vector draw_offset = context.get_time_offset() * m_physic.get_velocity(); + Vector draw_pos = get_pos() + draw_offset; + if (m_state == STATE_INIT || m_state == STATE_INACTIVE) { if (Editor::is_active()) { - m_sprite->draw(context.color(), get_pos(), m_layer, m_flip); + m_sprite->draw(context.color(), draw_pos, m_layer, m_flip); } } else @@ -153,27 +157,27 @@ BadGuy::draw(DrawingContext& context) { context.push_transform(); context.set_flip(context.get_flip() ^ VERTICAL_FLIP); - m_sprite->draw(context.color(), get_pos(), m_layer, m_flip); + m_sprite->draw(context.color(), draw_pos, m_layer, m_flip); context.pop_transform(); } else { if (m_unfreeze_timer.started() && m_unfreeze_timer.get_timeleft() <= 1.f) { - m_sprite->draw(context.color(), get_pos() + Vector(graphicsRandom.randf(-3, 3), 0.f), m_layer-1, m_flip); + m_sprite->draw(context.color(), draw_pos + Vector(graphicsRandom.randf(-3, 3), 0.f), m_layer - 1, m_flip); if (is_portable()) - m_freezesprite->draw(context.color(), get_pos() + Vector(graphicsRandom.randf(-3, 3), 0.f), m_layer); + m_freezesprite->draw(context.color(), draw_pos + Vector(graphicsRandom.randf(-3, 3), 0.f), m_layer); } else { if (m_frozen && is_portable()) - m_freezesprite->draw(context.color(), get_pos(), m_layer); - m_sprite->draw(context.color(), get_pos(), m_layer - (m_frozen ? 1 : 0), m_flip); + m_freezesprite->draw(context.color(), draw_pos, m_layer); + m_sprite->draw(context.color(), draw_pos, m_layer - (m_frozen ? 1 : 0), m_flip); } if (m_glowing) { - m_lightsprite->draw(context.light(), m_col.m_bbox.get_middle(), 0); + m_lightsprite->draw(context.light(), m_col.m_bbox.get_middle() + draw_offset, 0); } } } @@ -892,6 +896,7 @@ BadGuy::grab(MovingObject& object, const Vector& pos, Direction dir_) { Portable::grab(object, pos, dir_); m_col.set_movement(pos - get_pos()); + m_physic.set_velocity(m_col.get_movement() * LOGICAL_FPS); m_dir = dir_; if (m_frozen) { diff --git a/src/badguy/crusher.cpp b/src/badguy/crusher.cpp index 0dfb20407ad..c0053e380cc 100644 --- a/src/badguy/crusher.cpp +++ b/src/badguy/crusher.cpp @@ -468,14 +468,15 @@ Crusher::spawn_roots(Direction direction) void Crusher::draw(DrawingContext& context) { - m_sprite->draw(context.color(), get_pos(), m_layer + 2, m_flip); + Vector draw_pos = get_pos() + m_physic.get_velocity() * context.get_time_offset(); + m_sprite->draw(context.color(), draw_pos, m_layer + 2, m_flip); if (m_sprite->has_action("whites")) { // Draw crusher's eyes slightly behind. - m_lefteye->draw(context.color(), get_pos() + eye_position(false), m_layer + 1, m_flip); - m_righteye->draw(context.color(), get_pos() + eye_position(true), m_layer + 1, m_flip); + m_lefteye->draw(context.color(), draw_pos + eye_position(false), m_layer + 1, m_flip); + m_righteye->draw(context.color(), draw_pos + eye_position(true), m_layer + 1, m_flip); // Draw the whites of crusher's eyes even further behind. - m_whites->draw(context.color(), get_pos(), m_layer, m_flip); + m_whites->draw(context.color(), draw_pos, m_layer, m_flip); } } diff --git a/src/badguy/mrbomb.cpp b/src/badguy/mrbomb.cpp index ceebd31f5d4..d119aa2d2ef 100644 --- a/src/badguy/mrbomb.cpp +++ b/src/badguy/mrbomb.cpp @@ -26,6 +26,7 @@ #include "object/portable.hpp" #include "sprite/sprite.hpp" #include "sprite/sprite_manager.hpp" +#include "supertux/constants.hpp" #include "supertux/sector.hpp" #include "util/reader_mapping.hpp" @@ -242,6 +243,7 @@ MrBomb::grab(MovingObject& object, const Vector& pos, Direction dir_) set_action(dir_); m_col.set_movement(pos - get_pos()); + m_physic.set_velocity(m_col.get_movement() * LOGICAL_FPS); m_dir = dir_; set_colgroup_active(COLGROUP_DISABLED); } diff --git a/src/badguy/mriceblock.cpp b/src/badguy/mriceblock.cpp index 41cf220861f..9b524fac442 100644 --- a/src/badguy/mriceblock.cpp +++ b/src/badguy/mriceblock.cpp @@ -24,6 +24,7 @@ #include "object/player.hpp" #include "object/portable.hpp" #include "sprite/sprite.hpp" +#include "supertux/constants.hpp" #include "supertux/sector.hpp" namespace { @@ -356,6 +357,7 @@ MrIceBlock::grab(MovingObject& object, const Vector& pos, Direction dir_) Portable::grab(object, pos, dir_); m_col.set_movement(pos - get_pos()); + m_physic.set_velocity(m_col.get_movement() * LOGICAL_FPS); m_dir = dir_; set_action("flat", m_dir, /* loops = */ -1); set_state(ICESTATE_GRABBED); diff --git a/src/badguy/rcrystallo.cpp b/src/badguy/rcrystallo.cpp index ad56180a9e2..367c61dcb4b 100644 --- a/src/badguy/rcrystallo.cpp +++ b/src/badguy/rcrystallo.cpp @@ -148,7 +148,8 @@ void RCrystallo::draw(DrawingContext& context) { context.push_transform(); - m_sprite->draw(context.color(), get_pos(), m_layer); + Vector draw_pos = get_pos() + m_physic.get_velocity() * context.get_time_offset(); + m_sprite->draw(context.color(), draw_pos, m_layer); context.pop_transform(); } diff --git a/src/badguy/snail.cpp b/src/badguy/snail.cpp index b800b86bc91..c0f017d5d1b 100644 --- a/src/badguy/snail.cpp +++ b/src/badguy/snail.cpp @@ -23,6 +23,7 @@ #include "object/player.hpp" #include "object/portable.hpp" #include "sprite/sprite.hpp" +#include "supertux/constants.hpp" #include "supertux/sector.hpp" namespace { @@ -390,6 +391,7 @@ Snail::grab(MovingObject& object, const Vector& pos, Direction dir_) if (m_frozen) BadGuy::grab(object, pos, dir_); m_col.set_movement(pos - get_pos()); + m_physic.set_velocity(m_col.get_movement() * LOGICAL_FPS); m_dir = dir_; if (!m_frozen) { diff --git a/src/object/player.cpp b/src/object/player.cpp index 168a2a35244..276bf6dc647 100644 --- a/src/object/player.cpp +++ b/src/object/player.cpp @@ -2017,6 +2017,7 @@ Player::draw(DrawingContext& context) // if Tux is above camera, draw little "air arrow" to show where he is x-wise if (m_col.m_bbox.get_bottom() - 16 < Sector::get().get_camera().get_translation().y) { float px = m_col.m_bbox.get_left() + (m_col.m_bbox.get_right() - m_col.m_bbox.get_left() - static_cast(m_airarrow.get()->get_width())) / 2.0f; + px += context.get_time_offset() * m_physic.get_velocity().x; float py = Sector::get().get_camera().get_translation().y; py += std::min(((py - (m_col.m_bbox.get_bottom() + 16)) / 4), 16.0f); context.color().draw_surface(m_airarrow, Vector(px, py), LAYER_HUD - 1); diff --git a/src/object/rock.cpp b/src/object/rock.cpp index 2e200d4c91c..13707afc5a0 100644 --- a/src/object/rock.cpp +++ b/src/object/rock.cpp @@ -24,6 +24,7 @@ #include "object/lit_object.hpp" #include "object/pushbutton.hpp" #include "object/trampoline.hpp" +#include "supertux/constants.hpp" #include "supertux/sector.hpp" #include "supertux/tile.hpp" #include "object/player.hpp" @@ -229,6 +230,7 @@ Rock::grab(MovingObject& object, const Vector& pos, Direction dir_) Portable::grab(object, pos, dir_); Vector movement = pos - get_pos(); m_col.set_movement(movement); + physic.set_velocity(movement * LOGICAL_FPS); last_movement = movement; set_group(COLGROUP_TOUCHABLE); //needed for lanterns catching willowisps on_ground = false; @@ -272,6 +274,13 @@ Rock::ungrab(MovingObject& object, Direction dir) Portable::ungrab(object, dir); } +void +Rock::draw(DrawingContext& context) +{ + Vector offset = physic.get_velocity() * context.get_time_offset(); + m_sprite->draw(context.color(), get_pos() + offset, m_layer, m_flip); +} + ObjectSettings Rock::get_settings() { diff --git a/src/object/rock.hpp b/src/object/rock.hpp index f38b9cd7c90..3ac5d20c56b 100644 --- a/src/object/rock.hpp +++ b/src/object/rock.hpp @@ -44,6 +44,7 @@ class Rock : public MovingSprite, virtual ObjectSettings get_settings() override; virtual GameObjectTypes get_types() const override; std::string get_default_sprite_name() const override; + void draw(DrawingContext& context) override; /** Adds velocity from wind */ virtual void add_wind_velocity(const Vector& velocity, const Vector& end_speed); diff --git a/src/object/smoke_cloud.cpp b/src/object/smoke_cloud.cpp index d27f395aaf2..88f94bd51d5 100644 --- a/src/object/smoke_cloud.cpp +++ b/src/object/smoke_cloud.cpp @@ -39,7 +39,8 @@ SmokeCloud::update(float dt_sec) void SmokeCloud::draw(DrawingContext& context) { - sprite->draw(context.color(), position, LAYER_OBJECTS + 1); + Vector draw_pos = position - Vector(0.0, 120.0) * std::min(context.get_time_offset(), timer.get_timeleft()); + sprite->draw(context.color(), draw_pos, LAYER_OBJECTS + 1); } /* EOF */ diff --git a/src/object/sprite_particle.cpp b/src/object/sprite_particle.cpp index 634dc0e3845..ae7387a511c 100644 --- a/src/object/sprite_particle.cpp +++ b/src/object/sprite_particle.cpp @@ -99,13 +99,14 @@ SpriteParticle::update(float dt_sec) void SpriteParticle::draw(DrawingContext& context) { - sprite->draw(context.color(), position, drawing_layer); + Vector draw_pos = position + velocity * context.get_time_offset(); + sprite->draw(context.color(), draw_pos, drawing_layer); //Sparkles glow in the dark if (glow) { - sprite->draw(context.light(), position, drawing_layer); - lightsprite->draw(context.light(), position + Vector(12, 12), 0); + sprite->draw(context.light(), draw_pos, drawing_layer); + lightsprite->draw(context.light(), draw_pos + Vector(12, 12), 0); } }