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.

Gameplay in action:

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¶
State Machine¶
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¶
- Navigate to Function Select
- Select "External Apps"
- Choose "Drone Invaders"
- Press FIRE to start
Standalone (Development)¶
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:
- Registration - Proper application lifecycle
- Display Rendering - Draw command-based graphics
- Input Handling - Soft key event processing
- State Management - Application state machine
- Resource Management - Clean shutdown
Developers can use this as a template for creating their own external applications.