Files
aee-arcade/source/core/rendering/sprite/animated_sprite.cpp
T

202 lines
7.5 KiB
C++

#include "core/rendering/sprite/animated_sprite.hpp"
#include <cmath>
#include <iostream>
#include <numeric>
#include "core/rendering/surface.hpp"
#include "core/resources/resource_cache.hpp"
#include "external/fkyaml_node.hpp"
namespace {
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 = static_cast<float>(NUM_TILE % frames_per_row) * frame_width;
rect.y = static_cast<float>(NUM_TILE / frames_per_row) * frame_height;
frames.emplace_back(rect);
}
}
return frames;
}
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;
}
struct SheetParams {
float frame_width{0.0F};
float frame_height{0.0F};
int frames_per_row{1};
int max_tiles{1};
};
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_TILES = surface->getHeight() / static_cast<int>(params.frame_height);
params.max_tiles = params.frames_per_row * H_TILES;
}
return params;
}
} // namespace
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;
}
}
void AnimatedSprite::buildNameIndex() {
animation_index_.clear();
for (int i = 0; i < static_cast<int>(animations_.size()); ++i) {
animation_index_[animations_[i].name] = i;
}
}
void AnimatedSprite::animate(float delta_time) {
if (animations_.empty()) { return; }
auto& anim = animations_[current_animation_];
if (anim.speeds.empty()) { return; }
anim.accumulated_time += delta_time;
const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F);
if (anim.accumulated_time >= TOTAL) {
if (anim.loop_from < 0) {
anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
anim.completed = true;
setClip(anim.frames[anim.current_frame]);
return;
}
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);
}
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;
}
}
anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
setClip(anim.frames[anim.current_frame]);
}
auto AnimatedSprite::animationIsCompleted() -> bool {
return animations_[current_animation_].completed;
}
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);
}
void AnimatedSprite::setCurrentAnimation(int index) {
if (current_animation_ != index) {
current_animation_ = index;
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]);
}
}
void AnimatedSprite::update(float delta_time) {
animate(delta_time);
MovingSprite::update(delta_time);
}
void AnimatedSprite::resetAnimation() {
animations_[current_animation_].current_frame = 0;
animations_[current_animation_].accumulated_time = 0.0F;
animations_[current_animation_].completed = false;
}
void AnimatedSprite::setCurrentAnimationFrame(int num) {
if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size())) {
num = 0;
}
animations_[current_animation_].current_frame = num;
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}