Drone Invaders

Drone Invaders is an example GVA external application that demonstrates the external application interface while providing an entertaining game for operator training and recreation.

Drone Invaders

Gameplay in action:

Drone Invaders Gameplay

Overview

Drone Invaders is a classic arcade-style game where players defend against waves of descending drones. It showcases:

  • External application registration
  • Display rendering via DDS
  • Soft key input handling
  • Game state management

Gameplay

Objective

Defend your position by shooting down incoming drones before they reach the ground. Survive as long as possible and achieve the highest score.

Controls

Soft Key Function
1 (←) Move left
2 (→) Move right
3 Fire
4 -
5 -
6 Exit

Scoring

Target Points
Basic Drone 10
Fast Drone 25
Armoured Drone 50
Boss Drone 100
Wave Bonus Wave × 50

Game Architecture

Application Flow

sequenceDiagram participant Op as Operator participant HMI as HMI participant DDS as DDS Network participant Game as Drone Invaders participant Audio as Audio System Note over Game: Game starts Game->>DDS: Register application Game->>DDS: Set soft key labels Note right of Game: LEFT, RIGHT, FIRE, EXIT DDS->>HMI: Update menus Op->>HMI: Select Drone Invaders HMI->>DDS: Activate app DDS->>Game: Application active Game->>Game: Init game state Game->>DDS: Display title screen Op->>HMI: Press FIRE to start HMI->>DDS: Key event (FIRE) DDS->>Game: Start game loop Game loop (30 FPS) Game->>Game: Update game state Game->>Game: Move player Game->>Game: Move drones Game->>Game: Check collisions Game->>DDS: Render frame DDS->>HMI: Display graphics alt Drone destroyed Game->>Audio: Play explosion Game->>Game: Add score end alt Player hit Game->>Audio: Play damage Game->>Game: Lose life end end Note over Game: Game over Game->>DDS: Display score Op->>HMI: Press EXIT HMI->>DDS: Key event DDS->>Game: Deactivate

State Machine

stateDiagram-v2 [*] --> Title : App starts Title --> Playing : FIRE pressed Playing --> Paused : PAUSE Paused --> Playing : RESUME Playing --> LevelComplete : All drones destroyed LevelComplete --> Playing : Next wave Playing --> GameOver : Lives = 0 GameOver --> Title : Any key GameOver --> [*] : EXIT Title --> [*] : EXIT

Implementation Details

Game Entities

struct Player {
    float x, y;
    int lives;
    int score;
    float fireRate;
    bool canFire;
};

struct Drone {
    float x, y;
    float velocityX, velocityY;
    DroneType type;
    int health;
    bool alive;
};

struct Bullet {
    float x, y;
    float velocityY;
    bool active;
};

enum class DroneType {
    BASIC,      // Standard drone
    FAST,       // Quick movement
    ARMOURED,   // Takes multiple hits
    BOSS        // Large, tough, high score
};

Rendering

The game renders graphics using GVA draw commands:

void DroneInvaders::generateDrawCommands(std::vector<GVA::DrawCommand>& cmds) {
    // Background
    cmds.push_back({
        .type = GVA::DrawCommandType::CLEAR,
        .color = 0xFF0A0A1E  // Dark blue
    });

    // Stars (background decoration)
    for (const auto& star : m_stars) {
        cmds.push_back({
            .type = GVA::DrawCommandType::FILLED_RECT,
            .x = star.x, .y = star.y,
            .width = 2, .height = 2,
            .color = 0xFFFFFFFF
        });
    }

    // Ground
    cmds.push_back({
        .type = GVA::DrawCommandType::FILLED_RECT,
        .x = 0, .y = 450,
        .width = 640, .height = 30,
        .color = 0xFF2E7D32  // Green
    });

    // Player
    drawPlayer(cmds);

    // Drones
    for (const auto& drone : m_drones) {
        if (drone.alive) {
            drawDrone(cmds, drone);
        }
    }

    // Bullets
    for (const auto& bullet : m_bullets) {
        if (bullet.active) {
            cmds.push_back({
                .type = GVA::DrawCommandType::FILLED_RECT,
                .x = (int)bullet.x - 2, .y = (int)bullet.y,
                .width = 4, .height = 10,
                .color = 0xFFFFFF00  // Yellow
            });
        }
    }

    // HUD
    drawHud(cmds);
}

