408 lines
15 KiB
C++
408 lines
15 KiB
C++
#include "core/rendering/animatedsprite.h"
|
|
|
|
#include <iostream> // for cout
|
|
#include <sstream> // for basic_stringstream
|
|
|
|
#include "core/rendering/texture.h" // for Texture
|
|
#include "core/resources/resource_helper.h" // for loadFile (pack + filesystem fallback)
|
|
|
|
namespace {
|
|
|
|
// Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que
|
|
// line == "[animation]" no faci match i el parser entri en bucle
|
|
// infinit / no carregui cap animació.
|
|
void stripCr(std::string &s) {
|
|
if (!s.empty() && s.back() == '\r') {
|
|
s.pop_back();
|
|
}
|
|
}
|
|
|
|
void parseFramesList(const std::string &value, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles) {
|
|
std::stringstream ss(value);
|
|
std::string tmp;
|
|
SDL_Rect rect = {0, 0, frame_width, frame_height};
|
|
while (getline(ss, tmp, ',')) {
|
|
const int NUM_TILE = std::stoi(tmp) > max_tiles ? 0 : std::stoi(tmp);
|
|
rect.x = (NUM_TILE % frames_per_row) * frame_width;
|
|
rect.y = (NUM_TILE / frames_per_row) * frame_height;
|
|
buffer.frames.push_back(rect);
|
|
}
|
|
}
|
|
|
|
void parseAnimationField(const std::string &line, int pos, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) {
|
|
const std::string KEY = line.substr(0, pos);
|
|
const std::string VALUE = line.substr(pos + 1, line.length());
|
|
if (KEY == "name") {
|
|
buffer.name = VALUE;
|
|
} else if (KEY == "speed") {
|
|
buffer.speed = std::stoi(VALUE);
|
|
// Time-based: el valor del .ani s'expressa en "ticks per frame
|
|
// d'animació" (assumint 60 Hz). El camp `speed` (int) es manté per al
|
|
// fallback frame-based; el nou `step_duration_s` (float) és el que
|
|
// gasta animate(dt).
|
|
buffer.step_duration_s = static_cast<float>(buffer.speed) / 60.0F;
|
|
} else if (KEY == "loop") {
|
|
buffer.loop = std::stoi(VALUE);
|
|
} else if (KEY == "frames") {
|
|
parseFramesList(VALUE, buffer, frame_width, frame_height, frames_per_row, max_tiles);
|
|
} else {
|
|
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n';
|
|
}
|
|
}
|
|
|
|
auto parseAnimationBlock(std::istream &file, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) -> Animation {
|
|
Animation buffer;
|
|
buffer.speed = 0;
|
|
buffer.step_duration_s = 0.0F;
|
|
buffer.loop = -1;
|
|
buffer.counter = 0;
|
|
buffer.current_frame = 0;
|
|
buffer.completed = false;
|
|
buffer.time_accumulator_s = 0.0F;
|
|
|
|
std::string line;
|
|
do {
|
|
if (!std::getline(file, line)) {
|
|
break;
|
|
}
|
|
stripCr(line);
|
|
int pos = line.find('=');
|
|
if (pos != (int)std::string::npos) {
|
|
parseAnimationField(line, pos, buffer, frame_width, frame_height, frames_per_row, max_tiles, filename);
|
|
}
|
|
} while (line != "[/animation]");
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void parseGlobalField(const std::string &line, int pos, int &frames_per_row, int &frame_width, int &frame_height, int &max_tiles, const Texture *texture, const std::string &filename) {
|
|
const std::string KEY = line.substr(0, pos);
|
|
const std::string VALUE = line.substr(pos + 1, line.length());
|
|
if (KEY == "framesPerRow") {
|
|
frames_per_row = std::stoi(VALUE);
|
|
} else if (KEY == "frameWidth") {
|
|
frame_width = std::stoi(VALUE);
|
|
} else if (KEY == "frameHeight") {
|
|
frame_height = std::stoi(VALUE);
|
|
} else {
|
|
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n';
|
|
}
|
|
|
|
if (frames_per_row == 0 && frame_width > 0) {
|
|
frames_per_row = texture->getWidth() / frame_width;
|
|
}
|
|
if (max_tiles == 0 && frame_width > 0 && frame_height > 0) {
|
|
const int W = texture->getWidth() / frame_width;
|
|
const int H = texture->getHeight() / frame_height;
|
|
max_tiles = W * H;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Parser compartido: lee un istream con el formato .ani
|
|
static auto parseAnimationStream(std::istream &file, Texture *texture, const std::string &filename, bool verbose) -> AnimatedSpriteData {
|
|
AnimatedSpriteData as;
|
|
as.texture = texture;
|
|
int frames_per_row = 0;
|
|
int frame_width = 0;
|
|
int frame_height = 0;
|
|
int max_tiles = 0;
|
|
std::string line;
|
|
|
|
if (verbose) {
|
|
std::cout << "Animation loaded: " << filename << '\n';
|
|
}
|
|
|
|
while (std::getline(file, line)) {
|
|
stripCr(line);
|
|
if (line == "[animation]") {
|
|
as.animations.push_back(parseAnimationBlock(file, frame_width, frame_height, frames_per_row, max_tiles, filename));
|
|
} else {
|
|
int pos = line.find('=');
|
|
if (pos != (int)std::string::npos) {
|
|
parseGlobalField(line, pos, frames_per_row, frame_width, frame_height, max_tiles, texture, filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
return as;
|
|
}
|
|
|
|
// Carga la animación desde un fichero (vía ResourceHelper: pack si està inicialitzat, filesystem si no)
|
|
auto loadAnimationFromFile(Texture *texture, const std::string &file_path, bool verbose) -> AnimatedSpriteData {
|
|
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
|
|
auto bytes = ResourceHelper::loadFile(file_path);
|
|
if (bytes.empty()) {
|
|
if (verbose) {
|
|
std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << '\n';
|
|
}
|
|
AnimatedSpriteData as;
|
|
as.texture = texture;
|
|
return as;
|
|
}
|
|
return loadAnimationFromMemory(texture, bytes, FILE_NAME, verbose);
|
|
}
|
|
|
|
// Carga la animación desde bytes en memoria
|
|
auto loadAnimationFromMemory(Texture *texture, const std::vector<uint8_t> &bytes, const std::string &name_for_logs, bool verbose) -> AnimatedSpriteData {
|
|
if (bytes.empty()) {
|
|
AnimatedSpriteData as;
|
|
as.texture = texture;
|
|
return as;
|
|
}
|
|
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
|
std::stringstream ss(content);
|
|
return parseAnimationStream(ss, texture, name_for_logs, verbose);
|
|
}
|
|
|
|
// Constructor
|
|
AnimatedSprite::AnimatedSprite(Texture *texture, SDL_Renderer *renderer, const std::string &file, const std::vector<std::string> *buffer)
|
|
: current_animation_(0) {
|
|
// Copia los punteros
|
|
setTexture(texture);
|
|
setRenderer(renderer);
|
|
|
|
// Carga las animaciones
|
|
if (!file.empty()) {
|
|
AnimatedSpriteData as = loadAnimationFromFile(texture, file);
|
|
|
|
// Copia los datos de las animaciones
|
|
animation_.insert(animation_.end(), as.animations.begin(), as.animations.end());
|
|
}
|
|
|
|
else if (buffer != nullptr) {
|
|
loadFromVector(buffer);
|
|
}
|
|
}
|
|
|
|
// Constructor
|
|
AnimatedSprite::AnimatedSprite(SDL_Renderer *renderer, AnimatedSpriteData *data)
|
|
: current_animation_(0) {
|
|
// Copia los punteros
|
|
setTexture(data->texture);
|
|
setRenderer(renderer);
|
|
|
|
// Copia los datos de las animaciones
|
|
this->animation_.insert(this->animation_.end(), data->animations.begin(), data->animations.end());
|
|
}
|
|
|
|
// Destructor
|
|
AnimatedSprite::~AnimatedSprite() {
|
|
for (auto &a : animation_) {
|
|
a.frames.clear();
|
|
}
|
|
animation_.clear();
|
|
}
|
|
|
|
// Obtiene el indice de la animación a partir del nombre
|
|
auto AnimatedSprite::getIndex(const std::string &name) -> int {
|
|
int index = -1;
|
|
|
|
for (const auto &a : animation_) {
|
|
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
|
|
void AnimatedSprite::animate() {
|
|
if (!enabled_ || animation_[current_animation_].speed == 0) {
|
|
return;
|
|
}
|
|
|
|
// Calcula el frame actual a partir del contador
|
|
animation_[current_animation_].current_frame = animation_[current_animation_].counter / animation_[current_animation_].speed;
|
|
|
|
// Si alcanza el final de la animación, reinicia el contador de la animación
|
|
// en función de la variable loop y coloca el nuevo frame
|
|
if (animation_[current_animation_].current_frame >= (int)animation_[current_animation_].frames.size()) {
|
|
if (animation_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
|
|
animation_[current_animation_].current_frame = animation_[current_animation_].frames.size();
|
|
animation_[current_animation_].completed = true;
|
|
} else { // Si hay loop, vuelve al frame indicado
|
|
animation_[current_animation_].counter = 0;
|
|
animation_[current_animation_].current_frame = animation_[current_animation_].loop;
|
|
}
|
|
}
|
|
// En caso contrario
|
|
else {
|
|
// Escoge el frame correspondiente de la animación
|
|
setSpriteClip(animation_[current_animation_].frames[animation_[current_animation_].current_frame]);
|
|
|
|
// Incrementa el contador de la animacion
|
|
animation_[current_animation_].counter++;
|
|
}
|
|
}
|
|
|
|
// Time-based: avança l'acumulador i calcula el frame actual a partir de
|
|
// `step_duration_s`. La lògica de loop/completed és la mateixa que la
|
|
// variant frame-based.
|
|
void AnimatedSprite::animate(float dt_s) {
|
|
Animation &anim = animation_[current_animation_];
|
|
if (!enabled_ || anim.step_duration_s <= 0.0F) {
|
|
return;
|
|
}
|
|
|
|
anim.time_accumulator_s += dt_s;
|
|
anim.current_frame = static_cast<int>(anim.time_accumulator_s / anim.step_duration_s);
|
|
|
|
if (anim.current_frame >= (int)anim.frames.size()) {
|
|
if (anim.loop == -1) {
|
|
anim.current_frame = anim.frames.size();
|
|
anim.completed = true;
|
|
} else {
|
|
anim.time_accumulator_s = 0.0F;
|
|
anim.current_frame = anim.loop;
|
|
}
|
|
} else {
|
|
setSpriteClip(anim.frames[anim.current_frame]);
|
|
}
|
|
}
|
|
|
|
// Obtiene el numero de frames de la animación actual
|
|
auto AnimatedSprite::getNumFrames() -> int {
|
|
return (int)animation_[current_animation_].frames.size();
|
|
}
|
|
|
|
// Establece el frame actual de la animación
|
|
void AnimatedSprite::setCurrentFrame(int num) {
|
|
// Descarta valores fuera de rango
|
|
if (num >= (int)animation_[current_animation_].frames.size()) {
|
|
num = 0;
|
|
}
|
|
|
|
// Cambia el valor de la variable
|
|
animation_[current_animation_].current_frame = num;
|
|
animation_[current_animation_].counter = 0;
|
|
|
|
// Escoge el frame correspondiente de la animación
|
|
setSpriteClip(animation_[current_animation_].frames[animation_[current_animation_].current_frame]);
|
|
}
|
|
|
|
// Establece el valor del contador
|
|
void AnimatedSprite::setAnimationCounter(const std::string &name, int num) {
|
|
animation_[getIndex(name)].counter = num;
|
|
}
|
|
|
|
// Establece la velocidad de una animación
|
|
void AnimatedSprite::setAnimationSpeed(const std::string &name, int speed) {
|
|
animation_[getIndex(name)].counter = speed;
|
|
}
|
|
|
|
// Establece la velocidad de una animación
|
|
void AnimatedSprite::setAnimationSpeed(int index, int speed) {
|
|
animation_[index].counter = speed;
|
|
}
|
|
|
|
// Establece si la animación se reproduce en bucle
|
|
void AnimatedSprite::setAnimationLoop(const std::string &name, int loop) {
|
|
animation_[getIndex(name)].loop = loop;
|
|
}
|
|
|
|
// Establece si la animación se reproduce en bucle
|
|
void AnimatedSprite::setAnimationLoop(int index, int loop) {
|
|
animation_[index].loop = loop;
|
|
}
|
|
|
|
// Establece el valor de la variable
|
|
void AnimatedSprite::setAnimationCompleted(const std::string &name, bool value) {
|
|
animation_[getIndex(name)].completed = value;
|
|
}
|
|
|
|
// OLD - Establece el valor de la variable
|
|
void AnimatedSprite::setAnimationCompleted(int index, bool value) {
|
|
animation_[index].completed = value;
|
|
}
|
|
|
|
// Comprueba si ha terminado la animación
|
|
auto AnimatedSprite::animationIsCompleted() -> bool {
|
|
return animation_[current_animation_].completed;
|
|
}
|
|
|
|
// Devuelve el rectangulo de una animación y frame concreto
|
|
auto AnimatedSprite::getAnimationClip(const std::string &name, Uint8 index) -> SDL_Rect {
|
|
return animation_[getIndex(name)].frames[index];
|
|
}
|
|
|
|
// Devuelve el rectangulo de una animación y frame concreto
|
|
auto AnimatedSprite::getAnimationClip(int index_a, Uint8 index_f) -> SDL_Rect {
|
|
return animation_[index_a].frames[index_f];
|
|
}
|
|
|
|
// Carga la animación desde un vector (reutiliza parseAnimationStream via stringstream)
|
|
auto AnimatedSprite::loadFromVector(const std::vector<std::string> *source) -> bool {
|
|
std::stringstream ss;
|
|
for (const auto &line : *source) {
|
|
ss << line << '\n';
|
|
}
|
|
AnimatedSpriteData as = parseAnimationStream(ss, texture_, "", false);
|
|
animation_.insert(animation_.end(), as.animations.begin(), as.animations.end());
|
|
|
|
// El primer frame lleva frame_width/frame_height en .w/.h — los usamos como rect por defecto
|
|
if (!as.animations.empty() && !as.animations.front().frames.empty()) {
|
|
const auto &first = as.animations.front().frames.front();
|
|
setRect({0, 0, first.w, first.h});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Establece la animacion actual
|
|
void AnimatedSprite::setCurrentAnimation(const std::string &name) {
|
|
const int NEW_ANIMATION = getIndex(name);
|
|
if (current_animation_ != NEW_ANIMATION) {
|
|
current_animation_ = NEW_ANIMATION;
|
|
animation_[current_animation_].current_frame = 0;
|
|
animation_[current_animation_].counter = 0;
|
|
animation_[current_animation_].completed = false;
|
|
}
|
|
}
|
|
|
|
// Establece la animacion actual
|
|
void AnimatedSprite::setCurrentAnimation(int index) {
|
|
const int NEW_ANIMATION = index;
|
|
if (current_animation_ != NEW_ANIMATION) {
|
|
current_animation_ = NEW_ANIMATION;
|
|
animation_[current_animation_].current_frame = 0;
|
|
animation_[current_animation_].counter = 0;
|
|
animation_[current_animation_].completed = false;
|
|
}
|
|
}
|
|
|
|
// Actualiza las variables del objeto
|
|
void AnimatedSprite::update() {
|
|
animate();
|
|
MovingSprite::update();
|
|
}
|
|
|
|
// Time-based: animate(dt) + move(dt)
|
|
void AnimatedSprite::update(float dt_s) {
|
|
animate(dt_s);
|
|
MovingSprite::update(dt_s);
|
|
}
|
|
|
|
// Establece el rectangulo para un frame de una animación
|
|
void AnimatedSprite::setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h) {
|
|
animation_[index_animation].frames.push_back({x, y, w, h});
|
|
}
|
|
|
|
// OLD - Establece el contador para todas las animaciones
|
|
void AnimatedSprite::setAnimationCounter(int value) {
|
|
for (auto &a : animation_) {
|
|
a.counter = value;
|
|
}
|
|
}
|
|
|
|
// Reinicia la animación
|
|
void AnimatedSprite::resetAnimation() {
|
|
animation_[current_animation_].current_frame = 0;
|
|
animation_[current_animation_].counter = 0;
|
|
animation_[current_animation_].completed = false;
|
|
} |