From ed0f6784d96c589e2b842330525a92a35553db4e Mon Sep 17 00:00:00 2001
From: David Hermann <redeagle.private@gmail.com>
Date: Wed, 5 Feb 2025 20:37:31 +0100
Subject: [PATCH] Implementing animated movement of units (tile-based) -
 Creating `Box2dHelper.hpp` with inline converting helper functions of Box2D
 coordinates, tiles and pixels - Outsourcing  `PIXELS_PER_METER` into
 `Box2dHelper.hpp` - Setting `PIXELS_PER_METER` from 32 to 16 (tile length = 1
 meter)

---
 src/game/Box2dHelper.hpp |  42 ++++++++++++++++
 src/game/Bullet.cpp      |   1 +
 src/game/Bullet.hpp      |   4 --
 src/game/Level.cpp       |   5 +-
 src/game/Unit.cpp        | 103 +++++++++++++++++++++++++++++----------
 src/game/Unit.hpp        |  14 ++++++
 6 files changed, 136 insertions(+), 33 deletions(-)
 create mode 100644 src/game/Box2dHelper.hpp

diff --git a/src/game/Box2dHelper.hpp b/src/game/Box2dHelper.hpp
new file mode 100644
index 0000000..cbec14e
--- /dev/null
+++ b/src/game/Box2dHelper.hpp
@@ -0,0 +1,42 @@
+namespace advanced_wars
+{
+
+constexpr float PIXELS_PER_METER = 16.0f;
+
+/// Tile → Pixel
+inline int tileToPixel(int tile)
+{
+    return tile * 16;
+}
+
+/// Pixel → Tile
+inline int pixelToTile(int pixel)
+{
+    return pixel / 16;
+}
+
+/// Box2D (Meter) → Pixel
+inline float worldToPixel(float world)
+{
+    return world * PIXELS_PER_METER;
+}
+
+/// Pixel → Box2D (Meter)
+inline float pixelToWorld(float pixel)
+{
+    return pixel / PIXELS_PER_METER;
+}
+
+/// Tile → Box2D (Meter) (Oberer linker Punkt des Tiles)
+inline float tileToWorld(int tile)
+{
+    return pixelToWorld(tileToPixel(tile));
+}
+
+/// Box2D (Meter) → Tile (Berechnung über Pixel)
+inline int worldToTile(float world)
+{
+    return pixelToTile(worldToPixel(world));
+}
+
+}
\ No newline at end of file
diff --git a/src/game/Bullet.cpp b/src/game/Bullet.cpp
index fbb35d9..974b834 100644
--- a/src/game/Bullet.cpp
+++ b/src/game/Bullet.cpp
@@ -1,4 +1,5 @@
 #include "Bullet.hpp"
+#include "Box2dHelper.hpp"
 #include "Engine.hpp"
 #include "UnitContactListener.hpp"
 #include <iostream>
diff --git a/src/game/Bullet.hpp b/src/game/Bullet.hpp
index 6886ea2..60a8faa 100644
--- a/src/game/Bullet.hpp
+++ b/src/game/Bullet.hpp
@@ -8,10 +8,6 @@
 namespace advanced_wars
 {
 
-// Wir definieren einen Umrechnungsfaktor, um zwischen Pixeln und Box2D-Metern umzurechnen.
-// Passe diesen Wert an deine Spielwelt an. Hier gehen wir von 32 Pixel pro Meter aus.
-constexpr float PIXELS_PER_METER = 32.0f;
-
 class Bullet
 {
     public:
diff --git a/src/game/Level.cpp b/src/game/Level.cpp
index 585d558..b0883c0 100644
--- a/src/game/Level.cpp
+++ b/src/game/Level.cpp
@@ -1,4 +1,5 @@
 #include "Level.hpp"
+#include "Box2dHelper.hpp"
 #include "Building.hpp"
 #include "Effect.hpp"
 #include "Engine.hpp"
@@ -241,8 +242,7 @@ void Level::handleEvent(Engine& engine, SDL_Event& event)
                 }
                 else
                 {
-
-                    m_units.at(m_selectedUnit)->updatePosition(tileX, tileY);
+                    m_units.at(m_selectedUnit)->moveToTile(tileX, tileY);
                 }
             }
             else
