Browse Source

Archer click-to-move prototype: spawn single archer, render ground plane, QML click wiring, offscreen/xvfb run targets, per-object color in renderer

Adam Djellouli 2 months ago
parent
commit
b0e5e21a47
5 changed files with 136 additions and 30 deletions
  1. 17 1
      Makefile
  2. 78 26
      main.cpp
  3. 33 1
      render/gl/renderer.cpp
  4. 2 0
      render/gl/renderer.h
  5. 6 2
      ui/qml/GameView.qml

+ 17 - 1
Makefile

@@ -80,7 +80,23 @@ all: build
 .PHONY: run
 .PHONY: run
 run: build
 run: build
 	@echo "$(BOLD)$(BLUE)Running Standard of Iron...$(RESET)"
 	@echo "$(BOLD)$(BLUE)Running Standard of Iron...$(RESET)"
-	@cd $(BUILD_DIR) && ./$(BINARY_NAME)
+	@cd $(BUILD_DIR) && \
+	if [ -n "$$DISPLAY" ]; then \
+	  ./$(BINARY_NAME); \
+	else \
+	  echo "$(YELLOW)No DISPLAY detected; running with QT_QPA_PLATFORM=offscreen$(RESET)"; \
+	  QT_QPA_PLATFORM=offscreen ./$(BINARY_NAME); \
+	fi
+
+# Run with xvfb for headless environments (software rasterization)
+.PHONY: run-headless
+run-headless: build
+	@echo "$(BOLD)$(BLUE)Running Standard of Iron under xvfb...$(RESET)"
+	@if ! command -v xvfb-run >/dev/null 2>&1; then \
+	  echo "$(YELLOW)xvfb-run not found. Installing...$(RESET)"; \
+	  sudo apt-get update -y >/dev/null 2>&1 && sudo apt-get install -y xvfb >/dev/null 2>&1; \
+	fi
+	@cd $(BUILD_DIR) && xvfb-run -s "-screen 0 1280x720x24" ./$(BINARY_NAME)
 
 
 # Run the map editor
 # Run the map editor
 .PHONY: editor
 .PHONY: editor

+ 78 - 26
main.cpp

@@ -5,6 +5,7 @@
 #include <QDebug>
 #include <QDebug>
 #include <QDir>
 #include <QDir>
 #include <QQuickWindow>
 #include <QQuickWindow>
+#include <QQmlContext>
 
 
 #include "engine/core/world.h"
 #include "engine/core/world.h"
 #include "engine/core/component.h"
 #include "engine/core/component.h"
@@ -34,6 +35,22 @@ public:
         
         
         setupTestScene();
         setupTestScene();
     }
     }