void DroneInvaders::drawPlayer(std::vector<GVA::DrawCommand>& cmds) {
    // Player turret (simplified)
    cmds.push_back({
        .type = GVA::DrawCommandType::FILLED_RECT,
        .x = (int)m_player.x - 20, .y = 430,
        .width = 40, .height = 15,
        .color = 0xFF00AA00  // Green
    });

    // Gun barrel
    cmds.push_back({
        .type = GVA::DrawCommandType::FILLED_RECT,
        .x = (int)m_player.x - 3, .y = 415,
        .width = 6, .height = 20,
        .color = 0xFF008800
    });
}

void DroneInvaders::drawDrone(std::vector<GVA::DrawCommand>& cmds, 
                               const Drone& drone) {
    uint32_t color;
    int size;

    switch (drone.type) {
        case DroneType::BASIC:
            color = 0xFFFF4444;
            size = 24;
            break;
        case DroneType::FAST:
            color = 0xFFFF8800;
            size = 20;
            break;
        case DroneType::ARMOURED:
            color = 0xFF8844FF;
            size = 28;
            break;
        case DroneType::BOSS:
            color = 0xFFFF0000;
            size = 48;
            break;
    }

    // Drone body
    cmds.push_back({
        .type = GVA::DrawCommandType::FILLED_RECT,
        .x = (int)drone.x - size/2, .y = (int)drone.y - size/2,
        .width = size, .height = size,
        .color = color
    });

    // Rotor indicators
    cmds.push_back({
        .type = GVA::DrawCommandType::FILLED_RECT,
        .x = (int)drone.x - size/2 - 4, .y = (int)drone.y - 2,
        .width = 4, .height = 4,
        .color = 0xFF444444
    });
    cmds.push_back({
        .type = GVA::DrawCommandType::FILLED_RECT,
        .x = (int)drone.x + size/2, .y = (int)drone.y - 2,
        .width = 4, .height = 4,
        .color = 0xFF444444
    });
}

void DroneInvaders::drawHud(std::vector<GVA::DrawCommand>& cmds) {
    // Score
    char scoreText[32];
    snprintf(scoreText, sizeof(scoreText), "SCORE: %d", m_player.score);
    cmds.push_back({
        .type = GVA::DrawCommandType::TEXT,
        .x = 10, .y = 20,
        .color = 0xFFFFFFFF,
        .text = scoreText
    });

    // Lives
    char livesText[32];
    snprintf(livesText, sizeof(livesText), "LIVES: %d", m_player.lives);
    cmds.push_back({
        .type = GVA::DrawCommandType::TEXT,
        .x = 540, .y = 20,
        .color = 0xFFFFFFFF,
        .text = livesText
    });

    // Wave
    char waveText[32];
    snprintf(waveText, sizeof(waveText), "WAVE: %d", m_currentWave);
    cmds.push_back({
        .type = GVA::DrawCommandType::TEXT,
        .x = 280, .y = 20,
        .color = 0xFFFFFF00,
        .text = waveText
    });
}

Input Handling

void DroneInvaders::onKeyPressed(int keyId) {
    if (m_gameState == GameState::TITLE) {
        if (keyId == 2) {  // FIRE
            startGame();
        } else if (keyId == 5) {  // EXIT
            requestExit();
        }
        return;
    }

    if (m_gameState == GameState::PLAYING) {
        switch (keyId) {
            case 0:  // LEFT
                m_moveLeft = true;
                break;
            case 1:  // RIGHT
                m_moveRight = true;
                break;
            case 2:  // FIRE
                fire();
                break;
            case 5:  // EXIT
                m_gameState = GameState::PAUSED;
                break;
        }
    }

    if (m_gameState == GameState::GAME_OVER) {
        if (keyId == 5) {
            requestExit();
        } else {
            m_gameState = GameState::TITLE;
        }
    }
}

