306 lines
11 KiB
C++
306 lines
11 KiB
C++
#include "animated_sprite.hpp"
|
|
|
|
#include <SDL3/SDL.h> // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError, SDL_FRect
|
|
|
|
#include <algorithm> // Para min, max
|
|
#include <cstddef> // Para size_t
|
|
#include <fstream> // Para basic_istream, basic_ifstream, basic_ios, ifstream, stringstream
|
|
#include <sstream> // Para basic_stringstream
|
|
#include <stdexcept> // Para runtime_error
|
|
#include <utility> // Para pair
|
|
|
|
#include "resource_helper.hpp" // Para ResourceHelper
|
|
#include "texture.hpp" // Para Texture
|
|
#include "utils.hpp" // Para printWithDots
|
|
|
|
// Carga las animaciones en un vector(Animations) desde un fichero
|
|
auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffer {
|
|
// Intentar cargar desde ResourceHelper primero
|
|
auto resource_data = ResourceHelper::loadFile(file_path);
|
|
std::istringstream stream;
|
|
bool using_resource_data = false;
|
|
|
|
if (!resource_data.empty()) {
|
|
std::string content(resource_data.begin(), resource_data.end());
|
|
stream.str(content);
|
|
using_resource_data = true;
|
|
}
|
|
|
|
// Fallback a archivo directo
|
|
std::ifstream file;
|
|
if (!using_resource_data) {
|
|
file.open(file_path);
|
|
if (!file) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str());
|
|
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
|
}
|
|
}
|
|
|
|
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
|
|
|
printWithDots("Animation : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
|
|
|
|
std::vector<std::string> buffer;
|
|
std::string line;
|
|
while (std::getline(input_stream, line)) {
|
|
// Eliminar caracteres de retorno de carro (\r) al final de la línea
|
|
if (!line.empty() && line.back() == '\r') {
|
|
line.pop_back();
|
|
}
|
|
if (!line.empty()) {
|
|
buffer.push_back(line);
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// Constructor
|
|
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const std::string& file_path)
|
|
: MovingSprite(std::move(texture)) {
|
|
// Carga las animaciones
|
|
if (!file_path.empty()) {
|
|
auto buffer = loadAnimationsFromFile(file_path);
|
|
loadFromAnimationsFileBuffer(buffer);
|
|
}
|
|
}
|
|
|
|
// Constructor
|
|
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const AnimationsFileBuffer& animations)
|
|
: MovingSprite(std::move(texture)) {
|
|
if (!animations.empty()) {
|
|
loadFromAnimationsFileBuffer(animations);
|
|
}
|
|
}
|
|
|
|
// Obtiene el índice de la animación a partir del nombre
|
|
auto AnimatedSprite::getAnimationIndex(const std::string& name) -> int {
|
|
auto iterator = animation_indices_.find(name);
|
|
if (iterator != animation_indices_.end()) {
|
|
// Si se encuentra la animación en el mapa, devuelve su índice
|
|
return iterator->second;
|
|
}
|
|
|
|
// Si no se encuentra, muestra una advertencia y devuelve -1
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "** Warning: could not find \"%s\" animation", name.c_str());
|
|
return -1;
|
|
}
|
|
|
|
// Calcula el frame correspondiente a la animación (time-based)
|
|
void AnimatedSprite::animate(float deltaTime) {
|
|
if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) {
|
|
return;
|
|
}
|
|
|
|
// Acumular tiempo transcurrido
|
|
animations_[current_animation_].time_accumulator += deltaTime;
|
|
|
|
// Verificar si es momento de cambiar frame
|
|
if (animations_[current_animation_].time_accumulator >= animations_[current_animation_].speed) {
|
|
animations_[current_animation_].time_accumulator -= animations_[current_animation_].speed;
|
|
animations_[current_animation_].current_frame++;
|
|
|
|
// Si alcanza el final de la animación
|
|
if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) {
|
|
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
|
|
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size() - 1;
|
|
animations_[current_animation_].completed = true;
|
|
} else { // Si hay loop, vuelve al frame indicado
|
|
animations_[current_animation_].time_accumulator = 0.0f;
|
|
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
|
|
}
|
|
}
|
|
|
|
// Actualizar el sprite clip
|
|
updateSpriteClip();
|
|
}
|
|
}
|
|
|
|
// Comprueba si ha terminado la animación
|
|
auto AnimatedSprite::animationIsCompleted() -> bool {
|
|
return animations_[current_animation_].completed;
|
|
}
|
|
|
|
// Establece la animacion actual
|
|
void AnimatedSprite::setCurrentAnimation(const std::string& name, bool reset) {
|
|
const auto NEW_ANIMATION = getAnimationIndex(name);
|
|
if (current_animation_ != NEW_ANIMATION) {
|
|
const auto OLD_ANIMATION = current_animation_;
|
|
current_animation_ = NEW_ANIMATION;
|
|
if (reset) {
|
|
animations_[current_animation_].current_frame = 0;
|
|
animations_[current_animation_].time_accumulator = 0.0f;
|
|
animations_[current_animation_].completed = false;
|
|
} else {
|
|
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1);
|
|
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
|
|
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
|
|
}
|
|
updateSpriteClip();
|
|
}
|
|
}
|
|
|
|
// Establece la animacion actual
|
|
void AnimatedSprite::setCurrentAnimation(int index, bool reset) {
|
|
const auto NEW_ANIMATION = index;
|
|
if (current_animation_ != NEW_ANIMATION) {
|
|
const auto OLD_ANIMATION = current_animation_;
|
|
current_animation_ = NEW_ANIMATION;
|
|
if (reset) {
|
|
animations_[current_animation_].current_frame = 0;
|
|
animations_[current_animation_].time_accumulator = 0.0f;
|
|
animations_[current_animation_].completed = false;
|
|
} else {
|
|
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size());
|
|
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
|
|
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
|
|
}
|
|
updateSpriteClip();
|
|
}
|
|
}
|
|
|
|
// Actualiza las variables del objeto (time-based)
|
|
void AnimatedSprite::update(float deltaTime) {
|
|
animate(deltaTime);
|
|
MovingSprite::update(deltaTime);
|
|
}
|
|
|
|
// Reinicia la animación
|
|
void AnimatedSprite::resetAnimation() {
|
|
animations_[current_animation_].current_frame = 0;
|
|
animations_[current_animation_].time_accumulator = 0.0f;
|
|
animations_[current_animation_].completed = false;
|
|
animations_[current_animation_].paused = false;
|
|
updateSpriteClip();
|
|
}
|
|
|
|
// Carga la animación desde un vector de cadenas
|
|
void AnimatedSprite::loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source) {
|
|
AnimationConfig config;
|
|
|
|
size_t index = 0;
|
|
while (index < source.size()) {
|
|
const std::string& line = source.at(index);
|
|
|
|
if (line == "[animation]") {
|
|
index = processAnimationBlock(source, index, config);
|
|
} else {
|
|
processConfigLine(line, config);
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
// Pone un valor por defecto
|
|
setWidth(config.frame_width);
|
|
setHeight(config.frame_height);
|
|
|
|
// Establece el primer frame inmediatamente si hay animaciones
|
|
if (!animations_.empty()) {
|
|
current_animation_ = 0;
|
|
updateSpriteClip();
|
|
}
|
|
}
|
|
|
|
// Procesa una línea de configuración
|
|
void AnimatedSprite::processConfigLine(const std::string& line, AnimationConfig& config) {
|
|
size_t pos = line.find('=');
|
|
if (pos == std::string::npos) {
|
|
return;
|
|
}
|
|
|
|
std::string key = line.substr(0, pos);
|
|
int value = std::stoi(line.substr(pos + 1));
|
|
|
|
if (key == "frame_width") {
|
|
config.frame_width = value;
|
|
updateFrameCalculations(config);
|
|
} else if (key == "frame_height") {
|
|
config.frame_height = value;
|
|
updateFrameCalculations(config);
|
|
} else {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: unknown parameter %s", key.c_str());
|
|
}
|
|
}
|
|
|
|
// Actualiza los cálculos basados en las dimensiones del frame
|
|
void AnimatedSprite::updateFrameCalculations(AnimationConfig& config) {
|
|
config.frames_per_row = getTexture()->getWidth() / config.frame_width;
|
|
const int WIDTH = getTexture()->getWidth() / config.frame_width;
|
|
const int HEIGHT = getTexture()->getHeight() / config.frame_height;
|
|
config.max_tiles = WIDTH * HEIGHT;
|
|
}
|
|
|
|
// Procesa un bloque completo de animación
|
|
auto AnimatedSprite::processAnimationBlock(const AnimationsFileBuffer& source, size_t start_index, const AnimationConfig& config) -> size_t {
|
|
Animation animation;
|
|
size_t index = start_index + 1; // Salta la línea "[animation]"
|
|
|
|
while (index < source.size()) {
|
|
const std::string& line = source.at(index);
|
|
|
|
if (line == "[/animation]") {
|
|
break;
|
|
}
|
|
|
|
processAnimationParameter(line, animation, config);
|
|
index++;
|
|
}
|
|
|
|
// Añade la animación al vector de animaciones
|
|
animations_.emplace_back(animation);
|
|
|
|
// Rellena el mapa con el nombre y el nuevo índice
|
|
animation_indices_[animation.name] = animations_.size() - 1;
|
|
|
|
return index;
|
|
}
|
|
|
|
// Procesa un parámetro individual de animación
|
|
void AnimatedSprite::processAnimationParameter(const std::string& line, Animation& animation, const AnimationConfig& config) {
|
|
size_t pos = line.find('=');
|
|
if (pos == std::string::npos) {
|
|
return;
|
|
}
|
|
|
|
std::string key = line.substr(0, pos);
|
|
std::string value = line.substr(pos + 1);
|
|
|
|
if (key == "name") {
|
|
animation.name = value;
|
|
} else if (key == "speed") {
|
|
animation.speed = std::stof(value);
|
|
} else if (key == "loop") {
|
|
animation.loop = std::stoi(value);
|
|
} else if (key == "frames") {
|
|
parseFramesParameter(value, animation, config);
|
|
} else {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: unknown parameter %s", key.c_str());
|
|
}
|
|
}
|
|
|
|
// Parsea el parámetro de frames (lista separada por comas)
|
|
void AnimatedSprite::parseFramesParameter(const std::string& frames_str, Animation& animation, const AnimationConfig& config) {
|
|
std::stringstream stream(frames_str);
|
|
std::string tmp;
|
|
SDL_FRect rect = {0, 0, config.frame_width, config.frame_height};
|
|
|
|
while (getline(stream, tmp, ',')) {
|
|
const int NUM_TILE = std::stoi(tmp);
|
|
if (NUM_TILE <= config.max_tiles) {
|
|
rect.x = (NUM_TILE % config.frames_per_row) * config.frame_width;
|
|
rect.y = (NUM_TILE / config.frames_per_row) * config.frame_height;
|
|
animation.frames.emplace_back(rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Establece la velocidad de la animación
|
|
void AnimatedSprite::setAnimationSpeed(float value) {
|
|
animations_[current_animation_].speed = value;
|
|
}
|
|
|
|
// Actualiza el spriteClip con el frame correspondiente
|
|
void AnimatedSprite::updateSpriteClip() {
|
|
setSpriteClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
|
} |