Skip to content

Commit

Permalink
Tux emits bubbles while being underwater (#3014)
Browse files Browse the repository at this point in the history
This pr adds a visual effect - when Tux is underwater, he will emit bubble particles that will follow a sine pattern until they reach the water surface.

Co-authored-by: Brockengespenst <[email protected]>
  • Loading branch information
bruhmoent and Brockengespenst authored Oct 20, 2024
1 parent 9899876 commit 062dc80
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 1 deletion.
Binary file added data/images/particles/air_bubble-0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/images/particles/air_bubble-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/images/particles/air_bubble-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/images/particles/air_bubble-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/images/particles/air_bubble-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions data/images/particles/air_bubble.sprite
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(supertux-sprite
(action
(name "normal")
(fps 15)
(loops 1)
(images "air_bubble-0.png"
"air_bubble-1.png"
"air_bubble-2.png"
"air_bubble-3.png"
"air_bubble-4.png"
)
)
(action
(name "small")
(fps 15)
(loops 1)
(images "air_bubble-0.png"
"air_bubble-1.png"
"air_bubble-2.png"
"air_bubble-3.png"
)
)
)
91 changes: 90 additions & 1 deletion src/object/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ const float SWIM_BOOST_SPEED = 600.f;
const float SWIM_TO_BOOST_ACCEL = 15.f;
const float TURN_MAGNITUDE = 0.15f;
const float TURN_MAGNITUDE_BOOST = 0.2f;
const std::array<std::string, 2> BUBBLE_ACTIONS = { "normal", "small" };

/* Buttjump variables */

Expand Down Expand Up @@ -226,6 +227,7 @@ Player::Player(PlayerStatus& player_status, const std::string& name_, int player
m_swimming_accel_modifier(100.f),
m_water_jump(false),
m_airarrow(Surface::from_file("images/engine/hud/airarrow.png")),
m_bubbles_sprite(SpriteManager::current()->create("images/particles/air_bubble.sprite")),
m_floor_normal(0.0f, 0.0f),
m_ghost_mode(false),
m_unduck_hurt_timer(),
Expand Down Expand Up @@ -254,6 +256,8 @@ Player::Player(PlayerStatus& player_status, const std::string& name_, int player
SoundManager::current()->preload("sounds/invincible_start.ogg");
SoundManager::current()->preload("sounds/splash.wav");
SoundManager::current()->preload("sounds/grow.wav");
m_bubble_timer.start(3.0f + graphicsRandom.randf(2));

m_col.set_size(TUX_WIDTH, is_big() ? BIG_TUX_HEIGHT : SMALL_TUX_HEIGHT);

m_sprite->set_angle(0.0f);
Expand Down Expand Up @@ -412,6 +416,23 @@ Player::update(float dt_sec)
}
}

if (!m_active_bubbles.empty())
{
for (auto& bubble : m_active_bubbles)
{
bubble.second.y -= dt_sec * 40.0f;
bubble.second.x += std::sin(bubble.second.y * 0.1f) * dt_sec * 5.0f;
}

m_active_bubbles.remove_if([&](const std::pair<SpritePtr, Vector>& bubble)
{
Rectf bubble_box(bubble.second.x, bubble.second.y, bubble.second.x + 16.f, bubble.second.y + 16.f);
bool is_out_of_water = Sector::get().is_free_of_tiles(bubble_box, true, Tile::WATER);
bool hits_solid = !Sector::get().is_free_of_tiles(bubble_box, false, Tile::SOLID);
return is_out_of_water || hits_solid;
});
}

// Skip if in multiplayer respawn
if (is_dead() && m_target && Sector::get().get_object_count<Player>([this](const Player& p) { return p.is_active() && &p != this; }))
{
Expand Down Expand Up @@ -514,6 +535,71 @@ Player::update(float dt_sec)
if (m_physic.get_velocity_y() > -350.f && m_controller->hold(Control::UP))
m_physic.set_velocity_y(-350.f);
}

if (m_bubble_timer.check())
{
Vector beak_local_offset(30.f, 0.0f);
float big_offset_x = is_big() ? 4.0f : 0.0f;

// Calculate the offsets based on the sprite angle
float offset_x = std::cos(m_swimming_angle) * 10.0f;
float offset_y = std::sin(m_swimming_angle) * 10.0f;

// Rotate the beak offset based on the sprite's angle
float rotated_beak_offset_x = beak_local_offset.x * std::cos(m_swimming_angle) - beak_local_offset.y * std::sin(m_swimming_angle);
float rotated_beak_offset_y = beak_local_offset.x * std::sin(m_swimming_angle) + beak_local_offset.y * std::cos(m_swimming_angle);

Vector player_center = m_col.m_bbox.get_middle();
Vector beak_position;

// Determine direction based on the radians
if (std::abs(m_swimming_angle) > static_cast<float>(math::PI_2)) // Facing left
{
beak_position = player_center + Vector(rotated_beak_offset_x - big_offset_x * 2, rotated_beak_offset_y);
}
else // Facing right (including straight up or down)
{
beak_position = player_center + Vector(rotated_beak_offset_x - 4.0f + big_offset_x, rotated_beak_offset_y);
}

int num_bubbles = graphicsRandom.rand(1, 3);

for (int i = 0; i < num_bubbles; ++i)
{
int random_action_index = graphicsRandom.rand(0, 2);
std::string selected_action = BUBBLE_ACTIONS[random_action_index];

SpritePtr bubble_sprite = m_bubbles_sprite->clone();
bubble_sprite->set_animation_loops(1);
bubble_sprite->set_action(selected_action);

Vector bubble_pos(0.f, 0.f);
if (std::abs(m_swimming_angle) > static_cast<float>(math::PI_2)) // Facing left
{
bubble_pos = beak_position + Vector(offset_x - big_offset_x * 2, offset_y);
}
else // Facing right (including straight up or down)
{
bubble_pos = beak_position + Vector(offset_x - 4.0f + big_offset_x, offset_y);
}

if (num_bubbles > 1)
{
float burst_offset_x = graphicsRandom.randf(-5.0f, 5.0f);
float burst_offset_y = graphicsRandom.randf(-5.0f, 5.0f);
bubble_pos.x += burst_offset_x;
bubble_pos.y += burst_offset_y;
}

if (bubble_pos.y > -1)
{
m_active_bubbles.emplace_back(std::make_pair(std::move(bubble_sprite), bubble_pos));
}
}

// Restart the timer for the next wave of bubbles
m_bubble_timer.start(0.8f + graphicsRandom.randf(0.0f, 0.6f));
}
}
else
{
Expand Down Expand Up @@ -2248,10 +2334,13 @@ Player::draw(DrawingContext& context)
get_bonus() == EARTH_BONUS ? Color(1.f, 0.9f, 0.6f) :
Color(1.f, 1.f, 1.f));

for (auto& bubble_sprite : m_active_bubbles)
{
bubble_sprite.first->draw(context.color(), bubble_sprite.second, LAYER_TILES - 5);
}
m_sprite->set_color(m_stone ? Color(1.f, 1.f, 1.f) : power_color);
}


void
Player::collision_tile(uint32_t tile_attributes)
{
Expand Down
7 changes: 7 additions & 0 deletions src/object/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
#include "video/layer.hpp"
#include "video/surface_ptr.hpp"

#include <array>
#include <list>

class BadGuy;
class Climbable;
class Controller;
Expand Down Expand Up @@ -567,6 +570,10 @@ class Player final : public MovingObject

SurfacePtr m_airarrow; /**< arrow indicating Tux' position when he's above the camera */

SpritePtr m_bubbles_sprite; /**< bubble particles sprite for swimming */
Timer m_bubble_timer; /**< timer for spawning bubble particles */
std::list<std::pair<SpritePtr, Vector>> m_active_bubbles; /**< active bubble particles */

Vector m_floor_normal;

bool m_ghost_mode; /**< indicates if Tux should float around and through solid objects */
Expand Down

0 comments on commit 062dc80

Please sign in to comment.