An RTS combat system is one of the hardest things to build in game development. You need hundreds of units making independent decisions every frame, finding targets, dealing damage, moving in formation, and doing all of it without tanking performance. Most guides cover one piece of the puzzle. This one covers the full architecture: targeting, damage, commands, movement, scaling, and networking.

Key Takeaways

  • Target acquisition drives combat feel: closest-enemy targeting is simple but leads to focus fire problems. Priority systems using threat evaluation produce smarter unit behavior
  • Damage reservation prevents overkill: track incoming damage per target so units stop shooting things that are already dead. Zero-K’s open-source implementation is a solid reference
  • Attack-move needs a command queue: implement commands as objects with state machines, not one-off function calls. This makes queuing, undo, and replays possible
  • Spatial partitioning is non-negotiable at scale: a flat grid reduces target-finding from O(n squared) to near-constant time. Grids outperform quadtrees for uniformly distributed units
  • Deterministic lockstep keeps multiplayer bandwidth tiny: sync inputs instead of game state, and a million units costs the same bandwidth as one

What Makes RTS Combat Different

Most game combat systems handle one entity at a time. A player character fights an enemy. An NPC fires at a target. RTS combat is fundamentally different because every unit on the field is an autonomous agent making decisions simultaneously. A 200-unit battle means 200 parallel decision-makers running targeting, pathfinding, and damage calculations every single frame.

That creates a unique set of engineering problems. Each unit needs its own behavior tree or state machine for decision-making. Pathfinding has to work for groups, not just individuals. Damage calculations need to be fast enough to run thousands of times per tick. And the whole system needs to stay deterministic if you want multiplayer.

The systems you need to build, in order of dependency:

  1. Target acquisition (who to attack)
  2. Damage model (how attacks resolve)
  3. Command system (how players issue orders)
  4. Movement and formations (how units travel)
  5. Spatial partitioning (how to keep it fast)
  6. Networking (how to sync it all)

Target Acquisition and Selection

Target acquisition is the first thing that fires when a unit enters combat. The targeting algorithm determines how your RTS combat system feels to play. Get it wrong and your units will behave like idiots, splitting fire across six targets when focusing one would win the fight.

Detection

Every combat unit needs a detection radius. When an enemy enters that radius, the unit becomes aware of it as a potential target. The simplest approach is a distance check against all enemy units, but that is O(n) per unit and scales poorly. Spatial partitioning (covered later) drops this to near-constant time by only checking units in neighboring grid cells.

Selection Policies

Once a unit detects enemies, it needs a policy for choosing one. Common approaches, from simple to sophisticated:

PolicyHow It WorksTradeoff
Closest enemyAttack the nearest valid targetSimple but causes focus fire and overkill
Lowest HPAttack the target most likely to die fastEfficient kills but ignores incoming threats
Highest DPSAttack the unit dealing the most damageGood defense but slow at clearing numbers
Threat evaluationScore targets by weighted factors (distance, HP, DPS, unit type)Best results but needs tuning per unit type

Research on RTS combat models uses an evaluation function called LTD2 (Life-Time Damage squared) that favors keeping alive the units which deal the highest damage per frame. In practice, most shipped RTS games use threat evaluation with tunable weights. A ranged unit might weight distance heavily (shoot the closest thing), while a support unit might weight allied HP (protect the weakest friend).

One pattern from the Heresy RTS project: units maintain a focus target and only switch when the current target dies, leaves detection range, or a higher-priority threat appears. This prevents the flickering behavior you get when units re-evaluate every frame.

RTS combat system with multiple unit groups engaging targets simultaneously in a large-scale battle
Large-scale RTS combat requires each unit to independently select and engage targets every frame while coordinating with nearby allies.

Damage Models and Combat Resolution

Once a unit has a target, it needs to deal damage. RTS games use simpler damage models than RPGs because calculations run thousands of times per second across all active combatants.

Flat Damage with Armor Reduction

