245 lines
9.3 KiB
C++
245 lines
9.3 KiB
C++
#include "core/rendering/sprite/animated_sprite.hpp"
|
|
|
|
#include <cmath> // Para std::fmod
|
|
#include <cstddef> // Para size_t
|
|
#include <fstream> // Para basic_ostream, basic_istream, operator<<, basic...
|
|
#include <iostream> // Para cout, cerr
|
|
#include <sstream> // Para basic_stringstream
|
|
#include <stdexcept> // Para runtime_error
|
|
#include <utility>
|
|
|
|
#include "core/rendering/surface.hpp" // Para Surface
|
|
#include "core/resources/resource_cache.hpp" // Para Resource
|
|
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
|
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
|
#include "utils/utils.hpp" // Para printWithDots
|
|
|
|
// Helper: Convierte un nodo YAML de frames (array) a vector de SDL_FRect
|
|
auto convertYAMLFramesToRects(const fkyaml::node& frames_node, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector<SDL_FRect> {
|
|
std::vector<SDL_FRect> frames;
|
|
SDL_FRect rect = {.x = 0.0F, .y = 0.0F, .w = frame_width, .h = frame_height};
|
|
|
|
for (const auto& frame_index_node : frames_node) {
|
|
const int NUM_TILE = frame_index_node.get_value<int>();
|
|
if (NUM_TILE <= max_tiles) {
|
|
rect.x = (NUM_TILE % frames_per_row) * frame_width;
|
|
rect.y = (NUM_TILE / frames_per_row) * frame_height;
|
|
frames.emplace_back(rect);
|
|
}
|
|
}
|
|
|
|
return frames;
|
|
}
|
|
|
|
// Helper: parsea el array de animaciones de un nodo YAML
|
|
static auto parseAnimations(const fkyaml::node& yaml, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector<AnimatedSprite::AnimationData> {
|
|
std::vector<AnimatedSprite::AnimationData> animations;
|
|
if (!yaml.contains("animations") || !yaml["animations"].is_sequence()) {
|
|
return animations;
|
|
}
|
|
|
|
for (const auto& anim_node : yaml["animations"]) {
|
|
AnimatedSprite::AnimationData animation;
|
|
|
|
if (anim_node.contains("name")) {
|
|
animation.name = anim_node["name"].get_value<std::string>();
|
|
}
|
|
|
|
if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
|
|
animation.frames = convertYAMLFramesToRects(
|
|
anim_node["frames"],
|
|
frame_width,
|
|
frame_height,
|
|
frames_per_row,
|
|
max_tiles);
|
|
}
|
|
|
|
if (anim_node.contains("speed")) {
|
|
const auto& speed_node = anim_node["speed"];
|
|
if (speed_node.is_sequence()) {
|
|
for (const auto& s : speed_node) {
|
|
animation.speeds.push_back(s.get_value<float>());
|
|
}
|
|
} else {
|
|
auto spd = speed_node.get_value<float>();
|
|
if (spd > 0.0F) {
|
|
animation.speeds.assign(animation.frames.size(), spd);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (anim_node.contains("loopFrom")) {
|
|
animation.loop_from = anim_node["loopFrom"].get_value<int>();
|
|
}
|
|
|
|
animations.push_back(animation);
|
|
}
|
|
return animations;
|
|
}
|
|
|
|
// Helper: parsea la configuración global (tileSetFile, frameWidth, frameHeight) y calcula parámetros del spritesheet
|
|
struct SheetParams {
|
|
float frame_width{0.0F};
|
|
float frame_height{0.0F};
|
|
int frames_per_row{1};
|
|
int max_tiles{1};
|
|
};
|
|
|
|
static auto parseGlobalConfig(const fkyaml::node& yaml, std::shared_ptr<Surface>& surface) -> SheetParams {
|
|
SheetParams params;
|
|
if (yaml.contains("tileSetFile")) {
|
|
surface = Resource::Cache::get()->getSurface(yaml["tileSetFile"].get_value<std::string>());
|
|
}
|
|
if (yaml.contains("frameWidth")) {
|
|
params.frame_width = static_cast<float>(yaml["frameWidth"].get_value<int>());
|
|
}
|
|
if (yaml.contains("frameHeight")) {
|
|
params.frame_height = static_cast<float>(yaml["frameHeight"].get_value<int>());
|
|
}
|
|
if (surface && params.frame_width > 0.0F && params.frame_height > 0.0F) {
|
|
params.frames_per_row = surface->getWidth() / static_cast<int>(params.frame_width);
|
|
const int H = surface->getHeight() / static_cast<int>(params.frame_height);
|
|
params.max_tiles = params.frames_per_row * H;
|
|
}
|
|
return params;
|
|
}
|
|
|
|
// Constructor con bytes YAML del cache
|
|
AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) {
|
|
std::string yaml_content(cached_data.yaml_data.begin(), cached_data.yaml_data.end());
|
|
|
|
try {
|
|
auto yaml = fkyaml::node::deserialize(yaml_content);
|
|
auto params = parseGlobalConfig(yaml, surface_);
|
|
animations_ = parseAnimations(yaml, params.frame_width, params.frame_height, params.frames_per_row, params.max_tiles);
|
|
buildNameIndex();
|
|
|
|
setWidth(params.frame_width);
|
|
setHeight(params.frame_height);
|
|
|
|
if (!animations_.empty() && !animations_[0].frames.empty()) {
|
|
setClip(animations_[0].frames[0]);
|
|
}
|
|
} catch (const fkyaml::exception& e) {
|
|
std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n';
|
|
throw;
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "Error loading animation " << cached_data.name << ": " << e.what() << '\n';
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Constructor per a subclasses amb surface directa (sense YAML)
|
|
AnimatedSprite::AnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
|
: MovingSprite(std::move(surface), pos) {
|
|
// animations_ queda buit (protegit per el guard de animate())
|
|
if (surface_) {
|
|
clip_ = {.x = 0, .y = 0, .w = surface_->getWidth(), .h = surface_->getHeight()};
|
|
}
|
|
}
|
|
|
|
// Construye el mapa nombre→índice para búsqueda O(1)
|
|
void AnimatedSprite::buildNameIndex() {
|
|
animation_index_.clear();
|
|
for (int i = 0; i < static_cast<int>(animations_.size()); ++i) {
|
|
animation_index_[animations_[i].name] = i;
|
|
}
|
|
}
|
|
|
|
// Calcula el frame correspondiente a la animación (time-based)
|
|
void AnimatedSprite::animate(float delta_time) { // NOLINT(readability-convert-member-functions-to-static)
|
|
if (animations_.empty()) { return; }
|
|
auto& anim = animations_[current_animation_];
|
|
if (anim.speeds.empty()) { return; } // Animación estática
|
|
|
|
anim.accumulated_time += delta_time;
|
|
|
|
// Calcular duración total de la animación
|
|
float total = 0.0F;
|
|
for (auto s : anim.speeds) { total += s; }
|
|
|
|
// Si hemos superado la duración total, manejar loop o congelar
|
|
if (anim.accumulated_time >= total) {
|
|
if (anim.loop_from < 0) {
|
|
// Sin loop: congelar en el último frame
|
|
anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
|
|
anim.completed = true;
|
|
setClip(anim.frames[anim.current_frame]);
|
|
return;
|
|
}
|
|
// Con loop: envolver el tiempo en el rango del loop
|
|
float loop_start = 0.0F;
|
|
for (int i = 0; i < anim.loop_from; ++i) { loop_start += anim.speeds[i]; }
|
|
float loop_len = total - loop_start;
|
|
anim.accumulated_time = loop_start + std::fmod(anim.accumulated_time - loop_start, loop_len);
|
|
}
|
|
|
|
// Buscar el frame correspondiente al tiempo acumulado
|
|
float cursor = 0.0F;
|
|
for (int i = 0; i < static_cast<int>(anim.frames.size()); ++i) {
|
|
cursor += anim.speeds[i];
|
|
if (anim.accumulated_time < cursor) {
|
|
anim.current_frame = i;
|
|
setClip(anim.frames[i]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Seguridad: último frame
|
|
anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
|
|
setClip(anim.frames[anim.current_frame]);
|
|
}
|
|
|
|
// Comprueba si ha terminado la animación
|
|
auto AnimatedSprite::animationIsCompleted() -> bool {
|
|
return animations_[current_animation_].completed;
|
|
}
|
|
|
|
// Establece la animacion actual por nombre
|
|
void AnimatedSprite::setCurrentAnimation(const std::string& name) {
|
|
auto it = animation_index_.find(name);
|
|
if (it == animation_index_.end()) {
|
|
std::cout << "** Warning: could not find \"" << name << "\" animation" << '\n';
|
|
return;
|
|
}
|
|
setCurrentAnimation(it->second);
|
|
}
|
|
|
|
// Establece la animacion actual
|
|
void AnimatedSprite::setCurrentAnimation(int index) {
|
|
const auto NEW_ANIMATION = index;
|
|
if (current_animation_ != NEW_ANIMATION) {
|
|
current_animation_ = NEW_ANIMATION;
|
|
animations_[current_animation_].current_frame = 0;
|
|
animations_[current_animation_].accumulated_time = 0.0F;
|
|
animations_[current_animation_].completed = false;
|
|
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
|
}
|
|
}
|
|
|
|
// Actualiza las variables del objeto (time-based)
|
|
void AnimatedSprite::update(float delta_time) {
|
|
animate(delta_time);
|
|
MovingSprite::update(delta_time);
|
|
}
|
|
|
|
// Reinicia la animación
|
|
void AnimatedSprite::resetAnimation() {
|
|
animations_[current_animation_].current_frame = 0;
|
|
animations_[current_animation_].accumulated_time = 0.0F;
|
|
animations_[current_animation_].completed = false;
|
|
}
|
|
|
|
// Establece el frame actual de la animación
|
|
void AnimatedSprite::setCurrentAnimationFrame(int num) {
|
|
// Descarta valores fuera de rango
|
|
if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size())) {
|
|
num = 0;
|
|
}
|
|
|
|
// Cambia el valor de la variable
|
|
animations_[current_animation_].current_frame = num;
|
|
|
|
// Escoge el frame correspondiente de la animación
|
|
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
|
} |