+    Q_INVOKABLE void onMapClicked(qreal sx, qreal sy) {
+        if (!m_window) return;
+        ensureInitialized();
+        // Convert screen coords to world point on ground (y = 0)
+        QVector3D hit;
+        if (!screenToGround(QPointF(sx, sy), hit)) return;
+        // Move our unit to that point
+        if (auto* entity = m_world->getEntity(m_playerUnitId)) {
+            if (auto* move = entity->getComponent<Engine::Core::MovementComponent>()) {
+                move->targetX = hit.x();
+                move->targetY = hit.z();
+                move->hasTarget = true;
+            }
+        }
+    }
+    void setWindow(QQuickWindow* w) { m_window = w; }
     
     
     void initialize() {
     void initialize() {
         if (!m_renderer->initialize()) {
         if (!m_renderer->initialize()) {
@@ -73,38 +90,70 @@ public:
 
 
 private:
 private:
     void setupTestScene() {
     void setupTestScene() {
-        // Create some test units
-        for (int i = 0; i < 5; ++i) {
-            auto entity = m_world->createEntity();
-            
-            // Add transform component
-            auto transform = entity->addComponent<Engine::Core::TransformComponent>();
-            transform->position.x = i * 2.0f;
-            transform->position.y = 0.0f;
-            transform->position.z = 0.0f;
-            
-            // Add renderable component
-            auto renderable = entity->addComponent<Engine::Core::RenderableComponent>("", "");
-            renderable->visible = true;
-            
-            // Add unit component
-            auto unit = entity->addComponent<Engine::Core::UnitComponent>();
-            unit->unitType = "warrior";
-            unit->health = 100;
-            unit->maxHealth = 100;
-            unit->speed = 2.0f;
-            
-            // Add movement component
-            entity->addComponent<Engine::Core::MovementComponent>();
-        }
-        
-        qDebug() << "Test scene created with 5 units";
+        // Create a single archer unit at origin
+        auto entity = m_world->createEntity();
+        m_playerUnitId = entity->getId();
+
+        auto transform = entity->addComponent<Engine::Core::TransformComponent>();
+        transform->position = {0.0f, 0.0f, 0.0f};
+        transform->scale = {0.5f, 0.5f, 0.5f}; // smaller quad as a unit
+
+    auto renderable = entity->addComponent<Engine::Core::RenderableComponent>("", "");
+        renderable->visible = true;
+
+        auto unit = entity->addComponent<Engine::Core::UnitComponent>();
+        unit->unitType = "archer";
+        unit->health = 80;
+        unit->maxHealth = 80;
+        unit->speed = 3.0f;
+
+        entity->addComponent<Engine::Core::MovementComponent>();
+
+    // Rotate unit quad to lie on the ground (XZ plane)
+    transform->rotation.x = -90.0f;
+
+        qDebug() << "Test scene created with 1 archer (entity" << m_playerUnitId << ")";
+    }
+
+    bool screenToGround(const QPointF& screenPt, QVector3D& outWorld) {
+        if (!m_window || !m_camera) return false;
+        // Viewport
+        float w = static_cast<float>(m_window->width());
+        float h = static_cast<float>(m_window->height());
+        if (w <= 0 || h <= 0) return false;
+
+        // Convert to Normalized Device Coordinates
+        float x = (2.0f * static_cast<float>(screenPt.x()) / w) - 1.0f;
+        float y = 1.0f - (2.0f * static_cast<float>(screenPt.y()) / h);
+
+    bool ok = false;
+    QMatrix4x4 invVP = (m_camera->getProjectionMatrix() * m_camera->getViewMatrix()).inverted(&ok);
+    if (!ok) return false;
+
+        // Ray from near to far in world space
+        QVector4D nearClip(x, y, 0.0f, 1.0f);
+        QVector4D farClip(x, y, 1.0f, 1.0f);
+        QVector4D nearWorld4 = invVP * nearClip;
+        QVector4D farWorld4 = invVP * farClip;
+        if (nearWorld4.w() == 0.0f || farWorld4.w() == 0.0f) return false;
+        QVector3D rayOrigin = (nearWorld4 / nearWorld4.w()).toVector3D();
+        QVector3D rayEnd = (farWorld4 / farWorld4.w()).toVector3D();
+        QVector3D rayDir = (rayEnd - rayOrigin).normalized();
+
+        // Intersect with plane y=0
+        if (qFuzzyIsNull(rayDir.y())) return false; // parallel
+        float t = -rayOrigin.y() / rayDir.y();
+        if (t < 0.0f) return false; // behind camera
+        outWorld = rayOrigin + rayDir * t;
+        return true;
     }
     }
 
 
     std::unique_ptr<Engine::Core::World> m_world;
     std::unique_ptr<Engine::Core::World> m_world;
     std::unique_ptr<Render::GL::Renderer> m_renderer;
     std::unique_ptr<Render::GL::Renderer> m_renderer;
     std::unique_ptr<Render::GL::Camera> m_camera;
     std::unique_ptr<Render::GL::Camera> m_camera;
     std::unique_ptr<Game::Systems::SelectionSystem> m_selectionSystem;
     std::unique_ptr<Game::Systems::SelectionSystem> m_selectionSystem;
+    QQuickWindow* m_window = nullptr;
+    Engine::Core::EntityID m_playerUnitId = 0;
     bool m_initialized = false;
     bool m_initialized = false;
 };
 };
 
 
@@ -147,8 +196,11 @@ int main(int argc, char *argv[])
     }
     }
 
 
     auto gameEngine = new GameEngine();
     auto gameEngine = new GameEngine();