The standard model: damage is a flat number from the weapon, reduced by the target’s armor as a percentage. A unit dealing 20 damage against a target with 25% armor deals 15 effective damage. This is fast to calculate and easy to balance.

Damage Types and Counters

Most RTS games add a rock-paper-scissors layer through damage types. Infantry deals bonus damage to archers, archers deal bonus damage to cavalry, cavalry deals bonus damage to infantry. Implement this as a multiplier table: each weapon type has a modifier against each armor type.

// Pseudocode: damage multiplier lookup
damage_table = {
    "pierce":  { "light": 1.5, "heavy": 0.5, "siege": 0.25 },
    "slash":   { "light": 1.0, "heavy": 0.75, "siege": 0.5 },
    "siege":   { "light": 0.5, "heavy": 1.0, "siege": 1.5 }
}

effective_damage = base_damage * damage_table[weapon_type][armor_type]

Projectile vs Hitscan

Hitscan attacks resolve instantly when the attack animation fires. The target takes damage immediately. Projectile attacks create a physical object that travels across the map and can miss, get intercepted, or cause collateral damage. Projectile systems add depth but increase simulation complexity. The Heresy RTS uses physics-based projectiles where accuracy is influenced by a stat that mitigates force effects on the projectile, rather than arbitrary hit rolls.

Attack-Move and Command Architecture

Attack-move is the signature RTS command. The player clicks a destination and the selected units move toward it, automatically engaging any enemies they encounter along the way. It sounds simple but the implementation touches every system: movement, targeting, and the command queue.

The Command Pattern

Implement commands as objects, not function calls. The command pattern wraps each action (move, attack, attack-move, patrol) in an object that can be queued, canceled, and replayed. This is the foundation for shift-queuing orders, undo systems, and replay files.

A command object needs at minimum:

  • A state (pending, executing, finished, canceled)
  • An execute method that runs each tick
  • A reference to the unit it controls
  • A way to check if the goal is complete

Attack-Move Implementation

Attack-move combines a move command with a detection check. Each tick, the unit:

  1. Checks for enemies within detection range
  2. If an enemy is found, pauses movement and engages the target
  3. When the target dies or leaves range, resumes moving toward the original destination
  4. Finishes when the unit reaches the destination with no enemies in range

The DownFlux project handles this with command metadata that calculates state from the game world rather than storing it explicitly. An attack command holds a reference to a chase sub-command. When the target exits range, the attack evaluates its status through a read-only query and either persists (hoping the target returns) or self-cancels.

For partial pathfinding at scale, spread expensive A* calculations across multiple ticks. Calculate a path segment of length 10, move along it, then calculate the next segment. This prevents frame spikes when 50 units receive move orders simultaneously. If you are hitting performance walls with per-unit CharacterBody3D nodes, our CharacterBody3D vs MultiMesh guide covers how to batch hundreds of units into a single draw call.

Overkill Prevention

Without overkill prevention, ten units targeting the same enemy will all fire even though three shots would have killed it. The remaining seven shots are wasted. In a close battle, that wasted damage is the difference between winning and losing.

The solution is damage reservation. Each unit, before firing, checks how much damage is already incoming toward its target. If the target is already “doomed” (incoming damage exceeds remaining HP), the unit holds fire and retargets.

Zero-K, an open-source RTS built on the Spring engine, has a well-documented overkill prevention system. Their implementation tracks an incomingDamage table indexed by target unit ID. Each entry stores scheduled damage events keyed by the frame they will arrive. Before firing, the system:

  1. Sums all incoming damage scheduled for the target
  2. Adjusts for armor multipliers and shield regeneration
  3. Compares projected health-at-impact against zero
  4. Blocks the shot if the target will already be dead when existing projectiles land

The tricky part is handling projectile travel time. A slow projectile fired from far away needs to account for damage that will arrive sooner from closer units. Zero-K tracks this per-frame, so a unit only fires if its shot will meaningfully contribute to the kill.

