fixos en la plataforma mobil

This commit is contained in:
2026-04-08 17:39:19 +02:00
parent 3db74ebd4d
commit 88a822c562
8 changed files with 395 additions and 240 deletions

View File

@@ -91,10 +91,13 @@ items:
# Plataformas móviles en esta habitación # Plataformas móviles en esta habitación
platforms: platforms:
- animation: bin.yaml - animation: bin.yaml
position: {x: 2, y: 16} speed: 30
velocity: {x: 25, y: 0} loop: circular
boundaries: easing: cubicInOut
position1: {x: 2, y: 16}
position2: {x: 20, y: 15}
frame: 0 frame: 0
path:
- {x: 5, y: 18, wait: 2.0}
- {x: 20, y: 18, wait: 2.0}
- {x: 20, y: 13, wait: 2.0}
- {x: 5, y: 13, wait: 2.0}

View File

@@ -330,13 +330,7 @@ void MapEditor::autosave() {
room_data_.items[i].y = pos.y; room_data_.items[i].y = pos.y;
} }
// Sincronizar posiciones de plataformas desde los sprites vivos a room_data_ // Platforms are already synced via resetToInitialPosition during drag commit
auto* platform_mgr = room_->getPlatformManager();
for (int i = 0; i < platform_mgr->getCount() && i < static_cast<int>(room_data_.platforms.size()); ++i) {
SDL_FRect rect = platform_mgr->getPlatform(i)->getRect();
room_data_.platforms[i].x = rect.x;
room_data_.platforms[i].y = rect.y;
}
RoomSaver::saveYAML(file_path_, yaml_, room_data_); RoomSaver::saveYAML(file_path_, yaml_, room_data_);
} }
@@ -627,8 +621,8 @@ void MapEditor::handleMouseDown(float game_x, float game_y) {
} }
} }
// 3. Hit test on boundaries (enemies and platforms) // 3. Hit test on boundaries (enemies only)
for (auto type : {EntityType::ENEMY, EntityType::PLATFORM}) { for (auto type : {EntityType::ENEMY}) {
for (int i = 0; i < entityDataCount(type); ++i) { for (int i = 0; i < entityDataCount(type); ++i) {
auto bd = entityBoundaries(type, i); auto bd = entityBoundaries(type, i);
constexpr auto SZ = static_cast<float>(Tile::SIZE); constexpr auto SZ = static_cast<float>(Tile::SIZE);
@@ -718,9 +712,16 @@ auto MapEditor::commitEntityDrag() -> bool {
break; break;
case EntityType::PLATFORM: case EntityType::PLATFORM:
if (IDX >= 0 && IDX < static_cast<int>(room_data_.platforms.size())) { if (IDX >= 0 && IDX < static_cast<int>(room_data_.platforms.size())) {
room_data_.platforms[IDX].x = drag_.snap_x; auto& plat = room_data_.platforms[IDX];
room_data_.platforms[IDX].y = drag_.snap_y; if (!plat.path.empty()) {
room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(room_data_.platforms[IDX]); float dx = drag_.snap_x - plat.path[0].x;
float dy = drag_.snap_y - plat.path[0].y;
for (auto& wp : plat.path) {
wp.x += dx;
wp.y += dy;
}
}
room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(plat);
selection_ = {EntityType::PLATFORM, IDX}; selection_ = {EntityType::PLATFORM, IDX};
return true; return true;
} }
@@ -741,15 +742,6 @@ auto MapEditor::commitEntityDrag() -> bool {
return true; return true;
} }
break; break;
case EntityType::PLATFORM:
if (IDX >= 0 && IDX < static_cast<int>(room_data_.platforms.size())) {
room_data_.platforms[IDX].x1 = SNAP_X;
room_data_.platforms[IDX].y1 = SNAP_Y;
room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(room_data_.platforms[IDX]);
selection_ = {EntityType::PLATFORM, IDX};
return true;
}
break;
default: default:
break; break;
} }
@@ -766,15 +758,6 @@ auto MapEditor::commitEntityDrag() -> bool {
return true; return true;
} }
break; break;
case EntityType::PLATFORM:
if (IDX >= 0 && IDX < static_cast<int>(room_data_.platforms.size())) {
room_data_.platforms[IDX].x2 = SNAP_X;
room_data_.platforms[IDX].y2 = SNAP_Y;
room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(room_data_.platforms[IDX]);
selection_ = {EntityType::PLATFORM, IDX};
return true;
}
break;
default: default:
break; break;
} }
@@ -807,8 +790,14 @@ void MapEditor::moveEntityVisual() {
case EntityType::PLATFORM: case EntityType::PLATFORM:
if (drag_.index >= 0 && drag_.index < room_->getPlatformManager()->getCount()) { if (drag_.index >= 0 && drag_.index < room_->getPlatformManager()->getCount()) {
MovingPlatform::Data temp_data = room_data_.platforms[drag_.index]; MovingPlatform::Data temp_data = room_data_.platforms[drag_.index];
temp_data.x = drag_.snap_x; if (!temp_data.path.empty()) {
temp_data.y = drag_.snap_y; float dx = drag_.snap_x - temp_data.path[0].x;
float dy = drag_.snap_y - temp_data.path[0].y;
for (auto& wp : temp_data.path) {
wp.x += dx;
wp.y += dy;
}
}
room_->getPlatformManager()->getPlatform(drag_.index)->resetToInitialPosition(temp_data); room_->getPlatformManager()->getPlatform(drag_.index)->resetToInitialPosition(temp_data);
} }
break; break;
@@ -940,7 +929,7 @@ auto MapEditor::entityRect(EntityType type, int index) -> SDL_FRect {
} }
auto MapEditor::entityHasBoundaries(EntityType type) -> bool { auto MapEditor::entityHasBoundaries(EntityType type) -> bool {
return type == EntityType::ENEMY || type == EntityType::PLATFORM; return type == EntityType::ENEMY;
} }
auto MapEditor::entityBoundaries(EntityType type, int index) const -> BoundaryData { auto MapEditor::entityBoundaries(EntityType type, int index) const -> BoundaryData {
@@ -949,10 +938,6 @@ auto MapEditor::entityBoundaries(EntityType type, int index) const -> BoundaryDa
const auto& e = room_data_.enemies[index]; const auto& e = room_data_.enemies[index];
return {e.x1, e.y1, e.x2, e.y2}; return {e.x1, e.y1, e.x2, e.y2};
} }
case EntityType::PLATFORM: {
const auto& p = room_data_.platforms[index];
return {p.x1, p.y1, p.x2, p.y2};
}
default: return {}; default: return {};
} }
} }
@@ -961,7 +946,10 @@ auto MapEditor::entityPosition(EntityType type, int index) const -> std::pair<fl
switch (type) { switch (type) {
case EntityType::ENEMY: return {room_data_.enemies[index].x, room_data_.enemies[index].y}; case EntityType::ENEMY: return {room_data_.enemies[index].x, room_data_.enemies[index].y};
case EntityType::ITEM: return {room_data_.items[index].x, room_data_.items[index].y}; case EntityType::ITEM: return {room_data_.items[index].x, room_data_.items[index].y};
case EntityType::PLATFORM: return {room_data_.platforms[index].x, room_data_.platforms[index].y}; case EntityType::PLATFORM: {
const auto& path = room_data_.platforms[index].path;
return path.empty() ? std::pair{0.0F, 0.0F} : std::pair{path[0].x, path[0].y};
}
default: return {0.0F, 0.0F}; default: return {0.0F, 0.0F};
} }
} }
@@ -999,48 +987,93 @@ void MapEditor::renderEntityBoundaries() {
constexpr Uint8 DIM_BOUND2 = 13; // YELLOW constexpr Uint8 DIM_BOUND2 = 13; // YELLOW
constexpr Uint8 DIM_ROUTE = 14; // WHITE (gris medio) constexpr Uint8 DIM_ROUTE = 14; // WHITE (gris medio)
for (auto type : {EntityType::ENEMY, EntityType::PLATFORM}) { constexpr float HALF = Tile::SIZE / 2.0F;
for (int i = 0; i < entityDataCount(type); ++i) {
auto [pos_x, pos_y] = entityPosition(type, i);
auto bd = entityBoundaries(type, i);
constexpr float HALF = Tile::SIZE / 2.0F;
bool is_selected = selection_.is(type) && selection_.index == i; for (int i = 0; i < entityDataCount(EntityType::ENEMY); ++i) {
auto [pos_x, pos_y] = entityPosition(EntityType::ENEMY, i);
auto bd = entityBoundaries(EntityType::ENEMY, i);
// Posiciones base (pueden estar siendo arrastradas) bool is_selected = selection_.is(EntityType::ENEMY) && selection_.index == i;
float init_x = pos_x;
float init_y = pos_y;
auto b1_x = static_cast<float>(bd.x1);
auto b1_y = static_cast<float>(bd.y1);
auto b2_x = static_cast<float>(bd.x2);
auto b2_y = static_cast<float>(bd.y2);
// Si estamos arrastrando una boundary de esta entidad, usar la posición snapped // Posiciones base (pueden estar siendo arrastradas)
if (drag_.entity_type == type && drag_.index == i) { float init_x = pos_x;
if (drag_.target == DragTarget::ENTITY_BOUND1) { float init_y = pos_y;
b1_x = drag_.snap_x; auto b1_x = static_cast<float>(bd.x1);
b1_y = drag_.snap_y; auto b1_y = static_cast<float>(bd.y1);
} else if (drag_.target == DragTarget::ENTITY_BOUND2) { auto b2_x = static_cast<float>(bd.x2);
b2_x = drag_.snap_x; auto b2_y = static_cast<float>(bd.y2);
b2_y = drag_.snap_y;
} else if (drag_.target == DragTarget::ENTITY_INITIAL) { // Si estamos arrastrando una boundary de esta entidad, usar la posición snapped
init_x = drag_.snap_x; if (drag_.entity_type == EntityType::ENEMY && drag_.index == i) {
init_y = drag_.snap_y; if (drag_.target == DragTarget::ENTITY_BOUND1) {
} b1_x = drag_.snap_x;
is_selected = true; // Arrastrando = siempre iluminado b1_y = drag_.snap_y;
} else if (drag_.target == DragTarget::ENTITY_BOUND2) {
b2_x = drag_.snap_x;
b2_y = drag_.snap_y;
} else if (drag_.target == DragTarget::ENTITY_INITIAL) {
init_x = drag_.snap_x;
init_y = drag_.snap_y;
} }
is_selected = true; // Arrastrando = siempre iluminado
}
Uint8 color_b1 = is_selected ? SEL_BOUND1 : DIM_BOUND1; Uint8 color_b1 = is_selected ? SEL_BOUND1 : DIM_BOUND1;
Uint8 color_b2 = is_selected ? SEL_BOUND2 : DIM_BOUND2; Uint8 color_b2 = is_selected ? SEL_BOUND2 : DIM_BOUND2;
Uint8 color_route = is_selected ? SEL_ROUTE : DIM_ROUTE; Uint8 color_route = is_selected ? SEL_ROUTE : DIM_ROUTE;
// Dibujar líneas de ruta // Dibujar líneas de ruta
game_surface->drawLine(b1_x + HALF, b1_y + HALF, init_x + HALF, init_y + HALF, color_route); game_surface->drawLine(b1_x + HALF, b1_y + HALF, init_x + HALF, init_y + HALF, color_route);
game_surface->drawLine(init_x + HALF, init_y + HALF, b2_x + HALF, b2_y + HALF, color_route); game_surface->drawLine(init_x + HALF, init_y + HALF, b2_x + HALF, b2_y + HALF, color_route);
// Marcadores en las boundaries // Marcadores en las boundaries
renderBoundaryMarker(b1_x, b1_y, color_b1); renderBoundaryMarker(b1_x, b1_y, color_b1);
renderBoundaryMarker(b2_x, b2_y, color_b2); renderBoundaryMarker(b2_x, b2_y, color_b2);
}
// Render platform waypoint routes
for (int i = 0; i < entityDataCount(EntityType::PLATFORM); ++i) {
const auto& plat = room_data_.platforms[i];
if (plat.path.size() < 2) { continue; }
bool is_selected = selection_.is(EntityType::PLATFORM) && selection_.index == i;
// If dragging this platform, apply the drag offset to all points for visualization
float drag_dx = 0.0F;
float drag_dy = 0.0F;
if (drag_.entity_type == EntityType::PLATFORM && drag_.index == i && drag_.target == DragTarget::ENTITY_INITIAL) {
if (!plat.path.empty()) {
drag_dx = drag_.snap_x - plat.path[0].x;
drag_dy = drag_.snap_y - plat.path[0].y;
}
is_selected = true;
}
Uint8 color_wp = is_selected ? SEL_BOUND1 : DIM_BOUND1;
Uint8 color_route = is_selected ? SEL_ROUTE : DIM_ROUTE;
// Draw route lines between consecutive waypoints
for (int j = 0; j < static_cast<int>(plat.path.size()) - 1; ++j) {
float ax = plat.path[j].x + drag_dx + HALF;
float ay = plat.path[j].y + drag_dy + HALF;
float bx = plat.path[j + 1].x + drag_dx + HALF;
float by = plat.path[j + 1].y + drag_dy + HALF;
game_surface->drawLine(ax, ay, bx, by, color_route);
}
// For circular mode, draw line from last to first
if (plat.loop == LoopMode::CIRCULAR && plat.path.size() > 2) {
int last = static_cast<int>(plat.path.size()) - 1;
float ax = plat.path[last].x + drag_dx + HALF;
float ay = plat.path[last].y + drag_dy + HALF;
float bx = plat.path[0].x + drag_dx + HALF;
float by = plat.path[0].y + drag_dy + HALF;
game_surface->drawLine(ax, ay, bx, by, color_route);
}
// Draw waypoint markers
for (const auto& wp : plat.path) {
renderBoundaryMarker(wp.x + drag_dx, wp.y + drag_dy, color_wp);
} }
} }
} }
@@ -1145,12 +1178,10 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
if (selection_.index < static_cast<int>(room_data_.platforms.size())) { if (selection_.index < static_cast<int>(room_data_.platforms.size())) {
const auto& p = room_data_.platforms[selection_.index]; const auto& p = room_data_.platforms[selection_.index];
std::string anim = p.animation_path; std::string anim = p.animation_path;
auto dot = anim.rfind('.'); if (anim.size() > 5 && anim.substr(anim.size() - 5) == ".yaml") { anim = anim.substr(0, anim.size() - 5); }
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
line2 = "platform " + std::to_string(selection_.index) + ": " + anim; line2 = "platform " + std::to_string(selection_.index) + ": " + anim;
line3 = "vx:" + std::to_string(static_cast<int>(p.vx)) + line3 = "speed:" + std::to_string(static_cast<int>(p.speed)) + " " + (p.loop == LoopMode::CIRCULAR ? "circular" : "pingpong");
" vy:" + std::to_string(static_cast<int>(p.vy)); if (p.easing != "linear") { line3 += " " + p.easing; }
} }
break; break;
@@ -1197,7 +1228,7 @@ auto MapEditor::getSetCompletions() const -> std::vector<std::string> {
switch (selection_.type) { switch (selection_.type) {
case EntityType::ENEMY: return {"ANIMATION", "VX", "VY", "FLIP", "MIRROR"}; case EntityType::ENEMY: return {"ANIMATION", "VX", "VY", "FLIP", "MIRROR"};
case EntityType::ITEM: return {"TILE", "COUNTER"}; case EntityType::ITEM: return {"TILE", "COUNTER"};
case EntityType::PLATFORM: return {"ANIMATION", "VX", "VY"}; case EntityType::PLATFORM: return {"ANIMATION", "SPEED", "LOOP", "EASING"};
default: return {"ITEMCOLOR1", "ITEMCOLOR2", "CONVEYOR", "TILESET", "UP", "DOWN", "LEFT", "RIGHT"}; default: return {"ITEMCOLOR1", "ITEMCOLOR2", "CONVEYOR", "TILESET", "UP", "DOWN", "LEFT", "RIGHT"};
} }
} }
@@ -1856,50 +1887,56 @@ auto MapEditor::setPlatformProperty(const std::string& property, const std::stri
return "animation: " + anim; return "animation: " + anim;
} }
if (property == "VX") { if (property == "SPEED") {
try { try {
platform.vx = std::stof(value); platform.speed = std::stof(value);
} catch (...) { return "Invalid value: " + value; } } catch (...) { return "Invalid value: " + value; }
platform.vy = 0.0F;
room_->getPlatformManager()->getPlatform(selection_.index)->resetToInitialPosition(platform); room_->getPlatformManager()->getPlatform(selection_.index)->resetToInitialPosition(platform);
autosave(); autosave();
return "vx: " + std::to_string(static_cast<int>(platform.vx)) + " vy: 0"; return "speed: " + std::to_string(static_cast<int>(platform.speed));
} }
if (property == "VY") { if (property == "LOOP") {
try { std::string val = toLower(value);
platform.vy = std::stof(value); if (val == "circular") {
} catch (...) { return "Invalid value: " + value; } platform.loop = LoopMode::CIRCULAR;
platform.vx = 0.0F; } else {
platform.loop = LoopMode::PINGPONG;
val = "pingpong";
}
room_->getPlatformManager()->getPlatform(selection_.index)->resetToInitialPosition(platform); room_->getPlatformManager()->getPlatform(selection_.index)->resetToInitialPosition(platform);
autosave(); autosave();
return "vy: " + std::to_string(static_cast<int>(platform.vy)) + " vx: 0"; return "loop: " + val;
} }
return "Unknown property: " + property + " (use: animation, vx, vy)"; if (property == "EASING") {
platform.easing = toLower(value);
room_->getPlatformManager()->getPlatform(selection_.index)->resetToInitialPosition(platform);
autosave();
return "easing: " + platform.easing;
}
return "Unknown property: " + property + " (use: animation, speed, loop, easing)";
} }
// Crea una nueva plataforma con valores por defecto, centrada en la habitación // Crea una nueva plataforma con valores por defecto, centrada en la habitación
auto MapEditor::addPlatform() -> std::string { auto MapEditor::addPlatform() -> std::string {
if (!active_) { return "Editor not active"; } if (!active_) { return "Editor not active"; }
constexpr float CENTER_X = PlayArea::CENTER_X;
constexpr float CENTER_Y = PlayArea::CENTER_Y;
constexpr float ROUTE_HALF = 2.5F * Tile::SIZE; // 2.5 tiles a cada lado (5 tiles total)
MovingPlatform::Data new_platform; MovingPlatform::Data new_platform;
new_platform.animation_path = "bin.yaml"; new_platform.animation_path = "bin.yaml";
new_platform.x = CENTER_X; new_platform.speed = 24.0F;
new_platform.y = CENTER_Y; new_platform.frame = -1;
new_platform.vx = 24.0F; constexpr float CENTER_X = PlayArea::CENTER_X;
new_platform.vy = 0.0F; constexpr float CENTER_Y = PlayArea::CENTER_Y;
new_platform.x1 = static_cast<int>(CENTER_X - ROUTE_HALF); constexpr float ROUTE_HALF = 2.0F * Tile::SIZE;
new_platform.y1 = static_cast<int>(CENTER_Y); new_platform.path = {
new_platform.x2 = static_cast<int>(CENTER_X + ROUTE_HALF); {CENTER_X - ROUTE_HALF, CENTER_Y, 0.0F},
new_platform.y2 = static_cast<int>(CENTER_Y); {CENTER_X + ROUTE_HALF, CENTER_Y, 0.0F}
new_platform.frame = 0; };
room_data_.platforms.push_back(new_platform); room_data_.platforms.push_back(new_platform);
room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(new_platform)); room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(new_platform));
@@ -1937,9 +1974,9 @@ auto MapEditor::duplicatePlatform() -> std::string {
if (!hasSelectedPlatform()) { return "No platform selected"; } if (!hasSelectedPlatform()) { return "No platform selected"; }
MovingPlatform::Data copy = room_data_.platforms[selection_.index]; MovingPlatform::Data copy = room_data_.platforms[selection_.index];
copy.x += Tile::SIZE; for (auto& wp : copy.path) {
copy.x1 += Tile::SIZE; wp.x += Tile::SIZE;
copy.x2 += Tile::SIZE; }
room_data_.platforms.push_back(copy); room_data_.platforms.push_back(copy);
room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(copy)); room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(copy));

