How to Add a New Enemy Type
This tutorial shows how to create and configure a new enemy type in R-Type.
๐ Overviewโ
Adding a new enemy involves:
- Defining enemy data in
config/game/enemies.toml - Creating sprite assets
- (Optional) Creating custom behavior system
- Testing in-game
๐ฏ Step 1: Define Enemy Configurationโ
Edit config/game/enemies.toml:
[[enemy]]
id = "drone" # Unique identifier
name = "Attack Drone" # Display name
health = 30 # Hit points
speed = 180.0 # Movement speed (pixels/second)
damage = 15 # Contact/collision damage
score = 150 # Points awarded on kill
sprite = "enemy_drone.png" # Sprite filename (in assets/img/)
behavior = "zigzag" # Movement pattern (see below)
fire_rate = 1.5 # Seconds between shots (0 = never fires)
projectile = "enemy_laser" # Projectile type to fire
drop_chance = 0.2 # Power-up drop probability (0.0-1.0)
width = 32 # Sprite width
height = 32 # Sprite height
Available Behavior Typesโ
straightโ
Flies straight left across the screen.
behavior = "straight"
zigzagโ
Vertical zig-zag pattern while moving left.
behavior = "zigzag"
behavior_params = { amplitude = 100.0, frequency = 2.0 }
sine_waveโ
Smooth sine wave movement.
behavior = "sine_wave"
behavior_params = { amplitude = 150.0, frequency = 1.5 }
circleโ
Circular motion around spawn point.
behavior = "circle"
behavior_params = { radius = 80.0, angular_speed = 3.14 }
kamikazeโ
Rushes directly at nearest player.
behavior = "kamikaze"
behavior_params = { acceleration = 200.0 }
stationaryโ
Stays in place (turret).
behavior = "stationary"
follow_playerโ
Tracks and chases the nearest player.
behavior = "follow_player"
behavior_params = { max_speed = 150.0, turn_rate = 2.0 }
๐ฏ Step 2: Create Sprite Assetโ
Sprite Requirementsโ
- Format: PNG with transparency
- Size: Power of 2 recommended (32x32, 64x64, etc.)
- Location:
assets/img/ - Naming: Match the
spritefield in config
Example Sprite Specificationsโ
Small enemies: 32x32 pixels
Medium enemies: 48x48 pixels
Large enemies: 64x64 pixels
Bosses: 128x128 or larger
Create Your Spriteโ
- Use image editor (GIMP, Photoshop, Aseprite)
- Design enemy ship with transparent background
- Export as PNG:
enemy_drone.png - Place in
assets/img/
Example Using Asepriteโ
# Create new sprite
aseprite -b --sheet enemy_drone.png --data enemy_drone.json
# Export with transparency
aseprite -b enemy_drone.aseprite --save-as enemy_drone.png
๐ฏ Step 3: Define Projectile (If Enemy Shoots)โ
If fire_rate > 0, define the projectile in config/game/projectiles.toml:
[[projectile]]
id = "enemy_laser"
sprite = "enemy_laser.png"
speed = 400.0
damage = 15
pierce = false # Pass through targets
homing = false # Track player
lifetime = 3.0 # Seconds before despawn
width = 16
height = 4
color = [255, 0, 0, 255] # Red laser (RGBA)
Projectile Typesโ
Basic Bullet:
id = "enemy_bullet"
speed = 300.0
pierce = false
homing = false
Homing Missile:
id = "enemy_missile"
speed = 250.0
pierce = false
homing = true
homing_strength = 2.0 # Turn rate
Piercing Laser:
id = "enemy_beam"
speed = 500.0
pierce = true
max_pierce_count = 3 # Max enemies to hit
๐ฏ Step 4: Add to Level Wavesโ
Edit level files in config/game/levels/:
# config/game/levels/level1.toml
[[wave]]
time = 45 # Spawn at 45 seconds
enemy = "drone" # Our new enemy
count = 8 # Number to spawn
formation = "v" # V-formation
spawn_interval = 0.8 # Seconds between spawns
spawn_x = 1920 # Right edge of screen
spawn_y = "random" # Random Y position (or specific value)
[[wave]]
time = 60
enemy = "drone"
count = 5
formation = "line"
spawn_interval = 1.0
spawn_x = 1920
spawn_y = 360 # Center of screen
Formation Optionsโ
V-Formation:
formation = "v"
formation_params = { angle = 45.0, spacing = 50.0 }
Line Formation:
formation = "line"
formation_params = { spacing = 60.0, orientation = "horizontal" }
Box/Grid:
formation = "box"
formation_params = { rows = 3, cols = 4, spacing_x = 50.0, spacing_y = 50.0 }
Random:
formation = "random"
formation_params = { min_x = 1500, max_x = 1920, min_y = 100, max_y = 980 }
๐ฏ Step 5: (Optional) Create Custom Behaviorโ
For complex behaviors not covered by presets, create a custom system.
Create Behavior Systemโ
src/games/rtype/server/Systems/DroneBehaviorSystem.hpp:
#ifndef RTYPE_SERVER_SYSTEMS_DRONEBEHAVIORSYSTEM_HPP_
#define RTYPE_SERVER_SYSTEMS_DRONEBEHAVIORSYSTEM_HPP_
#include "engine/src/ASystem.hpp"
namespace rtype::server {
class DroneBehaviorSystem : public engine::ASystem {
public:
DroneBehaviorSystem() : ASystem("DroneBehaviorSystem") {}
void update(ECS::Registry& registry, float dt) override;
};
} // namespace rtype::server
#endif
src/games/rtype/server/Systems/DroneBehaviorSystem.cpp:
#include "DroneBehaviorSystem.hpp"
#include "common/src/components/Position.hpp"
#include "common/src/components/Velocity.hpp"
#include "common/src/components/EnemyTag.hpp"
namespace rtype::server {
void DroneBehaviorSystem::update(ECS::Registry& registry, float dt) {
auto view = registry.view<component::EnemyTag,
component::Position,
component::Velocity>();
view.each([dt](auto entity,
component::EnemyTag& tag,
component::Position& pos,
component::Velocity& vel) {
// Only affect "drone" type enemies
if (tag.type != "drone") return;
// Custom behavior: spiral pattern
float time = tag.aliveTime;
vel.dx = -150.0f; // Move left
vel.dy = std::sin(time * 2.0f) * 100.0f; // Sine wave
// Add rotation (if you have rotation component)
// rotation.angle += dt * 3.14159f;
});
}
} // namespace rtype::server
Register Systemโ
In GameEngine.cpp:
#include "Systems/DroneBehaviorSystem.hpp"
void GameEngine::initialize() {
// ... other systems
systems_.push_back(std::make_unique<DroneBehaviorSystem>());
}
๐ฏ Step 6: Balance Testingโ
Test Checklistโ
- Enemy spawns correctly
- Movement pattern works as expected
- Projectiles fire at correct rate
- Collision detection works
- Health/damage values are balanced
- Score value is appropriate
- Power-up drop rate feels right
- Visual appearance is clear
Balancing Guidelinesโ
Early Game (Level 1-2):
health = 20-50
speed = 100-200
damage = 10-15
score = 100-250
Mid Game (Level 3-4):
health = 50-100
speed = 150-250
damage = 15-25
score = 250-500
Late Game (Level 5+):
health = 100-200
speed = 200-300
damage = 25-40
score = 500-1000
Quick Test Modeโ
Add test wave at start of level for rapid iteration:
# Temporary test wave
[[wave]]
time = 5 # Spawn early
enemy = "drone"
count = 3
formation = "line"
spawn_interval = 1.0
spawn_x = 1000 # Closer to player
spawn_y = 360
๐ฏ Step 7: Visual Enhancementsโ
Add Explosion Effectโ
Define in config/game/effects.toml:
[[effect]]
id = "drone_explosion"
sprite_sheet = "explosions.png"
frame_count = 8
frame_duration = 0.05 # Seconds per frame
width = 64
height = 64
sound = "explosion_small.ogg"
Link to enemy:
[[enemy]]
id = "drone"
# ... other properties
death_effect = "drone_explosion"
death_sound = "explosion_small.ogg"
Add Trail Effectโ
[[enemy]]
id = "drone"
# ... other properties
trail_effect = "engine_trail"
trail_color = [100, 200, 255, 200] # Blue trail
Add Audioโ
Place audio files in assets/audio/:
enemy_drone_spawn.oggenemy_drone_fire.oggenemy_drone_death.ogg
Reference in config:
[[enemy]]
id = "drone"
# ... other properties
spawn_sound = "enemy_drone_spawn.ogg"
fire_sound = "enemy_drone_fire.ogg"
death_sound = "enemy_drone_death.ogg"
๐ฏ Step 8: Create Boss Enemyโ
Bosses are special enemies with complex behavior.
[[enemy]]
id = "boss_guardian"
name = "Guardian Mech"
health = 5000 # Much higher HP
speed = 50.0 # Slower movement
damage = 50 # High contact damage
score = 10000 # Huge score
sprite = "boss_guardian.png"
behavior = "boss" # Special boss behavior
fire_rate = 0.5 # Rapid fire
projectile = "boss_laser"
drop_chance = 1.0 # Always drop power-up
width = 256 # Large sprite
height = 256
is_boss = true # Mark as boss
phases = 3 # Multi-phase boss
# Boss-specific properties
[[enemy.boss_phases]]
phase = 1
health_threshold = 3500 # HP when phase starts
behavior = "stationary"
fire_pattern = "spread" # Spread shot
fire_rate = 0.8
[[enemy.boss_phases]]
phase = 2
health_threshold = 2000
behavior = "circle"
fire_pattern = "spiral" # Spiral bullet pattern
fire_rate = 0.5
[[enemy.boss_phases]]
phase = 3
health_threshold = 0
behavior = "kamikaze"
fire_pattern = "random" # Chaotic firing
fire_rate = 0.3
# Weak points (optional)
[[enemy.weak_points]]
name = "core"
offset_x = 0 # Relative to enemy position
offset_y = 0
width = 64
height = 64
damage_multiplier = 2.0 # 2x damage to weak point
Boss in Levelโ
[[boss]]
enemy = "boss_guardian"
spawn_time = 180 # After 3 minutes
music = "boss_theme.ogg" # Special music
warning_time = 10 # Warning 10s before spawn
warning_message = "WARNING: BOSS APPROACHING"
๐ฏ Step 9: Advanced Featuresโ
State Machine Behaviorโ
For enemies that change behavior based on conditions:
struct DroneAIComponent {
enum class State {
Patrol,
Attack,
Retreat
};
State currentState = State::Patrol;
float stateTimer = 0.0f;
float aggroRange = 300.0f;
float retreatHealthPercent = 0.3f;
};
Flocking Behaviorโ
For groups of enemies that move together:
void FlockingSystem::update(ECS::Registry& registry, float dt) {
auto view = registry.view<component::FlockingTag,
component::Position,
component::Velocity>();
view.each([&](auto entity, auto& flock, auto& pos, auto& vel) {
// Separation: avoid crowding neighbors
// Alignment: steer towards average heading
// Cohesion: steer towards average position
calculateFlockingForces(registry, entity, pos, vel);
});
}
Dynamic Difficultyโ
Adjust enemy stats based on player performance:
[[enemy]]
id = "adaptive_drone"
# ... base properties
difficulty_scaling = true
health_per_level = 5 # +5 HP per level
speed_per_level = 10.0 # +10 speed per level
๐ Enemy Design Patternsโ
Cannon Fodderโ
- Low HP, high count
- Simple movement
- Rarely fire
- Low score
health = 10
speed = 150
fire_rate = 5.0 # Rarely
score = 50
Elite Unitsโ
- Medium HP
- Complex movement
- Frequent fire
- Medium score
health = 60
speed = 200
fire_rate = 1.5
score = 300
Mini-Bossesโ
- High HP
- Slow, strategic movement
- Dangerous attacks
- High score
health = 500
speed = 80
fire_rate = 0.5
score = 2000
๐งช Testingโ
Create test file tests/games/test_enemy_drone.cpp:
#include <gtest/gtest.h>
#include "ecs/src/Registry.hpp"
#include "config/EnemyLoader.hpp"
TEST(EnemyTest, DroneLoadsCorrectly) {
ECS::Registry registry;
auto enemyDef = loadEnemyDefinition("drone");
EXPECT_EQ(enemyDef.id, "drone");
EXPECT_EQ(enemyDef.health, 30);
EXPECT_FLOAT_EQ(enemyDef.speed, 180.0f);
}
TEST(EnemyTest, DroneSpawnsCorrectly) {
ECS::Registry registry;
auto enemy = spawnEnemy(registry, "drone", 100.0f, 200.0f);
EXPECT_TRUE(registry.hasComponent<component::Position>(enemy));
EXPECT_TRUE(registry.hasComponent<component::Health>(enemy));
EXPECT_TRUE(registry.hasComponent<component::EnemyTag>(enemy));
}
๐ Related Resourcesโ
Happy enemy designing! ๐พ