mapcreator git · master
SDL3 2.5D game and engine using assets, with map editor
C++ 99.3%git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gzAdd bird somehow landing, taking off, following player etc
src/bird/bird.cpp | 360 +++++++++++++++++++++++++++++++++++++------- src/bird/bird.hpp | 68 +++++++-- src/character/character.cpp | 6 +- src/game/state_playing.cpp | 1 - 4 files changed, 364 insertions(+), 71 deletions(-) diff --git a/src/bird/bird.cpp b/src/bird/bird.cpp index 812400f..88ca72d 100644 --- a/src/bird/bird.cpp +++ b/src/bird/bird.cpp @@ -4,136 +4,388 @@ #include <SDL3_image/SDL_image.h> #include <cstdint> #include <iostream> +#include <algorithm> +#include <cmath> #include "defaults/defaults.hpp" +#include "map/map.hpp" + +/* +static constexpr float TAKEOFF_TIME = 1.8f; +static constexpr float LANDING_TIME = 1.8f; +*/ +static constexpr float FLIGHT_HEIGHT = 80.0f; // Consistent flying altitude SDL_Texture* Bird::texture; -Bird::Bird(int x, int y) +Bird::Bird(float x, float y) : pos_x(x), - pos_y(y) { - set_State(BirdState::IDLE); + pos_y(y), + target_pos{x, y}, + decision_timer(0.0) { + set_State(BirdState::WALKING, 2.4); } -bool -Bird::Init(SDL_Renderer* renderer) { +bool Bird::Init(SDL_Renderer* renderer) { texture = IMG_LoadTexture(renderer, "Bird/Spritesheet/Bird Spritesheet.png"); SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); return texture != nullptr; - } -void -Bird::Update(double delta_ms) { - animation_timer += delta_ms; +void Bird::Update(double delta_s, const SDL_FPoint& player_pos) { + std::cout << (int)state << " ISTART " << is_starting_to_fly << " ILAND " << is_landing << " XY: " << pos_x << " " << pos_y << " DC " << decision_timer << " STT: " << state_timer << std::endl; + state_timer += delta_s; + decision_timer -= delta_s; - std::cout << "FT: " << current_frame << " " << animation_timer << std::endl; - if (animation_timer >= frame_duration_ms) { - animation_timer = 0; - current_frame += frame_step; - if (current_frame >= frame_count || current_frame < 0) { - if (state == BirdState::START_TO_FLY) { - set_State(BirdState::FLYING); - return; - } + // Make decisions when idle on ground + //if (landing_cooldown <= 0.0 && state == BirdState::IDLE && on_ground && decision_timer >= 2.0) { + if ( decision_timer <= 0 && + state != BirdState::FLYING && + state != BirdState::WALKING) { + Decide(player_pos); + } - if (state == BirdState::STOP_FLYING) { - set_State(BirdState::IDLE); - return; + /* + // Handle eating state + if (state == BirdState::EATING) { + eating_timer += delta_s; + if (eating_timer >= 3.0) { + set_State(BirdState::START_TO_FLY, 0.0); + PickFlyTarget(player_pos); + return; + } + } + */ + + // Determine movement speed + float speed = 0.0f; + if (state == BirdState::FLYING) + speed = FLY_SPEED; + else if (state == BirdState::WALKING) + speed = WALK_SPEED; + + // Calculate distance to target + float dx = target_pos.x - pos_x; + float dy = target_pos.y - pos_y; + float dist = sqrtf(dx * dx + dy * dy); + + // Handle flying movement + if (state == BirdState::FLYING && !is_landing && !is_starting_to_fly) { + if (dist > 2.0f) { + pos_x += (dx / dist) * speed * delta_s; + pos_y += (dy / dist) * speed * delta_s; + + // Update facing direction + if (fabsf(dx) > 0.01f) { + facing_left = (dx < 0.0f); } + } else { + // Reached target - begin landing + std::cout << "BIRD INIT LANDING!\n"; + is_landing = true; + landing_progress = 0.0f; + landing_start_x = pos_x; + landing_start_y = pos_y; + landing_direction = facing_left ? -1.0f : 1.0f; // Store current flight direction + landing_prev_x_offset = 0.0f; + landing_prev_y_offset = 0.0f; + } + } + // Handle walking movement + else + if (state == BirdState::WALKING) { + std::cout << "DIST: " << dist << " SP " << speed << std::endl; + if (dist > 0.4) { + float move = speed * delta_s; + if (move > dist) move = dist; + pos_x += (dx / dist) * move; + pos_y += (dy / dist) * move; - current_frame = 0; + if (fabsf(dx) > 0.01f) facing_left = (dx < 0.0f); + } else { + set_State(BirdState::IDLE, 0.0); + return; + } + } + else + if (is_starting_to_fly) { + + float overall_dx = target_pos.x - pos_x; + takeoff_direction = (overall_dx >= 0.0f) ? 1.0f : -1.0f; + + float TAKEOFF_SPEED = 35.0f; + + takeoff_progress += TAKEOFF_SPEED * delta_s; + + float t = takeoff_progress / FLIGHT_HEIGHT; + + // Bias circle to start opposite target + float circle_direction = -takeoff_direction; + + float angle, x_offset; + if (t < 0.5f) { + angle = (t / 0.5f) * (M_PI / 2.0f); + x_offset = sinf(angle) * 30.0f * circle_direction; + } else { + angle = ((t - 0.5f) / 0.5f) * (M_PI / 2.0f); + x_offset = (1.0f + sinf(angle)) * 30.0f * circle_direction; } + float full_angle = t * M_PI; + float y_offset = -sinf(full_angle) * 20.0f; + + // Delta + float x_offset_delta = x_offset - takeoff_prev_x_offset; + takeoff_prev_x_offset = x_offset; + float y_offset_delta = -((takeoff_progress + y_offset) - takeoff_prev_y_offset); + takeoff_prev_y_offset = takeoff_progress + y_offset; + + std::cout << "FACIN A: " << facing_left << std::endl; + facing_left = ( pos_x+x_offset_delta - pos_x < 0.0f); + std::cout << "FACIN B: " << facing_left << std::endl; + + pos_x += x_offset_delta; + pos_y += y_offset_delta; + + // If finished takeoff, align for straight flight + if (takeoff_progress >= FLIGHT_HEIGHT) { + is_starting_to_fly = false; + + // Reset offsets + takeoff_prev_x_offset = 0.0f; + takeoff_prev_y_offset = 0.0f; + + facing_left = (target_pos.x - pos_x < 0.0f); + } + + std::cout << "DEL: " << x_offset_delta << " " << y_offset_delta << std::endl; + } + // Handle landing animation with circular arc (position-based) + else if (is_landing) { + + landing_progress += speed * delta_s; + + if (landing_progress >= FLIGHT_HEIGHT) { + // if (fabs(target_pos.y - landing_start_y) >= FLIGHT_HEIGHT+1) { + std::cout << "STOP FLYING! START LANDING" << std::endl; + set_State(BirdState::STOP_FLYING, 0.0); + return; + } + + float t = landing_progress / FLIGHT_HEIGHT; + + float angle; + float x_offset; + if (t < 0.5f) { + angle = (t / 0.5f) * (M_PI / 2.0f); + x_offset = sinf(angle) * 30.0f * landing_direction; + } else { + angle = ((t - 0.5f) / 0.5f) * (M_PI / 2.0f); + x_offset = (1.0f - sinf(angle)) * 30.0f * landing_direction; + } + + float full_angle = t * M_PI; + float y_offset = -sinf(full_angle) * 20.0f; + + float x_offset_delta = x_offset - landing_prev_x_offset; + landing_prev_x_offset = x_offset; + float y_offset_delta = (landing_progress + y_offset) - landing_prev_y_offset; + landing_prev_y_offset = landing_progress + y_offset; + + float dx = pos_x + x_offset_delta - pos_x; + if (fabsf(dx) > 0.01f) { + facing_left = (dx < 0.0f); + } + pos_x += x_offset_delta; + pos_y += y_offset_delta; + } + + // Update animation frames + animation_timer += delta_s; + if (animation_timer >= frame_duration_seconds) { + animation_timer = 0.0; + current_frame += frame_step; + + if (reverse_animation) { + if (current_frame < 0) + current_frame = 0; + } else { + if (current_frame >= frame_count) + current_frame = 0; + } + src_rect.x = current_frame * tile_size; } + } -void -Bird::Render(SDL_Renderer* renderer) { +void Bird::Render(SDL_Renderer* renderer) { SDL_FRect dst; - dst.x = Scale(static_cast<float>(pos_x)); - dst.y = Scale(static_cast<float>(pos_y)); + dst.x = Scale(pos_x + Map::offset.x); + dst.y = Scale(pos_y + Map::offset.y); dst.w = Scale(tile_size); dst.h = Scale(tile_size); - SDL_RenderTexture(renderer, texture, &src_rect, &dst); + SDL_FlipMode flip = facing_left ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; + SDL_RenderTextureRotated(renderer, texture, &src_rect, &dst, 0.0, nullptr, flip); } -void -Bird::set_Position(SDL_FPoint point) { +void Bird::set_Position(SDL_FPoint point) { pos_x = point.x; pos_y = point.y; } -void -Bird::set_State(BirdState new_state) { +void Bird::set_State(BirdState new_state, double dec_timer) { + + decision_timer = dec_timer; + if (state == new_state) return; state = new_state; - std::cout << "SET BIRD STATE\n"; - animation_timer = 0; - + state_timer = 0.0f; + transition_timer = 0.0f; + animation_timer = 0.0; reverse_animation = false; frame_step = 1; switch (state) { case BirdState::IDLE: current_frame = 0; - set_Animation(0, 2, 0.05); + set_Animation(0, 2, 0.25); break; case BirdState::FLYING: current_frame = 0; - set_Animation(1, 8, 0.08); + set_Animation(1, 8, 0.06); break; case BirdState::WALKING: current_frame = 0; - set_Animation(2, 3, 0.02); + set_Animation(2, 3, 0.15); break; case BirdState::EATING: current_frame = 0; - set_Animation(3, 3, 0.03); + //eating_timer = 0.0; + set_Animation(3, 3, 0.15); break; - case BirdState::START_TO_FLY: + case BirdState::START_TO_FLY: { current_frame = 0; - set_Animation(4, 8, 0.01); + is_starting_to_fly = true; + set_Animation(4, 8, 0.06); + decision_timer = 8*0.06; + takeoff_progress = 0.0f; + takeoff_prev_x_offset = 0.0f; + takeoff_prev_y_offset = 0.0f; break; - - case BirdState::STOP_FLYING: - set_Animation(4, 8, 0.01); - reverse_animation = true; + } + case BirdState::STOP_FLYING: { frame_step = -1; - current_frame = frame_count - 1; + reverse_animation = true; + set_Animation(4, 8, 0.06); + decision_timer = 8*0.06; + is_landing = false; + takeoff_direction = facing_left ? -1.0f : 1.0f; break; - - case BirdState::ATTACK: + } + case BirdState::ATTACK: { current_frame = 0; set_Animation(1, 8, 0.06); break; + } } src_rect.x = current_frame * tile_size; } -BirdState -Bird::get_State() const { +BirdState Bird::get_State() const { return state; } - -void -Bird::set_Animation(int row, int frames, double frame_time) { +void Bird::set_Animation(int row, int frames, double frame_time) { frame_count = frames; - frame_duration_ms = frame_time; + frame_duration_seconds = frame_time; - src_rect.x = 0; + src_rect.x = current_frame * tile_size; src_rect.y = row * tile_size; src_rect.w = tile_size; src_rect.h = tile_size; } + +void Bird::Decide(const SDL_FPoint& player_pos) { + + // Calculate distance to player + float dx = pos_x - player_pos.x; + float dy = pos_y - player_pos.y; + float dist_sq = dx * dx + dy * dy; + + // If too far, fly closer + if ( dist_sq > MAX_RADIUS * MAX_RADIUS && + state != BirdState::FLYING && + state != BirdState::STOP_FLYING && + state != BirdState::START_TO_FLY) { + std::cout << "BIRD TARGET TOO FAR\n"; + set_State(BirdState::START_TO_FLY, 0.0); + PickFlyTarget(player_pos); + return; + } + if (state == BirdState::START_TO_FLY) { + set_State(BirdState::FLYING, 0.0); + return; + } + + // Random behavior when close to player + int choice = rand() % 100; + float duration = rand() % 80 * 0.1f + 1.1f; + + if (state == BirdState::STOP_FLYING) { + set_State(BirdState::IDLE, duration); + return; + } + + if (choice < 25) { + set_State(BirdState::EATING, duration); + } + else if (choice < 50) { + set_State(BirdState::WALKING, duration); + PickGroundTarget(player_pos); + } + else if (choice < 75) { + set_State(BirdState::IDLE, duration); + } + else { + set_State(BirdState::START_TO_FLY, 0.0); + PickFlyTarget(player_pos); + } +} + +void Bird::PickFlyTarget(const SDL_FPoint& player_pos) { + const float MIN_DIST = MAX_RADIUS; // minimum distance from current position + + float angle, radius, dx, dy, dist_sq; + do { + angle = (rand() % 628) / 100.0f; // 0 to ~2*PI + radius = MIN_RADIUS + (rand() % static_cast<int>(MAX_RADIUS - MIN_RADIUS)); + + target_pos.x = player_pos.x + cosf(angle) * radius; + target_pos.y = player_pos.y - FLIGHT_HEIGHT + sinf(angle) * (radius * 0.3f); + + // Clamp within map bounds + target_pos.x = std::clamp(target_pos.x, 0.0f, static_cast<float>(Map::size.x * Tile::tile_size)); + target_pos.y = std::clamp(target_pos.y, 0.0f, static_cast<float>(Map::size.y * Tile::tile_size)); + + dx = target_pos.x - pos_x; + dy = target_pos.y - pos_y; + dist_sq = dx * dx + dy * dy; + + } while (dist_sq < MIN_DIST * MIN_DIST); // ensure minimum distance +} + +void Bird::PickGroundTarget(const SDL_FPoint& player_pos) { + float offset = static_cast<float>((rand() % 64) - 32); // -32 to +32 + target_pos.x = player_pos.x + offset; + target_pos.y = player_pos.y; +} diff --git a/src/bird/bird.hpp b/src/bird/bird.hpp index ed26f86..c683fe3 100644 --- a/src/bird/bird.hpp +++ b/src/bird/bird.hpp @@ -1,31 +1,39 @@ #pragma once +static constexpr float MIN_RADIUS = 124.f; +static constexpr float MAX_RADIUS = 164.f; +static constexpr float FLY_SPEED = 42.f; +static constexpr float WALK_SPEED = 10.f; + enum class BirdState { - IDLE, - FLYING, - WALKING, - EATING, - START_TO_FLY, - STOP_FLYING, - ATTACK + IDLE, // 0 + FLYING, // 1 + WALKING, // 2 + EATING, // 3 + START_TO_FLY, // 4 + STOP_FLYING, // 5 + ATTACK // 6 }; #include <SDL3/SDL.h> #include <cstdint> -class Bird -{ +class Bird { public: - Bird(int x, int y); + Bird(float x, float y); static bool Init(SDL_Renderer*); - void Update(double delta_ms); + void Update(double delta_s, const SDL_FPoint& player_pos); + void Decide(const SDL_FPoint& player_pos); + void PickFlyTarget(const SDL_FPoint& player_pos); + void PickGroundTarget(const SDL_FPoint& player_pos); + void Render(SDL_Renderer* renderer); - void set_State(BirdState new_state); + void set_State(BirdState new_state, double decision_timer); void set_Position(SDL_FPoint); BirdState get_State() const; @@ -38,6 +46,40 @@ private: float pos_x = 0; float pos_y = 0; + SDL_FPoint velocity{0.f, 0.f}; + SDL_FPoint target_pos{0.f, 0.f}; + + double state_timer = 0.0; + double decision_timer = 0.0; + double eating_timer = 0.0; + + bool is_landing = false; + bool is_starting_to_fly = false; + float landing_progress = 0.0f; + float takeoff_progress = 0.0f; + bool facing_left = true; + float vertical_offset = 0.0f; + float transition_timer = 0.0f; + + // Bird class private members + float landing_start_y = 0.0f; // store Y when landing starts + + float landing_start_x = 0.0f; // Store X when landing starts + float landing_direction = 1.0f; // Store flight direction for landing arc + float landing_prev_x_offset = 0.0f; + float landing_prev_y_offset = 0.0f; + + + float takeoff_direction = 1.0f; // Store flight direction for takeoff arc + float takeoff_prev_x_offset = 0.0f; + float takeoff_prev_y_offset = 0.0f; + + + + + bool reached_target = false; + bool init_start_to_fly = false; + static constexpr int tile_size = 16; BirdState state = BirdState::IDLE; @@ -48,6 +90,6 @@ private: bool reverse_animation = false; int frame_step = 1; - double frame_duration_ms = 200; + double frame_duration_seconds = 200; double animation_timer = 0; }; diff --git a/src/character/character.cpp b/src/character/character.cpp index 8aea8a9..0127f60 100644 --- a/src/character/character.cpp +++ b/src/character/character.cpp @@ -70,7 +70,7 @@ Character::Init(SDL_Renderer* renderer) { selected_item = inventory->hotbar_hands; bird = new Bird(x, y); - bird->set_State(BirdState::FLYING); + bird->set_State(BirdState::WALKING, 1.4); // load_MapTiles animations @@ -1614,8 +1614,8 @@ void Character::handle_Move() { void Character::Update() { - bird->set_Position({x,y}); - bird->Update(Default::delta_time); + //bird->set_Position({x,y}); + bird->Update(Default::delta_time, collider_world); if ( is_placing_build) BuildingData::update_ActiveBuildData(collider_world.x, collider_world.y - collider.h); diff --git a/src/game/state_playing.cpp b/src/game/state_playing.cpp index 83c62f5..ab7cf88 100644 --- a/src/game/state_playing.cpp +++ b/src/game/state_playing.cpp @@ -7,7 +7,6 @@ #include "game/game.hpp" #include "defaults/ui.hpp" #include "character/message.hpp" -#include "bird/bird.hpp" StatePlaying::StatePlaying() { std::cout << "CREATE STATE_PLAYING\n";