334 lines
13 KiB
C++
334 lines
13 KiB
C++
#include "core/rendering/surface_animated_sprite.hpp"
|
|
|
|
#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 = {0.0F, 0.0F, frame_width, 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;
|
|
}
|
|
|
|
// Carga las animaciones desde un fichero YAML
|
|
auto SurfaceAnimatedSprite::loadAnimationsFromYAML(const std::string& file_path, std::shared_ptr<Surface>& surface, float& frame_width, float& frame_height) -> std::vector<AnimationData> {
|
|
std::vector<AnimationData> animations;
|
|
|
|
// Extract filename for logging
|
|
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
|
|
|
|
try {
|
|
// Load YAML file using ResourceHelper (supports both filesystem and pack)
|
|
auto file_data = Resource::Helper::loadFile(file_path);
|
|
|
|
if (file_data.empty()) {
|
|
std::cerr << "Error: Unable to load animation file " << FILE_NAME << '\n';
|
|
throw std::runtime_error("Animation file not found: " + file_path);
|
|
}
|
|
|
|
printWithDots("Animation : ", FILE_NAME, "[ LOADED ]");
|
|
|
|
// Parse YAML from string
|
|
std::string yaml_content(file_data.begin(), file_data.end());
|
|
auto yaml = fkyaml::node::deserialize(yaml_content);
|
|
|
|
// --- Parse global configuration ---
|
|
if (yaml.contains("tileSetFile")) {
|
|
auto tile_set_file = yaml["tileSetFile"].get_value<std::string>();
|
|
surface = Resource::Cache::get()->getSurface(tile_set_file);
|
|
}
|
|
|
|
if (yaml.contains("frameWidth")) {
|
|
frame_width = static_cast<float>(yaml["frameWidth"].get_value<int>());
|
|
}
|
|
|
|
if (yaml.contains("frameHeight")) {
|
|
frame_height = static_cast<float>(yaml["frameHeight"].get_value<int>());
|
|
}
|
|
|
|
// Calculate sprite sheet parameters
|
|
int frames_per_row = 1;
|
|
int max_tiles = 1;
|
|
if (surface) {
|
|
frames_per_row = surface->getWidth() / static_cast<int>(frame_width);
|
|
const int W = surface->getWidth() / static_cast<int>(frame_width);
|
|
const int H = surface->getHeight() / static_cast<int>(frame_height);
|
|
max_tiles = W * H;
|
|
}
|
|
|
|
// --- Parse animations array ---
|
|
if (yaml.contains("animations") && yaml["animations"].is_sequence()) {
|
|
const auto& animations_node = yaml["animations"];
|
|
|
|
for (const auto& anim_node : animations_node) {
|
|
AnimationData animation;
|
|
|
|
// Parse animation name
|
|
if (anim_node.contains("name")) {
|
|
animation.name = anim_node["name"].get_value<std::string>();
|
|
}
|
|
|
|
// Parse speed (seconds per frame)
|
|
if (anim_node.contains("speed")) {
|
|
animation.speed = anim_node["speed"].get_value<float>();
|
|
}
|
|
|
|
// Parse loop frame index
|
|
if (anim_node.contains("loop")) {
|
|
animation.loop = anim_node["loop"].get_value<int>();
|
|
}
|
|
|
|
// Parse frames array
|
|
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);
|
|
}
|
|
|
|
animations.push_back(animation);
|
|
}
|
|
}
|
|
|
|
} catch (const fkyaml::exception& e) {
|
|
std::cerr << "YAML parsing error in " << FILE_NAME << ": " << e.what() << '\n';
|
|
throw;
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "Error loading animation " << FILE_NAME << ": " << e.what() << '\n';
|
|
throw;
|
|
}
|
|
|
|
return animations;
|
|
}
|
|
|
|
// Constructor con bytes YAML del cache (parsing lazy)
|
|
SurfaceAnimatedSprite::SurfaceAnimatedSprite(const AnimationResource& cached_data) {
|
|
// Parsear YAML desde los bytes cargados en cache
|
|
std::string yaml_content(cached_data.yaml_data.begin(), cached_data.yaml_data.end());
|
|
|
|
try {
|
|
auto yaml = fkyaml::node::deserialize(yaml_content);
|
|
|
|
// Variables para almacenar configuración global
|
|
float frame_width = 0.0F;
|
|
float frame_height = 0.0F;
|
|
|
|
// --- Parse global configuration ---
|
|
if (yaml.contains("tileSetFile")) {
|
|
auto tile_set_file = yaml["tileSetFile"].get_value<std::string>();
|
|
// Ahora SÍ podemos acceder al cache (ya está completamente cargado)
|
|
surface_ = Resource::Cache::get()->getSurface(tile_set_file);
|
|
}
|
|
|
|
if (yaml.contains("frameWidth")) {
|
|
frame_width = static_cast<float>(yaml["frameWidth"].get_value<int>());
|
|
}
|
|
|
|
if (yaml.contains("frameHeight")) {
|
|
frame_height = static_cast<float>(yaml["frameHeight"].get_value<int>());
|
|
}
|
|
|
|
// Calculate sprite sheet parameters
|
|
int frames_per_row = 1;
|
|
int max_tiles = 1;
|
|
if (surface_) {
|
|
frames_per_row = surface_->getWidth() / static_cast<int>(frame_width);
|
|
const int W = surface_->getWidth() / static_cast<int>(frame_width);
|
|
const int H = surface_->getHeight() / static_cast<int>(frame_height);
|
|
max_tiles = W * H;
|
|
}
|
|
|
|
// --- Parse animations array ---
|
|
if (yaml.contains("animations") && yaml["animations"].is_sequence()) {
|
|
const auto& animations_node = yaml["animations"];
|
|
|
|
for (const auto& anim_node : animations_node) {
|
|
AnimationData animation;
|
|
|
|
// Parse animation name
|
|
if (anim_node.contains("name")) {
|
|
animation.name = anim_node["name"].get_value<std::string>();
|
|
}
|
|
|
|
// Parse speed (seconds per frame)
|
|
if (anim_node.contains("speed")) {
|
|
animation.speed = anim_node["speed"].get_value<float>();
|
|
}
|
|
|
|
// Parse loop frame index
|
|
if (anim_node.contains("loop")) {
|
|
animation.loop = anim_node["loop"].get_value<int>();
|
|
}
|
|
|
|
// Parse frames array
|
|
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);
|
|
}
|
|
|
|
animations_.push_back(animation);
|
|
}
|
|
}
|
|
|
|
// Set dimensions
|
|
setWidth(frame_width);
|
|
setHeight(frame_height);
|
|
|
|
// Inicializar con la primera animación si existe
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Obtiene el indice de la animación a partir del nombre
|
|
auto SurfaceAnimatedSprite::getIndex(const std::string& name) -> int {
|
|
auto index = -1;
|
|
|
|
for (const auto& a : animations_) {
|
|
index++;
|
|
if (a.name == name) {
|
|
return index;
|
|
}
|
|
}
|
|
std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << '\n';
|
|
return -1;
|
|
}
|
|
|
|
// Calcula el frame correspondiente a la animación (time-based)
|
|
void SurfaceAnimatedSprite::animate(float delta_time) {
|
|
if (animations_[current_animation_].speed <= 0.0F) {
|
|
return;
|
|
}
|
|
|
|
// Acumula el tiempo transcurrido
|
|
animations_[current_animation_].accumulated_time += delta_time;
|
|
|
|
// Calcula el frame actual a partir del tiempo acumulado
|
|
const int TARGET_FRAME = static_cast<int>(
|
|
animations_[current_animation_].accumulated_time /
|
|
animations_[current_animation_].speed);
|
|
|
|
// Si alcanza el final de la animación, maneja el loop
|
|
if (TARGET_FRAME >= static_cast<int>(animations_[current_animation_].frames.size())) {
|
|
if (animations_[current_animation_].loop == -1) {
|
|
// Si no hay loop, congela en el último frame
|
|
animations_[current_animation_].current_frame =
|
|
static_cast<int>(animations_[current_animation_].frames.size()) - 1;
|
|
animations_[current_animation_].completed = true;
|
|
|
|
// Establece el clip del último frame
|
|
if (animations_[current_animation_].current_frame >= 0) {
|
|
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
|
}
|
|
} else {
|
|
// Si hay loop, vuelve al frame indicado
|
|
animations_[current_animation_].accumulated_time =
|
|
static_cast<float>(animations_[current_animation_].loop) *
|
|
animations_[current_animation_].speed;
|
|
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
|
|
|
|
// Establece el clip del frame de loop
|
|
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
|
}
|
|
} else {
|
|
// Actualiza el frame actual
|
|
animations_[current_animation_].current_frame = TARGET_FRAME;
|
|
|
|
// Establece el clip del frame actual
|
|
if (animations_[current_animation_].current_frame >= 0 &&
|
|
animations_[current_animation_].current_frame <
|
|
static_cast<int>(animations_[current_animation_].frames.size())) {
|
|
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comprueba si ha terminado la animación
|
|
auto SurfaceAnimatedSprite::animationIsCompleted() -> bool {
|
|
return animations_[current_animation_].completed;
|
|
}
|
|
|
|
// Establece la animacion actual
|
|
void SurfaceAnimatedSprite::setCurrentAnimation(const std::string& name) {
|
|
const auto NEW_ANIMATION = getIndex(name);
|
|
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]);
|
|
}
|
|
}
|
|
|
|
// Establece la animacion actual
|
|
void SurfaceAnimatedSprite::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 SurfaceAnimatedSprite::update(float delta_time) {
|
|
animate(delta_time);
|
|
SurfaceMovingSprite::update(delta_time);
|
|
}
|
|
|
|
// Reinicia la animación
|
|
void SurfaceAnimatedSprite::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 SurfaceAnimatedSprite::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]);
|
|
} |