View File

@@ -162,23 +162,18 @@ auto RoomSaver::buildYAML(const fkyaml::node& original_yaml, const Room::Data& r
out << "platforms:\n"; out << "platforms:\n";
for (const auto& plat : room_data.platforms) { for (const auto& plat : room_data.platforms) {
out << " - animation: " << plat.animation_path << "\n"; out << " - animation: " << plat.animation_path << "\n";
out << " speed: " << plat.speed << "\n";
int pos_x = static_cast<int>(std::round(plat.x / Tile::SIZE)); out << " loop: " << (plat.loop == LoopMode::CIRCULAR ? "circular" : "pingpong") << "\n";
int pos_y = static_cast<int>(std::round(plat.y / Tile::SIZE)); if (plat.easing != "linear") { out << " easing: " << plat.easing << "\n"; }
out << " position: {x: " << pos_x << ", y: " << pos_y << "}\n";
out << " velocity: {x: " << plat.vx << ", y: " << plat.vy << "}\n";
int b1_x = plat.x1 / Tile::SIZE;
int b1_y = plat.y1 / Tile::SIZE;
int b2_x = plat.x2 / Tile::SIZE;
int b2_y = plat.y2 / Tile::SIZE;
out << " boundaries:\n";
out << " position1: {x: " << b1_x << ", y: " << b1_y << "}\n";
out << " position2: {x: " << b2_x << ", y: " << b2_y << "}\n";
if (plat.frame != -1) { out << " frame: " << plat.frame << "\n"; } if (plat.frame != -1) { out << " frame: " << plat.frame << "\n"; }
out << " path:\n";
for (const auto& wp : plat.path) {
int wx = static_cast<int>(std::round(wp.x / Tile::SIZE));
int wy = static_cast<int>(std::round(wp.y / Tile::SIZE));
out << " - {x: " << wx << ", y: " << wy;
if (wp.wait > 0.0F) { out << ", wait: " << wp.wait; }
out << "}\n";
}
out << "\n"; out << "\n";
} }
} }