void DroneInvaders::onKeyReleased(int keyId) {
    switch (keyId) {
        case 0:
            m_moveLeft = false;
            break;
        case 1:
            m_moveRight = false;
            break;
    }
}

Game Logic

void DroneInvaders::update() {
    if (m_gameState != GameState::PLAYING) return;

    float dt = 1.0f / 30.0f;  // 30 FPS

    // Move player
    float moveSpeed = 300.0f * dt;
    if (m_moveLeft) m_player.x -= moveSpeed;
    if (m_moveRight) m_player.x += moveSpeed;
    m_player.x = std::clamp(m_player.x, 30.0f, 610.0f);

    // Update bullets
    for (auto& bullet : m_bullets) {
        if (bullet.active) {
            bullet.y -= 400.0f * dt;
            if (bullet.y < 0) {
                bullet.active = false;
            }
        }
    }

    // Update drones
    bool anyAlive = false;
    for (auto& drone : m_drones) {
        if (!drone.alive) continue;
        anyAlive = true;

        drone.x += drone.velocityX * dt;
        drone.y += drone.velocityY * dt;

        // Bounce off walls
        if (drone.x < 20 || drone.x > 620) {
            drone.velocityX = -drone.velocityX;
            drone.y += 20;  // Move down when bouncing
        }

        // Check drone reached ground
        if (drone.y > 430) {
            m_player.lives--;
            drone.alive = false;
            if (m_player.lives <= 0) {
                m_gameState = GameState::GAME_OVER;
            }
        }
    }

    // Check bullet-drone collisions
    for (auto& bullet : m_bullets) {
        if (!bullet.active) continue;

        for (auto& drone : m_drones) {
            if (!drone.alive) continue;

            if (checkCollision(bullet, drone)) {
                bullet.active = false;
                drone.health--;

                if (drone.health <= 0) {
                    drone.alive = false;
                    m_player.score += getScore(drone.type);
                    // Spawn explosion effect
                }
                break;
            }
        }
    }

    // Check wave complete
    if (!anyAlive) {
        nextWave();
    }
}

Running the Game

From HMI

  1. Navigate to Function Select
  2. Select "External Apps"
  3. Choose "Drone Invaders"
  4. Press FIRE to start

Standalone (Development)

cd build/bin
./gva-qt6-app-drone-invaders

Configuration

The game can be configured via JSON:

{
    "droneInvaders": {
        "startLives": 3,
        "startWave": 1,
        "playerSpeed": 300,
        "bulletSpeed": 400,
        "fireRate": 0.25,
        "droneSpeedMultiplier": 1.0,
        "audioEnabled": true,
        "highScoreFile": "drone_invaders_scores.json"
    }
}

High Scores

High scores are stored locally and can be viewed after game over:

┌─────────────────────────────────────┐
│       DRONE INVADERS                │
│                                     │
│        GAME OVER                    │
│                                     │
│      YOUR SCORE: 4,250              │
│                                     │
│      HIGH SCORES:                   │
│      1. AAA .......... 12,500      │
│      2. BOB ..........  8,750      │
│      3. CAT ..........  6,200      │
│      4. YOU ..........  4,250 NEW! │
│      5. DOG ..........  3,100      │
│                                     │
│   Press any key to continue         │
└─────────────────────────────────────┘

Educational Value

Drone Invaders demonstrates several GVA integration concepts:

  1. Registration - Proper application lifecycle
  2. Display Rendering - Draw command-based graphics
  3. Input Handling - Soft key event processing
  4. State Management - Application state machine
  5. Resource Management - Clean shutdown

Developers can use this as a template for creating their own external applications.