+    // Expose to QML
+    engine.rootContext()->setContextProperty("game", gameEngine);
 
 
     if (window) {
     if (window) {
+        gameEngine->setWindow(window);
         // Per-frame update/render loop (context is current here)
         // Per-frame update/render loop (context is current here)
         QObject::connect(window, &QQuickWindow::beforeRendering, gameEngine, [gameEngine]() {
         QObject::connect(window, &QQuickWindow::beforeRendering, gameEngine, [gameEngine]() {
             gameEngine->ensureInitialized();
             gameEngine->ensureInitialized();

+ 33 - 1
render/gl/renderer.cpp

@@ -97,6 +97,28 @@ void Renderer::drawMesh(Mesh* mesh, const QMatrix4x4& modelMatrix, Texture* text
     m_basicShader->release();
     m_basicShader->release();
 }
 }
 
 
+void Renderer::drawMeshColored(Mesh* mesh, const QMatrix4x4& modelMatrix, const QVector3D& color, Texture* texture) {
+    if (!mesh || !m_basicShader || !m_camera) {
+        return;
+    }
+    m_basicShader->use();
+    m_basicShader->setUniform("u_model", modelMatrix);
+    m_basicShader->setUniform("u_view", m_camera->getViewMatrix());
+    m_basicShader->setUniform("u_projection", m_camera->getProjectionMatrix());
+    if (texture) {
+        texture->bind(0);
+        m_basicShader->setUniform("u_texture", 0);
+        m_basicShader->setUniform("u_useTexture", true);
+    } else {
+        m_whiteTexture->bind(0);
+        m_basicShader->setUniform("u_texture", 0);
+        m_basicShader->setUniform("u_useTexture", false);
+    }
+    m_basicShader->setUniform("u_color", color);
+    mesh->draw();
+    m_basicShader->release();
+}
+
 void Renderer::drawLine(const QVector3D& start, const QVector3D& end, const QVector3D& color) {
 void Renderer::drawLine(const QVector3D& start, const QVector3D& end, const QVector3D& color) {
     // Simple line drawing implementation
     // Simple line drawing implementation
     // In a full implementation, you'd want a proper line renderer
     // In a full implementation, you'd want a proper line renderer
@@ -114,7 +136,7 @@ void Renderer::flushBatch() {
     sortRenderQueue();
     sortRenderQueue();
     
     
     for (const auto& command : m_renderQueue) {
     for (const auto& command : m_renderQueue) {
-        drawMesh(command.mesh, command.modelMatrix, command.texture);
+        drawMeshColored(command.mesh, command.modelMatrix, command.color, command.texture);
     }
     }
     
     
     m_renderQueue.clear();
     m_renderQueue.clear();
@@ -124,6 +146,13 @@ void Renderer::renderWorld(Engine::Core::World* world) {
     if (!world) {
     if (!world) {
         return;
         return;
     }
     }
+    // Draw ground plane first
+    if (m_groundMesh) {
+        QMatrix4x4 groundModel;
+        groundModel.translate(0.0f, 0.0f, 0.0f);
+        groundModel.scale(50.0f, 1.0f, 50.0f);
+        drawMeshColored(m_groundMesh.get(), groundModel, QVector3D(0.15f, 0.2f, 0.15f));
+    }
     
     
     // Get all entities with both transform and renderable components
     // Get all entities with both transform and renderable components
     auto renderableEntities = world->getEntitiesWith<Engine::Core::RenderableComponent>();
     auto renderableEntities = world->getEntitiesWith<Engine::Core::RenderableComponent>();
@@ -151,6 +180,8 @@ void Renderer::renderWorld(Engine::Core::World* world) {
         command.mesh = m_quadMesh.get(); // Default mesh for now
         command.mesh = m_quadMesh.get(); // Default mesh for now
         command.texture = m_whiteTexture.get();
         command.texture = m_whiteTexture.get();
         
         
+        // Slight color tint for units
+        command.color = QVector3D(0.8f, 0.9f, 1.0f);
         submitRenderCommand(command);
         submitRenderCommand(command);
     }
     }
 }
 }
@@ -222,6 +253,7 @@ bool Renderer::loadShaders() {
 
 
 void Renderer::createDefaultResources() {
 void Renderer::createDefaultResources() {
     m_quadMesh = std::unique_ptr<Mesh>(createQuadMesh());
     m_quadMesh = std::unique_ptr<Mesh>(createQuadMesh());
+    m_groundMesh = std::unique_ptr<Mesh>(createPlaneMesh(1.0f, 1.0f, 1));
 
 
     m_whiteTexture = std::make_unique<Texture>();
     m_whiteTexture = std::make_unique<Texture>();
     m_whiteTexture->createEmpty(1, 1, Texture::Format::RGBA);
     m_whiteTexture->createEmpty(1, 1, Texture::Format::RGBA);

+ 2 - 0
render/gl/renderer.h

@@ -38,6 +38,7 @@ public:
     
     
     // Immediate mode rendering
     // Immediate mode rendering
     void drawMesh(Mesh* mesh, const QMatrix4x4& modelMatrix, Texture* texture = nullptr);
     void drawMesh(Mesh* mesh, const QMatrix4x4& modelMatrix, Texture* texture = nullptr);
+    void drawMeshColored(Mesh* mesh, const QMatrix4x4& modelMatrix, const QVector3D& color, Texture* texture = nullptr);
     void drawLine(const QVector3D& start, const QVector3D& end, const QVector3D& color);
     void drawLine(const QVector3D& start, const QVector3D& end, const QVector3D& color);
     
     
     // Batch rendering
     // Batch rendering
@@ -57,6 +58,7 @@ private:
     
     
     // Default resources
     // Default resources
     std::unique_ptr<Mesh> m_quadMesh;
     std::unique_ptr<Mesh> m_quadMesh;
+    std::unique_ptr<Mesh> m_groundMesh;
     std::unique_ptr<Texture> m_whiteTexture;
     std::unique_ptr<Texture> m_whiteTexture;
     
     
     bool loadShaders();
     bool loadShaders();

+ 6 - 2
ui/qml/GameView.qml

@@ -3,6 +3,7 @@ import QtQuick.Controls 2.15
 
 
 Item {
 Item {
     id: gameView
     id: gameView
+    objectName: "GameView"
     
     
     property bool isPaused: false
     property bool isPaused: false
     property real gameSpeed: 1.0
     property real gameSpeed: 1.0
@@ -25,10 +26,10 @@ Item {
     }
     }
     
     
     // OpenGL rendering area
     // OpenGL rendering area
-    Rectangle {
+        Rectangle {
         id: renderArea
         id: renderArea
         anchors.fill: parent
         anchors.fill: parent
-        color: "#2c3e50"
+            color: "transparent"
         
         
         // This will be replaced by actual OpenGL rendering
         // This will be replaced by actual OpenGL rendering
         Text {
         Text {
@@ -135,6 +136,9 @@ Item {
                     } else {
                     } else {
                         // Point selection
                         // Point selection
                         mapClicked(mouse.x, mouse.y)
                         mapClicked(mouse.x, mouse.y)
+                        if (typeof game !== 'undefined' && game.onMapClicked) {
+                            game.onMapClicked(mouse.x, mouse.y)
+                        }
                     }
                     }
                 }
                 }
             }
             }