Unit Movement and Formations

Moving a single unit is pathfinding. Moving a group of units without them piling on top of each other is steering. Moving a group in a recognizable shape is formation movement. An RTS combat system needs all three.

Steering Behaviors

Craig Reynolds published his boids algorithm in 1987, and it is still the foundation for group movement in games. Three rules produce emergent flocking behavior:

  • Separation – steer away from nearby units to avoid overlap
  • Cohesion – steer toward the average position of the group
  • Alignment – steer to match the average heading of neighbors

Each behavior produces a steering vector. Normalize all three, multiply by tunable weights, and sum them to get the final movement direction. Nine parameters control the system: a weight, a neighborhood distance, and a neighborhood angle for each of the three behaviors.

Beyond flocking, avoidance steering handles unit-to-unit collision without physics bodies. The tricky part is preventing oscillation when avoidance opposes seek. A perpendicular projection technique fixes this by constraining avoidance to only push units sideways relative to their target, never backwards.

Formation Systems

Formations extend steering by assigning each unit a target offset from the group center. A line formation spreads units evenly along a perpendicular axis. A wedge formation places the leader at the front with units fanning out behind. Calculate offsets mathematically (grid positions, circle distributions) and feed them as individual waypoints to each unit’s pathfinder.

The hard part is maintaining formations around obstacles. Units that pathfind around a narrow gap need to reform on the other side. One approach: use steering behaviors during movement but snap to formation offsets when the group stops or enters combat.

RTS unit formations showing grouped armies with formation banners and control groups in a large-scale battle
Formation systems assign target offsets from the group center, giving each unit a specific position to maintain during movement and combat.

Flowfield Pathfinding

Traditional A* pathfinding calculates one path per unit. With 500 units heading to the same location, that is 500 separate A* queries. Flowfield pathfinding solves this by pre-calculating movement directions for an entire map region. Every unit heading to the same destination reads from the same flowfield, making the cost independent of unit count.

A hierarchical approach divides the map into tiles (50×50 cells, for example). Run A* on the tile level to find which tiles the path crosses, then generate flowfields only for those tiles. One 50×50 flowfield generates in roughly 0.3ms after optimization. Branchless math operations (using select instead of if-else branching) can yield a 10x speedup in flowfield generation.

Scaling Combat with Spatial Partitioning

Without spatial partitioning, finding nearby enemies for 1,000 units requires 1,000 x 1,000 = 1,000,000 distance checks per frame. With a spatial grid, each unit only checks its own cell and immediate neighbors, dropping the comparison count by orders of magnitude.

Grid Implementation

The simplest and often fastest approach is a flat 2D grid. Divide the game world into fixed-size cells. Each cell holds a linked list of units whose positions fall within its bounds. To find enemies near a unit, check only the same cell and adjacent cells.

Robert Nystrom’s Game Programming Patterns covers the implementation in detail. Key operations:

  • Adding a unit: divide world position by cell size to get the cell index, insert at front of that cell’s linked list
  • Moving a unit: if the new position is in the same cell, just update coordinates. If it crosses a cell boundary, remove from the old list and insert into the new one
  • Querying neighbors: check the target cell plus adjacent cells (checking only half the neighbors avoids redundant pair comparisons)

Flat grids have constant memory regardless of unit count and handle uniformly distributed units well. They struggle when all units cluster in one cell (the local comparison degrades back to O(n squared)), but for most RTS maps with spread-out combat, grids outperform tree structures. If you are also dealing with rendering thousands of units, spatial partitioning pulls double duty for both combat queries and render culling.

When to Use Quadtrees Instead

Quadtrees subdivide space recursively based on object density. They handle clustering better than flat grids because dense areas get more subdivisions while empty areas stay as one large node. Use a quadtree if your game has extreme unit density variation, like a few scouts spread across a huge map while armies cluster in bases. For most RTS combat scenarios, a flat grid with cell sizes matching your maximum detection range is the better choice.