@@ -309,6 +309,7 @@ void Level::render(Engine& engine)
     // Units
     for (auto& [id, unit] : m_units)
     {
+        unit->update();
         unit->render(engine, RENDERING_SCALE);
     }
 
diff --git a/src/game/Unit.cpp b/src/game/Unit.cpp
index 479ba75..8ca69fd 100644
--- a/src/game/Unit.cpp
+++ b/src/game/Unit.cpp
@@ -1,4 +1,5 @@
 #include "Unit.hpp"
+#include "Box2dHelper.hpp"
 #include "Bullet.hpp"
 #include "UnitContactListener.hpp"
 #include <iostream>
@@ -51,6 +52,8 @@ void Unit::setWorld(b2World* world)
 
 void Unit::render(Engine& engine, int scale)
 {
+    b2Vec2 pos = m_body->GetPosition();
+
     Spritesheet* spritesheet = engine.getSpritesheet();
 
     int step = engine.getStage() % spritesheet->getUnitTextures()
@@ -58,56 +61,44 @@ void Unit::render(Engine& engine, int scale)
                                        .at(static_cast<int>(m_id))
                                        .at(static_cast<int>(m_state))
                                        .second;
-
+    SDL_Rect src;
+    SDL_Rect dst;
     if (m_state == UnitState::IDLE || m_state == UnitState::UNAVAILABLE)
     {
 
-        SDL_Rect src;
         src.x = step * spritesheet->getUnitWidth();
         src.y = 0;
         src.w = spritesheet->getUnitWidth();
         src.h = spritesheet->getUnitHeight();
 
-        SDL_Rect dst;
-        dst.x = m_x * spritesheet->getUnitWidth() * scale;
-        dst.y = m_y * spritesheet->getUnitHeight() * scale;
+        dst.x = worldToTile(pos.x) * spritesheet->getUnitWidth() * scale;
+        dst.y = worldToTile(pos.y) * spritesheet->getUnitHeight() * scale;
         dst.w = spritesheet->getUnitWidth() * scale;
         dst.h = spritesheet->getUnitHeight() * scale;
-
-        SDL_RenderCopyEx(
-            engine.renderer(),
-            spritesheet->getUnitTextures()
-                .at(static_cast<int>(m_faction))
-                .at(static_cast<int>(m_id))
-                .at(static_cast<int>(m_state))
-                .first,
-            &src, &dst, 0, NULL, SDL_FLIP_NONE);
     }
     else
     {
         // The moving states have a resolution of 24x24 instead of 16x16 and need to
         // be handled separately
-        SDL_Rect src;
         src.x = step * spritesheet->getUnitMovingWidth();
         src.y = 0;
         src.w = spritesheet->getUnitMovingWidth();
         src.h = spritesheet->getUnitMovingHeight();
 
-        SDL_Rect dst;
-        dst.x = ((m_x * spritesheet->getUnitWidth()) - 4) * scale;
-        dst.y = ((m_y * spritesheet->getUnitHeight()) - 4) * scale;
+        dst.x = ((worldToTile(pos.x) * spritesheet->getUnitWidth()) - 4) * scale;
+        dst.y = ((worldToTile(pos.y) * spritesheet->getUnitHeight()) - 4) * scale;
         dst.w = spritesheet->getUnitMovingWidth() * scale;
         dst.h = spritesheet->getUnitMovingHeight() * scale;
-
-        SDL_RenderCopyEx(
-            engine.renderer(),
-            spritesheet->getUnitTextures()
-                .at(static_cast<int>(m_faction))
-                .at(static_cast<int>(m_id))
-                .at(static_cast<int>(m_state))
-                .first,
-            &src, &dst, 0, NULL, SDL_FLIP_NONE);
     }
+
+    SDL_RenderCopyEx(
+        engine.renderer(),
+        spritesheet->getUnitTextures()
+            .at(static_cast<int>(m_faction))
+            .at(static_cast<int>(m_id))
+            .at(static_cast<int>(m_state))
+            .first,
+        &src, &dst, 0, NULL, SDL_FLIP_NONE);
 }
 
 void Unit::attack(Unit& enemy)
