Appearance
How to Build: Auto-Battle
Every idle game lives or dies by its core loop. For Endless Idle, that loop is deceptively simple: the hero walks down the road, fights whatever shows up, pockets the gold, and keeps marching toward the boss. No player input required. It sounds trivial until you realize this one system is the heartbeat of the entire game — everything else (manual navigation, vendors, checkpoints) layers on top of it.
I spent a lot of time thinking about how to make this feel right. The key insight is that auto-battle isn't really a "combat system." It's a state machine that orchestrates movement, combat, and progression into one seamless flow. Let's build it.
The Behavior Loop
Here's what happens when auto-battle is enabled, every single tick:
- Follow the main road path (next waypoint from the level's road data)
- Pause when enemies aggro — the combat system takes over
- After combat, collect gold and continue along the road
- Use potions automatically at an HP threshold (the consumable system handles this)
- Regen HP passively between enemy clusters
- Reach the boss, fight it
- Win: level complete. Die: new level generated.
The player can interrupt this at any time by tapping to move manually. We'll come back to that.
The AutoBattle Trait
The trait itself is minimal. It tracks whether auto-battle is on, where we are along the road, and whether the hero should fight back when manually navigating:
typescript
const AutoBattle = trait({
enabled: true,
roadPathIndex: 0,
autoRetaliate: true,
})Road path data comes from the LevelRoadPath world trait — that's defined in the level structure system. The auto-battle system just consumes it as an ordered array of waypoints.
Writing the Tick System
The tickAutoBattle system runs each game tick and manages the high-level behavior loop. Here's the signature and the logic you need to implement:
typescript
function tickAutoBattle(world: World): voidThe system queries for the player entity (needs IsPlayer, AutoBattle, CombatState, MovementTarget, and Health). Then it follows a simple priority chain:
- Not enabled? Return early.
- In combat? Let the combat system handle it — return early.
- Already moving to a waypoint? Wait for arrival — return early.
- Reached end of road? We're at the boss — return early.
- Otherwise: set the next road waypoint as the movement target, increment
roadPathIndex.
Notice how the system doesn't do combat or movement itself. It just sets targets and gets out of the way. The movement system handles walking, the combat system handles fighting, and auto-battle is the conductor that keeps the music playing.
Re-engaging After Combat
This is the elegant part: you don't need to write any "post-combat resume" logic. When combat ends (all nearby enemies are dead), the CombatState.inCombat flag flips to false. On the very next tick, tickAutoBattle passes through the priority chain, finds no target set, and queues up the next road waypoint. The hero just... keeps walking.
We now know how to keep the hero marching forward. But we still need a way for the player to take control.
Interrupting Auto-Battle
When the player taps a location on the terrain, the movement system sets AutoBattle.enabled = false and assigns a manual movement target. On the next tick, tickAutoBattle sees enabled is false and returns immediately. The hero walks wherever the player told it to go.
This is handled by the manual navigation system — auto-battle just needs to respect the enabled flag.
Re-enabling from the HUD
The HUD menu bar includes an auto-battle toggle button. When the player taps it, you re-enable road following from the current road path index:
typescript
function reEnableAutoBattle(player: Entity): voidSet AutoBattle.enabled back to true, and the tick system picks up where it left off. The hero snaps back to the road and continues its march.
What This System Depends On
- Movement — sets movement targets along the road
- Combat Tick — handles the actual fighting
- Aggro — detects enemies along the road
- Level Structure — provides the road path data via
LevelRoadPath
The auto-battle system is the glue that binds these together into a cohesive idle experience. Get this right and the game plays itself beautifully. Get it wrong and the hero stands around looking confused — which, honestly, is also kind of funny.