time-based: migrada escena Title (AnimatedSprite/Fade amb dual-API, counters a acumuladors)

This commit is contained in:
2026-05-19 16:31:57 +02:00
parent 2b57bfa4dd
commit fe240c750e
6 changed files with 259 additions and 167 deletions
+114 -76
View File
@@ -8,88 +8,95 @@
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);
} 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.loop = -1;
buffer.counter = 0;
buffer.current_frame = 0;
buffer.completed = false;
std::string line;
do {
if (!std::getline(file, line)) {
break;
// 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();
}
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);
}
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);
}
} 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;
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';
}
}
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;
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
@@ -234,6 +241,31 @@ void AnimatedSprite::animate() {
}
}
// 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();
@@ -350,6 +382,12 @@ void AnimatedSprite::update() {
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});