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.gzsrc/bird/bird.cpp 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);
}