fixos en la plataforma mobil
This commit is contained in:
@@ -91,10 +91,13 @@ items:
|
||||
# Plataformas móviles en esta habitación
|
||||
platforms:
|
||||
- animation: bin.yaml
|
||||
position: {x: 2, y: 16}
|
||||
velocity: {x: 25, y: 0}
|
||||
boundaries:
|
||||
position1: {x: 2, y: 16}
|
||||
position2: {x: 20, y: 15}
|
||||
speed: 30
|
||||
loop: circular
|
||||
easing: cubicInOut
|
||||
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}
|
||||
|
||||
|
||||
@@ -330,13 +330,7 @@ void MapEditor::autosave() {
|
||||
room_data_.items[i].y = pos.y;
|
||||
}
|
||||
|
||||
// Sincronizar posiciones de plataformas desde los sprites vivos a room_data_
|
||||
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;
|
||||
}
|
||||
// Platforms are already synced via resetToInitialPosition during drag commit
|
||||
|
||||
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)
|
||||
for (auto type : {EntityType::ENEMY, EntityType::PLATFORM}) {
|
||||
// 3. Hit test on boundaries (enemies only)
|
||||
for (auto type : {EntityType::ENEMY}) {
|
||||
for (int i = 0; i < entityDataCount(type); ++i) {
|
||||
auto bd = entityBoundaries(type, i);
|
||||
constexpr auto SZ = static_cast<float>(Tile::SIZE);
|
||||
@@ -718,9 +712,16 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
break;
|
||||
case EntityType::PLATFORM:
|
||||
if (IDX >= 0 && IDX < static_cast<int>(room_data_.platforms.size())) {
|
||||
room_data_.platforms[IDX].x = drag_.snap_x;
|
||||
room_data_.platforms[IDX].y = drag_.snap_y;
|
||||
room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(room_data_.platforms[IDX]);
|
||||
auto& plat = room_data_.platforms[IDX];
|
||||
if (!plat.path.empty()) {
|
||||
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};
|
||||
return true;
|
||||
}
|
||||
@@ -741,15 +742,6 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
return true;
|
||||
}
|
||||
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:
|
||||
break;
|
||||
}
|
||||
@@ -766,15 +758,6 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
return true;
|
||||
}
|
||||
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:
|
||||
break;
|
||||
}
|
||||
@@ -807,8 +790,14 @@ void MapEditor::moveEntityVisual() {
|
||||
case EntityType::PLATFORM:
|
||||
if (drag_.index >= 0 && drag_.index < room_->getPlatformManager()->getCount()) {
|
||||
MovingPlatform::Data temp_data = room_data_.platforms[drag_.index];
|
||||
temp_data.x = drag_.snap_x;
|
||||
temp_data.y = drag_.snap_y;
|
||||
if (!temp_data.path.empty()) {
|
||||
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);
|
||||
}
|
||||
break;
|
||||
@@ -940,7 +929,7 @@ auto MapEditor::entityRect(EntityType type, int index) -> SDL_FRect {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -949,10 +938,6 @@ auto MapEditor::entityBoundaries(EntityType type, int index) const -> BoundaryDa
|
||||
const auto& e = room_data_.enemies[index];
|
||||
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 {};
|
||||
}
|
||||
}
|
||||
@@ -961,7 +946,10 @@ auto MapEditor::entityPosition(EntityType type, int index) const -> std::pair<fl
|
||||
switch (type) {
|
||||
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::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};
|
||||
}
|
||||
}
|
||||
@@ -999,13 +987,13 @@ void MapEditor::renderEntityBoundaries() {
|
||||
constexpr Uint8 DIM_BOUND2 = 13; // YELLOW
|
||||
constexpr Uint8 DIM_ROUTE = 14; // WHITE (gris medio)
|
||||
|
||||
for (auto type : {EntityType::ENEMY, EntityType::PLATFORM}) {
|
||||
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);
|
||||
|
||||
bool is_selected = selection_.is(EntityType::ENEMY) && selection_.index == i;
|
||||
|
||||
// Posiciones base (pueden estar siendo arrastradas)
|
||||
float init_x = pos_x;
|
||||
@@ -1016,7 +1004,7 @@ void MapEditor::renderEntityBoundaries() {
|
||||
auto b2_y = static_cast<float>(bd.y2);
|
||||
|
||||
// Si estamos arrastrando una boundary de esta entidad, usar la posición snapped
|
||||
if (drag_.entity_type == type && drag_.index == i) {
|
||||
if (drag_.entity_type == EntityType::ENEMY && drag_.index == i) {
|
||||
if (drag_.target == DragTarget::ENTITY_BOUND1) {
|
||||
b1_x = drag_.snap_x;
|
||||
b1_y = drag_.snap_y;
|
||||
@@ -1042,6 +1030,51 @@ void MapEditor::renderEntityBoundaries() {
|
||||
renderBoundaryMarker(b1_x, b1_y, color_b1);
|
||||
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())) {
|
||||
const auto& p = room_data_.platforms[selection_.index];
|
||||
std::string anim = p.animation_path;
|
||||
auto dot = anim.rfind('.');
|
||||
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
|
||||
|
||||
if (anim.size() > 5 && anim.substr(anim.size() - 5) == ".yaml") { anim = anim.substr(0, anim.size() - 5); }
|
||||
line2 = "platform " + std::to_string(selection_.index) + ": " + anim;
|
||||
line3 = "vx:" + std::to_string(static_cast<int>(p.vx)) +
|
||||
" vy:" + std::to_string(static_cast<int>(p.vy));
|
||||
line3 = "speed:" + std::to_string(static_cast<int>(p.speed)) + " " + (p.loop == LoopMode::CIRCULAR ? "circular" : "pingpong");
|
||||
if (p.easing != "linear") { line3 += " " + p.easing; }
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1197,7 +1228,7 @@ auto MapEditor::getSetCompletions() const -> std::vector<std::string> {
|
||||
switch (selection_.type) {
|
||||
case EntityType::ENEMY: return {"ANIMATION", "VX", "VY", "FLIP", "MIRROR"};
|
||||
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"};
|
||||
}
|
||||
}
|
||||
@@ -1856,50 +1887,56 @@ auto MapEditor::setPlatformProperty(const std::string& property, const std::stri
|
||||
return "animation: " + anim;
|
||||
}
|
||||
|
||||
if (property == "VX") {
|
||||
if (property == "SPEED") {
|
||||
try {
|
||||
platform.vx = std::stof(value);
|
||||
platform.speed = std::stof(value);
|
||||
} catch (...) { return "Invalid value: " + value; }
|
||||
platform.vy = 0.0F;
|
||||
|
||||
room_->getPlatformManager()->getPlatform(selection_.index)->resetToInitialPosition(platform);
|
||||
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") {
|
||||
try {
|
||||
platform.vy = std::stof(value);
|
||||
} catch (...) { return "Invalid value: " + value; }
|
||||
platform.vx = 0.0F;
|
||||
if (property == "LOOP") {
|
||||
std::string val = toLower(value);
|
||||
if (val == "circular") {
|
||||
platform.loop = LoopMode::CIRCULAR;
|
||||
} else {
|
||||
platform.loop = LoopMode::PINGPONG;
|
||||
val = "pingpong";
|
||||
}
|
||||
|
||||
room_->getPlatformManager()->getPlatform(selection_.index)->resetToInitialPosition(platform);
|
||||
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
|
||||
auto MapEditor::addPlatform() -> std::string {
|
||||
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;
|
||||
new_platform.animation_path = "bin.yaml";
|
||||
new_platform.x = CENTER_X;
|
||||
new_platform.y = CENTER_Y;
|
||||
new_platform.vx = 24.0F;
|
||||
new_platform.vy = 0.0F;
|
||||
new_platform.x1 = static_cast<int>(CENTER_X - ROUTE_HALF);
|
||||
new_platform.y1 = static_cast<int>(CENTER_Y);
|
||||
new_platform.x2 = static_cast<int>(CENTER_X + ROUTE_HALF);
|
||||
new_platform.y2 = static_cast<int>(CENTER_Y);
|
||||
new_platform.frame = 0;
|
||||
new_platform.speed = 24.0F;
|
||||
new_platform.frame = -1;
|
||||
constexpr float CENTER_X = PlayArea::CENTER_X;
|
||||
constexpr float CENTER_Y = PlayArea::CENTER_Y;
|
||||
constexpr float ROUTE_HALF = 2.0F * Tile::SIZE;
|
||||
new_platform.path = {
|
||||
{CENTER_X - ROUTE_HALF, CENTER_Y, 0.0F},
|
||||
{CENTER_X + ROUTE_HALF, CENTER_Y, 0.0F}
|
||||
};
|
||||
|
||||
room_data_.platforms.push_back(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"; }
|
||||
|
||||
MovingPlatform::Data copy = room_data_.platforms[selection_.index];
|
||||
copy.x += Tile::SIZE;
|
||||
copy.x1 += Tile::SIZE;
|
||||
copy.x2 += Tile::SIZE;
|
||||
for (auto& wp : copy.path) {
|
||||
wp.x += Tile::SIZE;
|
||||
}
|
||||
|
||||
room_data_.platforms.push_back(copy);
|
||||
room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(copy));
|
||||
|
||||
@@ -162,23 +162,18 @@ auto RoomSaver::buildYAML(const fkyaml::node& original_yaml, const Room::Data& r
|
||||
out << "platforms:\n";
|
||||
for (const auto& plat : room_data.platforms) {
|
||||
out << " - animation: " << plat.animation_path << "\n";
|
||||
|
||||
int pos_x = static_cast<int>(std::round(plat.x / Tile::SIZE));
|
||||
int pos_y = static_cast<int>(std::round(plat.y / Tile::SIZE));
|
||||
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";
|
||||
|
||||
out << " speed: " << plat.speed << "\n";
|
||||
out << " loop: " << (plat.loop == LoopMode::CIRCULAR ? "circular" : "pingpong") << "\n";
|
||||
if (plat.easing != "linear") { out << " easing: " << plat.easing << "\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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,181 @@
|
||||
#include "game/entities/moving_platform.hpp"
|
||||
|
||||
#include <cmath> // Para std::sqrt
|
||||
#include <cstdlib> // Para rand
|
||||
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||
#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
|
||||
MovingPlatform::MovingPlatform(const Data& data)
|
||||
: sprite_(std::make_shared<AnimatedSprite>(Resource::Cache::get()->getAnimationData(data.animation_path))),
|
||||
x1_(data.x1),
|
||||
x2_(data.x2),
|
||||
y1_(data.y1),
|
||||
y2_(data.y2) {
|
||||
sprite_->setPosX(data.x);
|
||||
sprite_->setPosY(data.y);
|
||||
sprite_->setVelX(data.vx);
|
||||
sprite_->setVelY(data.vy);
|
||||
path_(data.path),
|
||||
speed_(data.speed),
|
||||
loop_mode_(data.loop),
|
||||
easing_(resolveEasing(data.easing)) {
|
||||
// Colocar el sprite en el primer waypoint
|
||||
if (!path_.empty()) {
|
||||
sprite_->setPosX(path_[0].x);
|
||||
sprite_->setPosY(path_[0].y);
|
||||
}
|
||||
|
||||
collider_ = getRect();
|
||||
|
||||
// Coloca un frame al azar o el designado
|
||||
// Frame inicial
|
||||
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) {
|
||||
sprite_->animate(delta_time);
|
||||
|
||||
if (path_.size() < 2) {
|
||||
last_dx_ = 0.0F;
|
||||
last_dy_ = 0.0F;
|
||||
return;
|
||||
}
|
||||
|
||||
float old_x = sprite_->getPosX();
|
||||
float old_y = sprite_->getPosY();
|
||||
|
||||
sprite_->update(delta_time);
|
||||
checkPath();
|
||||
// Si estamos esperando en un waypoint
|
||||
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_dy_ = sprite_->getPosY() - old_y;
|
||||
|
||||
collider_ = getRect();
|
||||
}
|
||||
|
||||
@@ -50,17 +192,24 @@ void MovingPlatform::updateAnimation(float delta_time) {
|
||||
|
||||
// Resetea la plataforma a su posición inicial (para editor)
|
||||
void MovingPlatform::resetToInitialPosition(const Data& data) {
|
||||
sprite_->setPosX(data.x);
|
||||
sprite_->setPosY(data.y);
|
||||
sprite_->setVelX(data.vx);
|
||||
sprite_->setVelY(data.vy);
|
||||
path_ = data.path;
|
||||
speed_ = data.speed;
|
||||
loop_mode_ = data.loop;
|
||||
easing_ = resolveEasing(data.easing);
|
||||
|
||||
x1_ = data.x1;
|
||||
x2_ = data.x2;
|
||||
y1_ = data.y1;
|
||||
y2_ = data.y2;
|
||||
current_segment_ = 0;
|
||||
direction_ = 1;
|
||||
segment_progress_ = 0.0F;
|
||||
waiting_ = false;
|
||||
wait_timer_ = 0.0F;
|
||||
|
||||
if (!path_.empty()) {
|
||||
sprite_->setPosX(path_[0].x);
|
||||
sprite_->setPosY(path_[0].y);
|
||||
}
|
||||
|
||||
collider_ = getRect();
|
||||
recalcSegmentLength();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -73,24 +222,3 @@ auto MovingPlatform::getRect() -> SDL_FRect {
|
||||
auto MovingPlatform::getCollider() -> SDL_FRect& {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,32 @@
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
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 {
|
||||
public:
|
||||
struct Data {
|
||||
std::string animation_path; // Ruta al fichero con la animación
|
||||
float x{0.0F}; // Posición inicial en el eje X
|
||||
float y{0.0F}; // Posición inicial en el eje Y
|
||||
float vx{0.0F}; // Velocidad en el eje X
|
||||
float vy{0.0F}; // Velocidad en el eje Y
|
||||
int x1{0}; // Límite izquierdo de la ruta en el eje X
|
||||
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
|
||||
std::string animation_path;
|
||||
float speed{30.0F}; // px/s a lo largo del path
|
||||
LoopMode loop{LoopMode::PINGPONG};
|
||||
std::string easing{"linear"}; // Nombre del easing
|
||||
int frame{0}; // Frame inicial (-1 = random)
|
||||
std::vector<Waypoint> path; // Mínimo 2 puntos
|
||||
};
|
||||
|
||||
explicit MovingPlatform(const Data& data);
|
||||
@@ -35,19 +45,31 @@ class MovingPlatform {
|
||||
auto getRect() -> 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 getLastDY() const -> float { return last_dy_; }
|
||||
|
||||
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_;
|
||||
SDL_FRect collider_{};
|
||||
int x1_{0};
|
||||
int x2_{0};
|
||||
int y1_{0};
|
||||
int y2_{0};
|
||||
float last_dx_{0.0F}; // Desplazamiento horizontal del último frame
|
||||
float last_dy_{0.0F}; // Desplazamiento vertical del último frame
|
||||
float last_dx_{0.0F};
|
||||
float last_dy_{0.0F};
|
||||
|
||||
// Estado del path
|
||||
std::vector<Waypoint> path_;
|
||||
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?
|
||||
};
|
||||
|
||||
@@ -84,9 +84,10 @@ auto PlatformManager::checkPlayerOnPlatform(const SDL_FRect& player_collider, fl
|
||||
float player_feet = player_collider.y + player_collider.h;
|
||||
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;
|
||||
if (player_feet >= platform_top && player_feet <= platform_top + TOLERANCE) {
|
||||
if (player_feet >= platform_top - TOLERANCE && player_feet <= platform_top + TOLERANCE) {
|
||||
return platform.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
auto RoomLoader::parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data {
|
||||
MovingPlatform::Data platform;
|
||||
|
||||
// Animation path
|
||||
if (platform_node.contains("animation")) {
|
||||
platform.animation_path = platform_node["animation"].get_value<std::string>();
|
||||
}
|
||||
|
||||
// Position (in tiles, convert to pixels)
|
||||
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 (platform_node.contains("speed")) {
|
||||
platform.speed = platform_node["speed"].get_value<float>();
|
||||
}
|
||||
if (pos.contains("y")) {
|
||||
platform.y = pos["y"].get_value<float>() * Tile::SIZE;
|
||||
if (platform_node.contains("loop")) {
|
||||
auto loop_str = platform_node["loop"].get_value<std::string>();
|
||||
platform.loop = (loop_str == "circular") ? LoopMode::CIRCULAR : LoopMode::PINGPONG;
|
||||
}
|
||||
if (platform_node.contains("easing")) {
|
||||
platform.easing = platform_node["easing"].get_value<std::string>();
|
||||
}
|
||||
|
||||
// Velocity (already in pixels/second)
|
||||
if (platform_node.contains("velocity")) {
|
||||
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>();
|
||||
}
|
||||
}
|
||||
|
||||
// Boundaries (in tiles, convert to pixels)
|
||||
if (platform_node.contains("boundaries")) {
|
||||
parsePlatformBoundaries(platform_node["boundaries"], platform);
|
||||
}
|
||||
|
||||
// Optional frame
|
||||
platform.frame = platform_node.contains("frame")
|
||||
? platform_node["frame"].get_value_or<int>(-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;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,5 +137,4 @@ class RoomLoader {
|
||||
|
||||
static void parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose);
|
||||
static auto parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data;
|
||||
static void parsePlatformBoundaries(const fkyaml::node& bounds_node, MovingPlatform::Data& platform);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user