View File

@@ -1,39 +1,181 @@
#include "game/entities/moving_platform.hpp" #include "game/entities/moving_platform.hpp"
#include <cmath> // Para std::sqrt
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite #include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
#include "core/resources/resource_cache.hpp" // Para Resource #include "core/resources/resource_cache.hpp" // Para Resource
#include "utils/easing_functions.hpp" // Para Easing::*
// Resuelve el nombre de un easing a su función
auto MovingPlatform::resolveEasing(const std::string& name) -> EasingFunc {
if (name == "quadIn") { return Easing::quadIn; }
if (name == "quadOut") { return Easing::quadOut; }
if (name == "quadInOut") { return Easing::quadInOut; }
if (name == "cubicIn") { return Easing::cubicIn; }
if (name == "cubicOut") { return Easing::cubicOut; }
if (name == "cubicInOut") { return Easing::cubicInOut; }
if (name == "sineIn") { return Easing::sineIn; }
if (name == "sineOut") { return Easing::sineOut; }
if (name == "sineInOut") { return Easing::sineInOut; }
return Easing::linear;
}
// Constructor // Constructor
MovingPlatform::MovingPlatform(const Data& data) MovingPlatform::MovingPlatform(const Data& data)
: sprite_(std::make_shared<AnimatedSprite>(Resource::Cache::get()->getAnimationData(data.animation_path))), : sprite_(std::make_shared<AnimatedSprite>(Resource::Cache::get()->getAnimationData(data.animation_path))),
x1_(data.x1), path_(data.path),
x2_(data.x2), speed_(data.speed),
y1_(data.y1), loop_mode_(data.loop),
y2_(data.y2) { easing_(resolveEasing(data.easing)) {
sprite_->setPosX(data.x); // Colocar el sprite en el primer waypoint
sprite_->setPosY(data.y); if (!path_.empty()) {
sprite_->setVelX(data.vx); sprite_->setPosX(path_[0].x);
sprite_->setVelY(data.vy); sprite_->setPosY(path_[0].y);
}
collider_ = getRect(); collider_ = getRect();
// Coloca un frame al azar o el designado // Frame inicial
sprite_->setCurrentAnimationFrame((data.frame == -1) ? (rand() % sprite_->getCurrentAnimationSize()) : data.frame); sprite_->setCurrentAnimationFrame((data.frame == -1) ? (rand() % sprite_->getCurrentAnimationSize()) : data.frame);
// Calcular longitud del primer segmento
recalcSegmentLength();
} }
// Actualiza posición, calcula desplazamiento real del frame // Índice del waypoint origen del segmento actual
auto MovingPlatform::getSegmentFrom() const -> int {
if (direction_ == 1) {
return current_segment_;
}
// Pingpong retrocediendo: segmento N va de path[N+1] a path[N]
return current_segment_ + 1;
}
// Índice del waypoint destino del segmento actual
auto MovingPlatform::getSegmentTo() const -> int {
if (direction_ == 1) {
if (loop_mode_ == LoopMode::CIRCULAR) {
return (current_segment_ + 1) % static_cast<int>(path_.size());
}
return current_segment_ + 1;
}
// Pingpong retrocediendo
return current_segment_;
}
// Recalcula la longitud del segmento actual
void MovingPlatform::recalcSegmentLength() {
if (path_.size() < 2) {
segment_length_ = 0.0F;
return;
}
int from = getSegmentFrom();
int to = getSegmentTo();
float dx = path_[to].x - path_[from].x;
float dy = path_[to].y - path_[from].y;
segment_length_ = std::sqrt(dx * dx + dy * dy);
}
// Avanza al siguiente segmento
void MovingPlatform::advanceSegment() {
int path_size = static_cast<int>(path_.size());
if (loop_mode_ == LoopMode::PINGPONG) {
if (direction_ == 1) {
if (current_segment_ + 1 >= path_size - 1) {
// Llegamos al final, invertir dirección
direction_ = -1;
current_segment_ = path_size - 2;
} else {
current_segment_++;
}
} else {
if (current_segment_ <= 0) {
// Llegamos al inicio, invertir dirección
direction_ = 1;
current_segment_ = 0;
} else {
current_segment_--;
}
}
} else {
// CIRCULAR
current_segment_ = (current_segment_ + 1) % path_size;
}
segment_progress_ = 0.0F;
recalcSegmentLength();
}
// Actualiza posición de la plataforma siguiendo la ruta
void MovingPlatform::update(float delta_time) { void MovingPlatform::update(float delta_time) {
sprite_->animate(delta_time);
if (path_.size() < 2) {
last_dx_ = 0.0F;
last_dy_ = 0.0F;
return;
}
float old_x = sprite_->getPosX(); float old_x = sprite_->getPosX();
float old_y = sprite_->getPosY(); float old_y = sprite_->getPosY();
sprite_->update(delta_time); // Si estamos esperando en un waypoint
checkPath(); if (waiting_) {
wait_timer_ -= delta_time;
if (wait_timer_ <= 0.0F) {
waiting_ = false;
advanceSegment();
} else {
last_dx_ = 0.0F;
last_dy_ = 0.0F;
return;
}
}
// Avanzar por el segmento
if (segment_length_ > 0.0F) {
float distance = speed_ * delta_time;
float delta_progress = distance / segment_length_;
segment_progress_ += delta_progress;
} else {
// Segmento de longitud 0: saltar al siguiente
segment_progress_ = 1.0F;
}
// Comprobar si llegamos al final del segmento
if (segment_progress_ >= 1.0F) {
segment_progress_ = 1.0F;
// Colocar en el waypoint destino exacto
int to = getSegmentTo();
sprite_->setPosX(path_[to].x);
sprite_->setPosY(path_[to].y);
// Comprobar si hay espera en este waypoint
if (path_[to].wait > 0.0F) {
waiting_ = true;
wait_timer_ = path_[to].wait;
} else {
advanceSegment();
}
} else {
// Interpolar posición con easing
float t = easing_(segment_progress_);
int from = getSegmentFrom();
int to = getSegmentTo();
float new_x = path_[from].x + (path_[to].x - path_[from].x) * t;
float new_y = path_[from].y + (path_[to].y - path_[from].y) * t;
sprite_->setPosX(new_x);
sprite_->setPosY(new_y);
}
last_dx_ = sprite_->getPosX() - old_x; last_dx_ = sprite_->getPosX() - old_x;
last_dy_ = sprite_->getPosY() - old_y; last_dy_ = sprite_->getPosY() - old_y;
collider_ = getRect(); collider_ = getRect();
} }
@@ -50,17 +192,24 @@ void MovingPlatform::updateAnimation(float delta_time) {
// Resetea la plataforma a su posición inicial (para editor) // Resetea la plataforma a su posición inicial (para editor)
void MovingPlatform::resetToInitialPosition(const Data& data) { void MovingPlatform::resetToInitialPosition(const Data& data) {
sprite_->setPosX(data.x); path_ = data.path;
sprite_->setPosY(data.y); speed_ = data.speed;
sprite_->setVelX(data.vx); loop_mode_ = data.loop;
sprite_->setVelY(data.vy); easing_ = resolveEasing(data.easing);
x1_ = data.x1; current_segment_ = 0;
x2_ = data.x2; direction_ = 1;
y1_ = data.y1; segment_progress_ = 0.0F;
y2_ = data.y2; waiting_ = false;
wait_timer_ = 0.0F;
if (!path_.empty()) {
sprite_->setPosX(path_[0].x);
sprite_->setPosY(path_[0].y);
}
collider_ = getRect(); collider_ = getRect();
recalcSegmentLength();
} }
#endif #endif
@@ -73,24 +222,3 @@ auto MovingPlatform::getRect() -> SDL_FRect {
auto MovingPlatform::getCollider() -> SDL_FRect& { auto MovingPlatform::getCollider() -> SDL_FRect& {
return collider_; return collider_;
} }
// Comprueba los límites del recorrido para invertir dirección
void MovingPlatform::checkPath() { // NOLINT(readability-make-member-function-const)
if (sprite_->getPosX() > x2_ || sprite_->getPosX() < x1_) {
if (sprite_->getPosX() > x2_) {
sprite_->setPosX(x2_);
} else {
sprite_->setPosX(x1_);
}
sprite_->setVelX(sprite_->getVelX() * (-1));
}
if (sprite_->getPosY() > y2_ || sprite_->getPosY() < y1_) {
if (sprite_->getPosY() > y2_) {
sprite_->setPosY(y2_);
} else {
sprite_->setPosY(y1_);
}
sprite_->setVelY(sprite_->getVelY() * (-1));
}
}

View File

@@ -4,22 +4,32 @@
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <string> // Para string #include <string> // Para string
#include <vector> // Para vector
class AnimatedSprite; class AnimatedSprite;
// Punto de paso en la ruta de una plataforma
struct Waypoint {
float x{0.0F}; // Posición en pixels
float y{0.0F};
float wait{0.0F}; // Tiempo de parada en este punto (segundos, 0 = sin parada)
};
// Modo de recorrido de la ruta
enum class LoopMode { PINGPONG, CIRCULAR };
// Tipo de función de easing
using EasingFunc = float (*)(float);
class MovingPlatform { class MovingPlatform {
public: public:
struct Data { struct Data {
std::string animation_path; // Ruta al fichero con la animación std::string animation_path;
float x{0.0F}; // Posición inicial en el eje X float speed{30.0F}; // px/s a lo largo del path
float y{0.0F}; // Posición inicial en el eje Y LoopMode loop{LoopMode::PINGPONG};
float vx{0.0F}; // Velocidad en el eje X std::string easing{"linear"}; // Nombre del easing
float vy{0.0F}; // Velocidad en el eje Y int frame{0}; // Frame inicial (-1 = random)
int x1{0}; // Límite izquierdo de la ruta en el eje X std::vector<Waypoint> path; // Mínimo 2 puntos
int x2{0}; // Límite derecho de la ruta en el eje X
int y1{0}; // Límite superior de la ruta en el eje Y
int y2{0}; // Límite inferior de la ruta en el eje Y
int frame{0}; // Frame inicial para la animación
}; };
explicit MovingPlatform(const Data& data); explicit MovingPlatform(const Data& data);
@@ -35,19 +45,31 @@ class MovingPlatform {
auto getRect() -> SDL_FRect; auto getRect() -> SDL_FRect;
auto getCollider() -> SDL_FRect&; auto getCollider() -> SDL_FRect&;
// Desplazamiento real del último frame (para transportar al jugador)
[[nodiscard]] auto getLastDX() const -> float { return last_dx_; } [[nodiscard]] auto getLastDX() const -> float { return last_dx_; }
[[nodiscard]] auto getLastDY() const -> float { return last_dy_; } [[nodiscard]] auto getLastDY() const -> float { return last_dy_; }
private: private:
void checkPath(); // Comprueba los límites del recorrido void advanceSegment();
void recalcSegmentLength();
[[nodiscard]] auto getSegmentFrom() const -> int;
[[nodiscard]] auto getSegmentTo() const -> int;
static auto resolveEasing(const std::string& name) -> EasingFunc;
std::shared_ptr<AnimatedSprite> sprite_; std::shared_ptr<AnimatedSprite> sprite_;
SDL_FRect collider_{}; SDL_FRect collider_{};
int x1_{0}; float last_dx_{0.0F};
int x2_{0}; float last_dy_{0.0F};
int y1_{0};
int y2_{0}; // Estado del path
float last_dx_{0.0F}; // Desplazamiento horizontal del último frame std::vector<Waypoint> path_;
float last_dy_{0.0F}; // Desplazamiento vertical del último frame float speed_{0.0F};
LoopMode loop_mode_{LoopMode::PINGPONG};
EasingFunc easing_{nullptr};
int current_segment_{0}; // Índice del segmento actual
int direction_{1}; // +1 avanzando, -1 retrocediendo (pingpong)
float segment_progress_{0.0F}; // Progreso dentro del segmento (0.0 a 1.0)
float segment_length_{0.0F}; // Longitud del segmento actual en pixels
float wait_timer_{0.0F}; // Tiempo restante de parada
bool waiting_{false}; // Está parado en un waypoint?
}; };

View File

@@ -84,9 +84,10 @@ auto PlatformManager::checkPlayerOnPlatform(const SDL_FRect& player_collider, fl
float player_feet = player_collider.y + player_collider.h; float player_feet = player_collider.y + player_collider.h;
float platform_top = plat_rect.y; float platform_top = plat_rect.y;
// Tolerancia de 4px (medio tile) para compensar el movimiento entre frames // Tolerancia bidireccional de 4px para compensar el movimiento entre frames
// (cuando la plataforma baja, los pies quedan por encima del top momentáneamente)
constexpr float TOLERANCE = 4.0F; constexpr float TOLERANCE = 4.0F;
if (player_feet >= platform_top && player_feet <= platform_top + TOLERANCE) { if (player_feet >= platform_top - TOLERANCE && player_feet <= platform_top + TOLERANCE) {
return platform.get(); return platform.get();
} }
} }

View File

@@ -328,70 +328,40 @@ void RoomLoader::parseItems(const fkyaml::node& yaml, Room::Data& room, bool ver
} }
} }
// Parsea los límites de movimiento de una plataforma
void RoomLoader::parsePlatformBoundaries(const fkyaml::node& bounds_node, MovingPlatform::Data& platform) {
// Formato: position1 y position2
if (bounds_node.contains("position1")) {
const auto& pos1 = bounds_node["position1"];
if (pos1.contains("x")) {
platform.x1 = pos1["x"].get_value<int>() * Tile::SIZE;
}
if (pos1.contains("y")) {
platform.y1 = pos1["y"].get_value<int>() * Tile::SIZE;
}
}
if (bounds_node.contains("position2")) {
const auto& pos2 = bounds_node["position2"];
if (pos2.contains("x")) {
platform.x2 = pos2["x"].get_value<int>() * Tile::SIZE;
}
if (pos2.contains("y")) {
platform.y2 = pos2["y"].get_value<int>() * Tile::SIZE;
}
}
}
// Parsea los datos de una plataforma individual // Parsea los datos de una plataforma individual
auto RoomLoader::parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data { auto RoomLoader::parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data {
MovingPlatform::Data platform; MovingPlatform::Data platform;
// Animation path
if (platform_node.contains("animation")) { if (platform_node.contains("animation")) {
platform.animation_path = platform_node["animation"].get_value<std::string>(); platform.animation_path = platform_node["animation"].get_value<std::string>();
} }
if (platform_node.contains("speed")) {
// Position (in tiles, convert to pixels) platform.speed = platform_node["speed"].get_value<float>();
if (platform_node.contains("position")) {
const auto& pos = platform_node["position"];
if (pos.contains("x")) {
platform.x = pos["x"].get_value<float>() * Tile::SIZE;
}
if (pos.contains("y")) {
platform.y = pos["y"].get_value<float>() * Tile::SIZE;
}
} }
if (platform_node.contains("loop")) {
// Velocity (already in pixels/second) auto loop_str = platform_node["loop"].get_value<std::string>();
if (platform_node.contains("velocity")) { platform.loop = (loop_str == "circular") ? LoopMode::CIRCULAR : LoopMode::PINGPONG;
const auto& vel = platform_node["velocity"];
if (vel.contains("x")) {
platform.vx = vel["x"].get_value<float>();
}
if (vel.contains("y")) {
platform.vy = vel["y"].get_value<float>();
}
} }
if (platform_node.contains("easing")) {
// Boundaries (in tiles, convert to pixels) platform.easing = platform_node["easing"].get_value<std::string>();
if (platform_node.contains("boundaries")) {
parsePlatformBoundaries(platform_node["boundaries"], platform);
} }
// Optional frame
platform.frame = platform_node.contains("frame") platform.frame = platform_node.contains("frame")
? platform_node["frame"].get_value_or<int>(-1) ? platform_node["frame"].get_value_or<int>(-1)
: -1; : -1;
// Path: lista de waypoints en tiles → pixels
if (platform_node.contains("path")) {
for (const auto& wp_node : platform_node["path"]) {
Waypoint wp;
wp.x = wp_node["x"].get_value<float>() * Tile::SIZE;
wp.y = wp_node["y"].get_value<float>() * Tile::SIZE;
if (wp_node.contains("wait")) {
wp.wait = wp_node["wait"].get_value<float>();
}
platform.path.push_back(wp);
}
}
return platform; return platform;
} }

View File

@@ -137,5 +137,4 @@ class RoomLoader {
static void parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose); static void parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose);
static auto parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data; static auto parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data;
static void parsePlatformBoundaries(const fkyaml::node& bounds_node, MovingPlatform::Data& platform);
}; };