From 393ad991a7df87f0c8cadfaed3d8fdb7ce1e8719 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 15 Sep 2025 09:23:08 +0200 Subject: [PATCH] Implementar sistema delta time independiente del framerate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migrar de fisica frame-based (60 FPS fijo) a time-based - Convertir velocidades: x60 multiplicador (pixeles/frame → pixeles/segundo) - Convertir gravedad: x3600 multiplicador (pixeles/frame² → pixeles/segundo²) - Añadir calculateDeltaTime() con limitador de saltos grandes - Actualizar Ball::update() para recibir deltaTime como parametro - Implementar debug display con valores de fisica en tiempo real - Documentar proceso completo de migracion en README.md - Conseguir velocidad consistente entre diferentes refresh rates - V-Sync independiente: misma velocidad con V-Sync ON/OFF 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 146 ++++++++++++++++++++++++++++++++++++++++------- source/ball.cpp | 32 ++++++----- source/ball.h | 7 ++- source/defines.h | 2 +- source/main.cpp | 68 +++++++++++++++++----- 5 files changed, 204 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 3aaec99..101d20f 100644 --- a/README.md +++ b/README.md @@ -103,50 +103,154 @@ make ### Configuracion Actual - **Resolucion**: 320x240 pixeles (escalado x3 = 960x720) -- **FPS objetivo**: 60 FPS (16.67ms por frame) +- **Sistema de timing**: Delta time independiente del framerate - **Fisica**: Gravedad constante (0.2f), rebotes con perdida de energia - **Tamaño de pelota**: 10x10 pixeles - **V-Sync**: Activado por defecto, controlable dinamicamente ### Arquitectura del Codigo -1. **main.cpp**: Contiene el bucle principal con tres fases: - - `update()`: Actualiza la logica del juego + calculo FPS +1. **main.cpp**: Contiene el bucle principal con cuatro fases: + - `calculateDeltaTime()`: Calcula tiempo transcurrido entre frames + - `update()`: Actualiza la logica del juego usando delta time + calculo FPS - `checkEvents()`: Procesa eventos de entrada - `render()`: Renderiza la escena + overlays informativos -2. **Ball**: Maneja la fisica de cada pelota individual +2. **Ball**: Maneja la fisica de cada pelota individual con timing basado en delta time 3. **Sprite**: Sistema de renderizado de texturas con filtro nearest neighbor 4. **Texture**: Gestion de carga y renderizado de imagenes con filtro pixel-perfect -### Problema Actual: FPS Dependiente +## ✅ Migracion a Delta Time (COMPLETADO) -El sistema actual usa un **bucle acoplado** donde la logica y renderizado estan sincronizados a 60 FPS fijos: +### Sistema Anterior (Frame-Based) + +El sistema original estaba **acoplado a 60 FPS** con logica dependiente del framerate: ```cpp -// En update() - linea 236 -if (SDL_GetTicks() - ticks > DEMO_SPEED) { // DEMO_SPEED = 1000/60 - // Solo aqui se actualiza la fisica +// Sistema ANTIGUO en update() +if (SDL_GetTicks() - ticks > DEMO_SPEED) { // DEMO_SPEED = 1000/60 = 16.67ms + // Solo aqui se actualizaba la fisica cada 16.67ms + for (auto &ball : balls) { + ball->update(); // Sin parametros de tiempo + } + ticks = SDL_GetTicks(); } ``` -## 🚧 Mejoras Planificadas +**Problemas del sistema anterior:** +- Velocidad inconsistente entre diferentes refresh rates (60Hz vs 75Hz vs 144Hz) +- Logica de fisica acoplada a framerate fijo +- V-Sync ON/OFF cambiaba la velocidad del juego +- Rendimiento inconsistente en diferentes hardware -### Implementacion de Delta Time +### Sistema Actual (Delta Time) -**Objetivo**: Separar el bucle de proceso del de renderizado usando delta time para conseguir: +**Implementacion delta time** para simulacion independiente del framerate: -- **Simulacion independiente del framerate** -- **Mejor rendimiento** en escenarios de alta carga -- **Consistencia** en diferentes dispositivos -- **Preparacion para optimizaciones futuras** +```cpp +// Sistema NUEVO - Variables globales +Uint64 last_frame_time = 0; +float delta_time = 0.0f; -### Cambios Previstos +// Calculo de delta time +void calculateDeltaTime() { + Uint64 current_time = SDL_GetTicks(); + if (last_frame_time == 0) { + last_frame_time = current_time; + delta_time = 1.0f / 60.0f; // Primer frame a 60 FPS + return; + } -1. **Sistema de timing basado en delta time** -2. **Bucle de juego desacoplado** (logica vs renderizado) -3. **Interpolacion** para renderizado suave -4. **Optimizaciones de rendimiento** + delta_time = (current_time - last_frame_time) / 1000.0f; // Convertir a segundos + last_frame_time = current_time; + + // Limitar delta time para evitar saltos grandes + if (delta_time > 0.05f) { + delta_time = 1.0f / 60.0f; // Fallback a 60 FPS + } +} + +// Bucle principal actualizado +while (!should_exit) { + calculateDeltaTime(); // 1. Calcular tiempo transcurrido + update(); // 2. Actualizar logica (usa delta_time) + checkEvents(); // 3. Procesar entrada + render(); // 4. Renderizar escena +} +``` + +### Conversion de Fisica: Frame-Based → Time-Based + +#### 1. Conversion de Velocidades + +```cpp +// En Ball::Ball() constructor +// ANTES: velocidades en pixeles/frame +vx_ = vx; +vy_ = vy; + +// AHORA: convertir a pixeles/segundo (x60) +vx_ = vx * 60.0f; +vy_ = vy * 60.0f; +``` + +#### 2. Conversion de Gravedad (Aceleracion) + +```cpp +// En Ball::Ball() constructor +// ANTES: gravedad en pixeles/frame² +gravity_force_ = GRAVITY_FORCE; + +// AHORA: convertir a pixeles/segundo² (x60²) +gravity_force_ = GRAVITY_FORCE * 60.0f * 60.0f; // 3600x multiplicador +``` + +**¿Por que 60² para gravedad?** +- Velocidad: `pixeles/frame * frame/segundo = pixeles/segundo` → **x60** +- Aceleracion: `pixeles/frame² * frame²/segundo² = pixeles/segundo²` → **x60²** + +#### 3. Aplicacion de Delta Time en Fisica + +```cpp +// En Ball::update(float deltaTime) +// ANTES: incrementos fijos por frame +vy_ += gravity_force_; +pos_.x += vx_; +pos_.y += vy_; + +// AHORA: incrementos proporcionales al tiempo transcurrido +vy_ += gravity_force_ * deltaTime; // Aceleracion * tiempo +pos_.x += vx_ * deltaTime; // Velocidad * tiempo +pos_.y += vy_ * deltaTime; +``` + +### Valores de Debug: Antes vs Ahora + +| Parametro | Sistema Anterior | Sistema Delta Time | Razon | +|-----------|------------------|-------------------|-------| +| **Velocidad X/Y** | 1.0 - 3.0 pixeles/frame | 60.0 - 180.0 pixeles/segundo | x60 conversion | +| **Gravedad** | 0.2 pixeles/frame² | 720.0 pixeles/segundo² | x3600 conversion | +| **Debug Display** | No disponible | GRAV: 720.000000 VY: -140.5 FLOOR: NO | Valores en tiempo real | + +### Beneficios Conseguidos + +- ✅ **Velocidad consistente** entre 60Hz, 75Hz, 144Hz y otros refresh rates +- ✅ **V-Sync independiente**: misma velocidad con V-Sync ON/OFF +- ✅ **Fisica precisa**: gravedad y movimiento calculados correctamente +- ✅ **Escalabilidad**: preparado para futuras optimizaciones +- ✅ **Debug en tiempo real**: monitoreo de valores de fisica + +### Sistema de Debug Implementado + +```cpp +// Debug display para primera pelota +if (!balls.empty()) { + std::string debug_text = "GRAV: " + std::to_string(balls[0]->getGravityForce()) + + " VY: " + std::to_string(balls[0]->getVelocityY()) + + " FLOOR: " + (balls[0]->isOnFloor() ? "YES" : "NO"); + dbg_print(8, 24, debug_text.c_str(), 255, 0, 255); // Magenta +} +``` ## 🛠️ Desarrollo diff --git a/source/ball.cpp b/source/ball.cpp index 53072e4..0733db0 100644 --- a/source/ball.cpp +++ b/source/ball.cpp @@ -9,35 +9,37 @@ Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr te : sprite_(std::make_unique(texture)), pos_({x, 0.0f, BALL_SIZE, BALL_SIZE}) { - vx_ = vx; - vy_ = vy; + // Convertir velocidades de píxeles/frame a píxeles/segundo (multiplicar por 60) + vx_ = vx * 60.0f; + vy_ = vy * 60.0f; sprite_->setPos({pos_.x, pos_.y}); sprite_->setSize(BALL_SIZE, BALL_SIZE); sprite_->setClip({0, 0, BALL_SIZE, BALL_SIZE}); color_ = color; - gravity_force_ = GRAVITY_FORCE; + // Convertir gravedad de píxeles/frame² a píxeles/segundo² (multiplicar por 60²) + gravity_force_ = GRAVITY_FORCE * 60.0f * 60.0f; on_floor_ = false; stopped_ = false; loss_ = ((rand() % 30) * 0.01f) + 0.6f; } // Actualiza la lógica de la clase -void Ball::update() +void Ball::update(float deltaTime) { if (stopped_) { return; } - // Aplica la gravedad a la velocidad - if (!on_floor_ && (pos_.y - SCREEN_HEIGHT) < BALL_SIZE * 2) + // Aplica la gravedad a la velocidad (píxeles/segundo²) + if (!on_floor_) { - vy_ += gravity_force_; + vy_ += gravity_force_ * deltaTime; } - // Actualiza la posición en función de la velocidad - pos_.x += vx_; - pos_.y += vy_; + // Actualiza la posición en función de la velocidad (píxeles/segundo) + pos_.x += vx_ * deltaTime; + pos_.y += vy_ * deltaTime; // Comprueba las colisiones con el lateral izquierdo if (pos_.x < 0) @@ -94,20 +96,20 @@ void Ball::render() sprite_->render(); } -// Modifica la velocidad +// Modifica la velocidad (convierte de frame-based a time-based) void Ball::modVel(float vx, float vy) { if (stopped_) { - vx_ = vx_ + vx; + vx_ = vx_ + (vx * 60.0f); // Convertir a píxeles/segundo } - vy_ = vy_ + vy; + vy_ = vy_ + (vy * 60.0f); // Convertir a píxeles/segundo on_floor_ = false; stopped_ = false; } -// Cambia la gravedad +// Cambia la gravedad (usa la versión convertida) void Ball::switchGravity() { - gravity_force_ = gravity_force_ == 0.0f ? GRAVITY_FORCE : 0.0f; + gravity_force_ = gravity_force_ == 0.0f ? (GRAVITY_FORCE * 60.0f * 60.0f) : 0.0f; } \ No newline at end of file diff --git a/source/ball.h b/source/ball.h index 498560a..8d6dbcc 100644 --- a/source/ball.h +++ b/source/ball.h @@ -26,7 +26,7 @@ public: ~Ball() = default; // Actualiza la lógica de la clase - void update(); + void update(float deltaTime); // Pinta la clase void render(); @@ -36,4 +36,9 @@ public: // Cambia la gravedad void switchGravity(); + + // Getters para debug + float getVelocityY() const { return vy_; } + float getGravityForce() const { return gravity_force_; } + bool isOnFloor() const { return on_floor_; } }; \ No newline at end of file diff --git a/source/defines.h b/source/defines.h index 0e49d37..b113d73 100644 --- a/source/defines.h +++ b/source/defines.h @@ -8,7 +8,7 @@ constexpr int WINDOW_SIZE = 3; constexpr int BALL_SIZE = 10; constexpr float GRAVITY_FORCE = 0.2f; -constexpr Uint64 DEMO_SPEED = 1000 / 60; +// DEMO_SPEED eliminado - ya no se usa con delta time constexpr Uint64 TEXT_DURATION = 2000; struct Color diff --git a/source/main.cpp b/source/main.cpp index 7f82974..58823d0 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -26,7 +26,7 @@ std::vector> balls; std::array test = {1, 10, 100, 500, 1000, 10000, 50000, 100000}; bool should_exit = false; // Controla si la aplicación debe cerrarse -Uint64 ticks = 0; // Tiempo en milisegundos para controlar la actualización +// ticks eliminado - reemplazado por delta time system int scenario = 0; // Escenario actual basado en el número de bolas std::string text; // Texto a mostrar en pantalla int text_pos = 0; // Posición del texto en la pantalla @@ -43,6 +43,10 @@ std::string fps_text = "FPS: 0"; // Texto del contador de FPS bool vsync_enabled = true; // Estado inicial del V-Sync (activado por defecto) std::string vsync_text = "VSYNC: ON"; // Texto del estado V-Sync +// Variables para Delta Time +Uint64 last_frame_time = 0; // Tiempo del último frame en milisegundos +float delta_time = 0.0f; // Tiempo transcurrido desde el último frame en segundos + // Establece el texto en pantalla mostrando el número de bolas actuales void setText() { @@ -101,6 +105,30 @@ void toggleVSync() SDL_SetRenderVSync(renderer, vsync_enabled ? 1 : 0); } +// Calcula el delta time entre frames +void calculateDeltaTime() +{ + Uint64 current_time = SDL_GetTicks(); + + // En el primer frame, inicializar el tiempo anterior + if (last_frame_time == 0) + { + last_frame_time = current_time; + delta_time = 1.0f / 60.0f; // Asumir 60 FPS para el primer frame + return; + } + + // Calcular delta time en segundos + delta_time = (current_time - last_frame_time) / 1000.0f; + last_frame_time = current_time; + + // Limitar delta time para evitar saltos grandes (pausa larga, depuración, etc.) + if (delta_time > 0.05f) // Máximo 50ms (20 FPS mínimo) + { + delta_time = 1.0f / 60.0f; // Usar 60 FPS como fallback + } +} + // Inicializa SDL y configura los componentes principales bool init() { @@ -146,7 +174,7 @@ bool init() // Inicializar otros componentes texture = std::make_shared(renderer, "resources/ball.png"); - ticks = SDL_GetTicks(); + // ticks eliminado - delta time system se inicializa automáticamente srand(static_cast(time(nullptr))); dbg_init(renderer); initBalls(scenario); @@ -254,19 +282,16 @@ void update() fps_text = "FPS: " + std::to_string(fps_current); } - if (SDL_GetTicks() - ticks > DEMO_SPEED) + // ¡DELTA TIME! Actualizar física siempre, usando tiempo transcurrido + for (auto &ball : balls) { - ticks = SDL_GetTicks(); + ball->update(delta_time); // Pasar delta time a cada pelota + } - for (auto &ball : balls) - { - ball->update(); - } - - if (show_text) - { - show_text = !(SDL_GetTicks() - text_init_time > TEXT_DURATION); - } + // Actualizar texto (sin cambios en la lógica) + if (show_text) + { + show_text = !(SDL_GetTicks() - text_init_time > TEXT_DURATION); } } @@ -294,6 +319,15 @@ void render() // Mostrar estado V-Sync en esquina superior izquierda dbg_print(8, 8, vsync_text.c_str(), 0, 255, 255); // Cian para distinguir + // Debug: Mostrar valores de la primera pelota (si existe) + if (!balls.empty()) + { + std::string debug_text = "GRAV: " + std::to_string(balls[0]->getGravityForce()) + + " VY: " + std::to_string(balls[0]->getVelocityY()) + + " FLOOR: " + (balls[0]->isOnFloor() ? "YES" : "NO"); + dbg_print(8, 24, debug_text.c_str(), 255, 0, 255); // Magenta para debug + } + SDL_RenderPresent(renderer); } @@ -308,8 +342,16 @@ int main(int argc, char *args[]) while (!should_exit) { + // 1. Calcular delta time antes de actualizar la lógica + calculateDeltaTime(); + + // 2. Actualizar lógica del juego con delta time update(); + + // 3. Procesar eventos de entrada checkEvents(); + + // 4. Renderizar la escena render(); }