Networking with Deterministic Lockstep

RTS multiplayer has a unique constraint: you might have thousands of units, but the bandwidth budget is the same as any other game. Deterministic lockstep, the networking model used by Age of Empires, StarCraft, and most competitive RTS games, solves this by syncing only player inputs instead of game state.

The principle is straightforward. Every client runs the same simulation. Each frame, clients exchange only their inputs (mouse clicks, keyboard commands). Because the simulation is deterministic, identical inputs on every machine produce identical results. Bandwidth is proportional to the number of players, not the number of units. A million units costs the same bandwidth as one.

The Determinism Requirement

Lockstep only works if the simulation is bitwise identical on every machine. That means:

  • Fixed timestep, every frame advances by the exact same delta
  • No random number generators without synchronized seeds
  • Fixed-point math instead of floating-point (different CPUs compute sin/cos/tan differently)
  • Deterministic iteration order for data structures (avoid hash maps with random ordering)

Floating-point non-determinism is the biggest trap. Even if your simulation is deterministic on one machine, it may not match across different compilers, operating systems, or CPU architectures. Fixed-point arithmetic eliminates this class of bugs entirely. For a deeper dive into the networking layer, our netcode fundamentals guide covers the broader networking patterns that lockstep builds on.

Handling Latency

The downside of lockstep: the simulation can only advance when it has received input from all players. If one player’s packet is delayed, everyone waits. Two techniques mitigate this:

Playout delay buffer: buffer incoming packets briefly to smooth out network jitter, delivering inputs at a steady rate rather than in bursts.

Redundant input packing: send multiple frames of input per UDP packet. If a packet is lost, the next one contains the missing frames. With inputs at 6 bits each, sending 120 frames of redundant data is roughly 90 bytes of overhead.

Frequently Asked Questions

What is the simplest RTS combat system to implement?

Start with closest-enemy targeting, flat damage with armor reduction, and a basic move/attack command. Skip formations, overkill prevention, and networking for your first prototype. You can add complexity once the core loop feels right.

How do RTS games handle focus fire without making combat feel instant?

Most games use a combination of overkill prevention (units stop shooting doomed targets) and attack cooldowns. Some games like StarCraft also give units a short acquisition delay when switching targets, which naturally spreads fire across multiple enemies.

Should I use A* or flowfields for RTS pathfinding?

A* is simpler and works fine for small unit counts (under 50 moving simultaneously). Flowfields become cost-effective once you hit hundreds of units sharing destinations. Most production RTS games use flowfields with hierarchical tile decomposition.

Can I use floating-point math in a deterministic lockstep simulation?

Only if all clients run on identical hardware and software configurations. In practice, most lockstep RTS games use fixed-point arithmetic to guarantee bitwise determinism across different CPUs, operating systems, and compilers.

How many units can an RTS combat system handle before performance degrades?

With spatial partitioning (flat grid) and efficient data structures, 8,000+ units with collision detection and combat is achievable on modern hardware. The bottleneck is usually rendering, not simulation. Batched rendering and LOD systems extend the ceiling further.

Summary

Building an RTS combat system means solving six interconnected problems: target acquisition, damage resolution, command architecture, unit movement, performance scaling, and network synchronization. Each piece feeds into the others. Your targeting system determines how damage distributes. Your command system determines what orders players can give. Your spatial partitioning determines how many units you can support.

Start with the simplest version of each system and iterate. Get closest-enemy targeting working before adding threat evaluation. Get basic move and attack commands working before implementing attack-move. Add overkill prevention when you notice wasted damage in playtests. The complexity builds on itself, and each layer makes the combat feel noticeably better.

For the spatial partitioning deep dive, Robert Nystrom’s Game Programming Patterns is one of the best references available. And if you are building out more game systems beyond combat, our complete guides on behavior trees, state machines, and netcode fundamentals cover the foundational systems that RTS combat builds on.