Stupid Enemy
Time to add some DANGER to your world! ๐พ A game without enemies is like a movie without conflict - technically possible, but nowhere near as exciting! You’re about to breathe life into your levels with patrolling enemies that turn peaceful exploration into heart-pounding challenge. Even “stupid” enemies can create incredible tension and satisfaction!
Try this: Watch how enemies patrol back and forth - simple but effective!
Why “Stupid” Enemies Are Actually Genius! ๐ง
Before you think your game needs super-intelligent AI, let’s talk strategy! Many legendary games use beautifully simple enemy patterns:
๐ด Pac-Man ghosts: Simple chase/scatter patterns that create complex emergent gameplay
๐ฆ Sonic badniks: Basic back-and-forth patrolling that’s predictable yet challenging
๐ Mario Goombas: Walk in straight lines, but placement makes them deadly
๐พ Space Invaders: Move in formation - simple rules, intense gameplay
Why simple enemies work:
- ๐ฏ Predictable = Fair: Players can learn patterns and improve
- โก Performance friendly: Hundreds of simple enemies > few complex ones
- ๐ญ Personality through movement: Each pattern creates different feelings
- ๐งฉ Placement matters more than AI: Smart level design beats smart AI
The wisdom: Players don’t need enemies to surprise them with complex decisions. They need enemies that create interesting spatial puzzles, timing challenges, and satisfying patterns to overcome!
Our Enemy Types
We’ll create two fundamental enemy archetypes:
๐ Horizontal Patroller
- Walks left โ right between walls
- Creates timing-based challenges
- Perfect for corridor and platform sections
โฌโฌ Vertical Patroller
- Moves up โ down between barriers
- Controls vertical space effectively
- Great for ladder and climbing sections
These simple behaviors combine to create surprisingly complex level challenges!
Setting Up Enemy Types: Modern Class Design
Let’s create a clean, flexible enemy system using contemporary JavaScript patterns:
// Enemy type definitions - easy to extend!
const ENEMY_TYPES = {
HORIZONTAL_PATROL: {
color: 0x8A2BE2, // Purple
moveX: 1, // Moves right initially
moveY: 0, // No vertical movement
speed: 1, // Pixels per frame
size: 10 // Square size
},
VERTICAL_PATROL: {
color: 0x00CED1, // Dark turquoise
moveX: 0, // No horizontal movement
moveY: 1, // Moves down initially
speed: 1,
size: 10
},
FAST_HORIZONTAL: {
color: 0x32CD32, // Lime green
moveX: 1,
moveY: 0,
speed: 2, // Twice as fast!
size: 8 // Smaller but faster
}
};
// Enemy spawn configuration for each level
const levelEnemies = {
1: [
{ type: 'HORIZONTAL_PATROL', tileX: 2, tileY: 1 },
{ type: 'VERTICAL_PATROL', tileX: 8, tileY: 1 },
{ type: 'FAST_HORIZONTAL', tileX: 1, tileY: 5 }
],
2: [
{ type: 'HORIZONTAL_PATROL', tileX: 3, tileY: 3 },
{ type: 'HORIZONTAL_PATROL', tileX: 6, tileY: 4 }
]
};
Why this approach rocks:
- ๐ฆ Organized data: All enemy properties in one place
- ๐ Easy expansion: Add new types without touching existing code
- ๐ฏ Level-specific: Different enemy layouts per level
- ๐ง Tweakable: Change speeds, colors, sizes instantly
Spawning Enemies: Bringing Danger to Life
Let’s create a robust enemy spawning system:
// Active enemies array
const enemies = [];
let nextEnemyId = 0;
// Spawn enemies for current level
function spawnEnemiesForLevel(levelId) {
// Clear existing enemies
enemies.forEach(enemy => enemy.sprite.destroy());
enemies.length = 0;
// Get enemy spawn data for this level
const spawns = levelEnemies[levelId] || [];
spawns.forEach(spawnData => {
createEnemy(spawnData.type, spawnData.tileX, spawnData.tileY);
});
}
// Create individual enemy
function createEnemy(typeName, tileX, tileY) {
const type = ENEMY_TYPES[typeName];
if (!type) {
console.warn(`Unknown enemy type: ${typeName}`);
return;
}
// Create enemy sprite
const sprite = new Graphics()
.rect(0, 0, type.size, type.size)
.fill(type.color)
.stroke({width: 1, color: 0x000000}); // Black outline
// Create enemy object
const enemy = {
id: nextEnemyId++,
sprite: sprite,
type: typeName,
// Position (convert tile coords to pixels)
x: tileX * TILE_SIZE + (TILE_SIZE - type.size) / 2,
y: tileY * TILE_SIZE + (TILE_SIZE - type.size) / 2,
width: type.size,
height: type.size,
// Movement properties
moveX: type.moveX,
moveY: type.moveY,
speed: type.speed,
// State
active: true
};
// Position sprite
sprite.x = enemy.x;
sprite.y = enemy.y;
// Add to stage and tracking
app.stage.addChild(sprite);
enemies.push(enemy);
return enemy;
}
Smart spawning features:
- ๐ฏ Tile-based positioning: Place enemies precisely on grid
- ๐ Unique IDs: Track individual enemies for special behaviors
- ๐งน Clean lifecycle: Proper creation and cleanup
- ๐จ Visual variety: Different colors and sizes per type
Enemy AI: Simple Brains, Effective Results! ๐ค
Time to give our enemies the intelligence to patrol and threaten the player:
// Main enemy AI update function
function updateEnemies() {
enemies.forEach(enemy => {
if (!enemy.active) return;
// Calculate next position
const nextX = enemy.x + enemy.moveX * enemy.speed;
const nextY = enemy.y + enemy.moveY * enemy.speed;
// Check for wall collision
if (wouldHitWall(nextX, nextY, enemy.width, enemy.height)) {
// Hit wall - reverse direction!
enemy.moveX = -enemy.moveX;
enemy.moveY = -enemy.moveY;
// Optional: Add turning animation or sound here
enemy.sprite.tint = 0xFFAAAA; // Brief flash
setTimeout(() => {
if (enemy.active) enemy.sprite.tint = 0xFFFFFF;
}, 100);
} else {
// Safe to move - update position
enemy.x = nextX;
enemy.y = nextY;
}
// Update sprite to match position
enemy.sprite.x = enemy.x;
enemy.sprite.y = enemy.y;
// Check collision with player
checkEnemyPlayerCollision(enemy);
});
}
// Collision detection between enemy and player
function checkEnemyPlayerCollision(enemy) {
if (!player.alive) return;
const distance = getDistance(enemy, player);
const minDistance = (enemy.width + player.width) / 2;
if (distance < minDistance) {
handlePlayerHit(enemy);
}
}
// Handle what happens when player gets hit
function handlePlayerHit(enemy) {
player.alive = false;
// Visual feedback
player.sprite.tint = 0xFF0000; // Flash red
enemy.sprite.tint = 0xFFFF00; // Enemy flashes yellow
// Respawn after delay
setTimeout(() => {
respawnPlayer();
}, 1500);
}
function respawnPlayer() {
player.alive = true;
player.sprite.tint = 0xFFFFFF;
// Reset to starting position
player.x = 60;
player.y = 180;
// Reset enemy colors
enemies.forEach(enemy => {
enemy.sprite.tint = 0xFFFFFF;
});
}
// Utility: Distance between two objects
function getDistance(obj1, obj2) {
const dx = (obj1.x + obj1.width/2) - (obj2.x + obj2.width/2);
const dy = (obj1.y + obj1.height/2) - (obj2.y + obj2.height/2);
return Math.sqrt(dx * dx + dy * dy);
}
The Magic of Simple AI
What makes this “stupid” AI actually brilliant:
๐ Predictable patterns: Players can learn and plan around enemy movements
โก Instant feedback: Enemies react immediately to walls with direction changes
๐ฏ Consistent threat: Always moving, always dangerous, never idle
๐จ Visual personality: Different colors and speeds create distinct “characters”
Performance benefits:
- ๐ Efficient: Simple math operations, no complex pathfinding
- ๐ Scalable: Can handle dozens of enemies without lag
- ๐งฉ Modular: Easy to add new behaviors or modify existing ones
Design wisdom: The best enemy AI doesn’t try to outsmart the player - it creates interesting spatial and temporal puzzles for them to solve!
Integration with Game Loop
// Add to your main game loop
function gameLoop() {
handleInput(); // Player movement
updateEnemies(); // Enemy AI and movement
updatePhysics(); // Gravity, collisions, etc.
render(); // Draw everything
}
app.ticker.add(gameLoop);
๐ Boom! Your peaceful world is now alive with danger and challenge! These simple patrolling enemies transform static levels into dynamic puzzles. Players must now time their movements, find safe paths, and feel the thrill of narrowly avoiding threats!
Coming next: We’ll make these enemies even smarter by adding platform awareness and more sophisticated patrol behaviors! Next: Enemy on Platform