Nimbin[12]?SDL & Graphics / mapcreator / src/bird/bird.cpp

mapcreator git · master

SDL3 2.5D game and engine using assets, with map editor

sdl3 c++ game engine map-editor cmake · first commit 2025-10-31 · last commit 2026-09-07 (1 month ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-spieleentwicklung/map_creator

C++ 99.3%
git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gz
src/bird/bird.cpp 13.8 KB · 471 lines raw
#include "bird.hpp"

#include <SDL3/SDL.h>
#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;

std::vector<Bird*> Bird::birds{};
std::atomic<std::uint64_t> Bird::next_id{0};

Bird::Bird(float x, float y)
    : drawn(false),
      pos_x(x),
      pos_y(y),
      id(next_id.fetch_add(1, std::memory_order_relaxed)),
      target_pos{x, y},
      decision_timer(0.0) {
    birds.push_back(this);
    set_State(BirdState::WALKING, 2.4);
}

bool Bird::Init(SDL_Renderer* renderer) {
    birds.clear();
    texture = IMG_LoadTexture(renderer, "Bird/Spritesheet/Bird Spritesheet.png");
    SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
    return texture != nullptr;
}

void Bird::init_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;
}


void Bird::Update(double delta_s, const SDL_FPoint& player_pos) {
    // std::cout << "ALT: " << altitude << std::endl;
    // 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;


    // 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) {
        Decide(player_pos);
    }

    /*
    // Handle eating state
    if (state == BirdState::EATING) {
        eating_timer += delta_s;
        if (eating_timer >= 3.0) {
            set_State(BirdState::START_TO_FLY, 0.0);
            pick_FlyTarget(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);

    if (state == BirdState::WALKING) {
        if (dist > 0.4) {
            float move = speed * delta_s;
            if (move > dist) move = dist;

            float next_px = pos_x + (dx / dist) * move;
            float next_py = pos_y + (dy / dist) * move;
            bool can_walk = false;
            if (Map::is_FreePosition({
                        next_px - tile_size / 2,
                        pos_y - tile_size / 2,
                        tile_size,
                        tile_size
                        })) {
                pos_x = next_px;
                can_walk = true;
            }
            if (Map::is_FreePosition({
                        pos_x - tile_size / 2,
                        next_py - tile_size / 2,
                        tile_size,
                        tile_size
                        })) {
                pos_y += (dy / dist) * move;
                can_walk = true;
            }
            if (!can_walk)
                set_State(BirdState::IDLE, 0.4);

            if (fabsf(dx) > 0.01f) facing_left = (dx < 0.0f);
        } else {
            //std::cout << "BIRD WALK REACHED GOAL" << std::endl;
            set_State(BirdState::IDLE, 0.4);
        }
    }
    else
    // 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
            init_Landing();
        }
    }
    // Handle walking movement
    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;

        facing_left = ( pos_x+x_offset_delta - pos_x < 0.0f);

        pos_x += x_offset_delta;
        pos_y += y_offset_delta;
        altitude += y_offset_delta*-1;

        // 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;
            altitude = 0.0f;
            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);
        }
        altitude -= y_offset_delta;
        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) {
    SDL_FRect dst;
    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_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) {
    pos_x = point.x;
    pos_y = point.y;
}

void Bird::set_State(BirdState new_state, double dec_timer) {

    //std::cout << "BIRD SET STATE " << (int)new_state << std::endl;
    decision_timer = dec_timer;

    /*
    if (state == new_state)
        return;
    */

    state = new_state;
    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.25);
            break;

        case BirdState::FLYING:
            current_frame = 0;
            set_Animation(1, 8, 0.06);
            break;

        case BirdState::WALKING:
            current_frame = 0;
            set_Animation(2, 3, 0.15);
            break;

        case BirdState::EATING:
            if (!Animation::audio->is_Playing("bird.sing"))
                Animation::audio->Play("bird.sing");
            current_frame = 0;
            set_Animation(3, 3, 0.15);
            break;

        case BirdState::START_TO_FLY: {
            current_frame = 0;
            is_starting_to_fly = true;
            altitude = 0.0f;
            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: {
            frame_step = -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: {
            current_frame = 0;
            set_Animation(1, 8, 0.06);
            break;
        }
    }

    src_rect.x = current_frame * tile_size;
}

BirdState Bird::get_State() const {
    return state;
}

void Bird::set_Animation(int row, int frames, double frame_time) {
    frame_count = frames;
    frame_duration_seconds = frame_time;

    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;

    //std::cout << "START DECIED\n";
    // 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);
        pick_FlyTarget(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() %  70 * 0.1f + 1.1f;

    // std::cout << "COC " << choice << std::endl;

    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+1);
        pick_GroundTarget(player_pos);
    }
    else if (choice < 75) {
        set_State(BirdState::IDLE, duration);
    }
    else {
        set_State(BirdState::START_TO_FLY, 0.0);
        std::cout << "PDECIDE PICK START\n";
        pick_FlyTarget(player_pos);
        std::cout << "PDECIDE PICK END\n";
    }
    //std::cout << "END\n";
}

void Bird::pick_FlyTarget(const SDL_FPoint& player_pos) {
    const float MIN_DIST = MAX_RADIUS; // minimum distance from current position

    float angle, radius, dx, dy, dist_sq;
    constexpr int max_attempts = 100;

    SDL_FPoint new_pos = target_pos;
    for (int i = 0; i < max_attempts; ++i) {
        angle = (rand() % 628) / 100.0f;  // 0 to ~2*PI
        radius = MIN_RADIUS + (rand() % static_cast<int>(MAX_RADIUS - MIN_RADIUS));

        new_pos.x = player_pos.x + cosf(angle) * radius;
        new_pos.y = player_pos.y - FLIGHT_HEIGHT + sinf(angle) * (radius * 0.3f);

        // Clamp within map bounds
        new_pos.x = std::clamp(new_pos.x, 0.0f, static_cast<float>(Map::size.x * Tile::tile_size));
        new_pos.y = std::clamp(new_pos.y, 0.0f, static_cast<float>(Map::size.y * Tile::tile_size));

        dx = new_pos.x - pos_x;
        dy = new_pos.y - pos_y;
        dist_sq = dx * dx + dy * dy;

        if (!Map::is_FreePosition({
                    new_pos.x - tile_size / 2,
                    new_pos.y - tile_size / 2,
                    tile_size,
                    tile_size
                    }))
            continue;

        //dist_sq = MIN_DIST*MIN_DIST+1;

        if (dist_sq >= MIN_DIST * MIN_DIST) {
            target_pos = new_pos;
            return;
        }
    };
    set_State(BirdState::IDLE, 0.8);
}

void Bird::pick_GroundTarget(const SDL_FPoint& player_pos) {
    constexpr int max_attempts = 100;

    SDL_FPoint new_pos = target_pos;
    for (int i = 0; i < max_attempts; ++i) {
        float offset_x= static_cast<float>((rand() % 64) - 32);  // -32 to +32
        float offset_y= static_cast<float>((rand() % 64) - 32);  // -32 to +32
        new_pos.x = player_pos.x + offset_x;
        new_pos.y = player_pos.y + offset_y;
        if (Map::is_FreePosition({
                    new_pos.x - tile_size / 2,
                    new_pos.y - tile_size / 2,
                    tile_size,
                    tile_size
                    })) {
            target_pos = new_pos;
            return;
        }
    };
    set_State(BirdState::IDLE, 0.8);
}