@@ -287,4 +278,62 @@ int Unit::getMapId()
     return this->m_mapId;
 }
 
+void Unit::moveToTile(int targetX, int targetY)
+{
+    // Speichere die Ziel-Tile-Koordinaten
+    m_targetTileX = targetX;
+    m_targetTileY = targetY;
+
+    // Zielposition in Meter umrechnen (Mitte des Tiles)
+    float worldTargetX = (targetX * 16 + 8) / PIXELS_PER_METER;
+    float worldTargetY = (targetY * 16 + 8) / PIXELS_PER_METER;
+
+    // Aktuelle Position abrufen
+    b2Vec2 currentPos = m_body->GetPosition();
+
+    // Differenz berechnen
+    float deltaX = worldTargetX - currentPos.x;
+    float deltaY = worldTargetY - currentPos.y;
+
+    // Distanz berechnen
+    float distance = std::sqrt(deltaX * deltaX + deltaY * deltaY);
+    if (distance < 0.1f)
+    {
+        return; // Falls schon fast da, nichts tun
+    }
+
+    // Normierte Richtung berechnen
+    float directionX = deltaX / distance;
+    float directionY = deltaY / distance;
+
+    // Geschwindigkeit setzen
+    float speed = 2.0f; // Tiles pro Sekunde
+    m_body->SetLinearVelocity(b2Vec2(directionX * speed, directionY * speed));
+
+    // State setzen
+    calcState(targetX, targetY);
+}
+
+void Unit::update()
+{
+    b2Vec2 pos = m_body->GetPosition();
+
+    // Berechne aktuelle Tile-Position
+    int currentTileX = static_cast<int>((pos.x * PIXELS_PER_METER) / 16);
+    int currentTileY = static_cast<int>((pos.y * PIXELS_PER_METER) / 16);
+    /*  if (getMapId() == 66)
+     {
+         std::cout << "Current Tile: " << currentTileX << ", " << currentTileY << std::endl;
+     } */
+    // Prüfe, ob wir am Ziel sind
+    if (currentTileX == m_targetTileX && currentTileY == m_targetTileY)
+    {
+        m_body->SetLinearVelocity(b2Vec2(0, 0)); // Bewegung stoppen
+        m_x = m_targetTileX; // Stelle sicher, dass die Unit auf dem richtigen Tile registriert
+                             // ist
+        m_y = m_targetTileY;
+        m_state = UnitState::IDLE;
+    }
+}
+
 } // namespace advanced_wars
\ No newline at end of file
diff --git a/src/game/Unit.hpp b/src/game/Unit.hpp
index ff26387..7450a81 100644
--- a/src/game/Unit.hpp
+++ b/src/game/Unit.hpp
@@ -201,6 +201,17 @@ class Unit
          */
         int getMapId();
 
+        /**
+         * @brief Aktualisiert den Zustand der Unit.
+         *
+         * Diese Methode prüft, ob sich die Unit gerade bewegt und ob sie nahe genug am Ziel ist.
+         * Ist das der Fall, wird die Bewegung gestoppt, und die Tile-Koordinaten werden
+         * aktualisiert.
+         */
+        void update();
+
+        void moveToTile(int targetX, int targetY);
+
     private:
         UnitFaction m_faction;
         UnitId      m_id;
@@ -209,6 +220,9 @@ class Unit
         b2World*    m_world = nullptr;
         int         m_mapId = -1;
 
+        int m_targetTileX;
+        int m_targetTileY;
+
         int m_maxHealth; // max_health required for damage_scaling
         int m_range;
         int m_fuel;
-- 
GitLab