Into the Breach style tactics in GDScript: rules as events, previews on a cloned board, and attacks that move when pushed

In Into the Breach style tactics, enemies show exactly where they will strike, and your turn is about pushing them so the attack lands somewhere else. It looks like a lot of special cases, but most of it falls out of a few structural choices. Here’s how I built it in plain GDScript, in case it helps anyone making a grid tactics game.

1. The rules never touch the screen

The board is a plain RefCounted class: terrain in an array, units as small data objects, an occupancy lookup. Every rule (move, attack, push, bump, fall, explode) changes that data and returns a list of events as plain dictionaries:

func _push(target: Piece, dir: Vector2i, events: Array) -> void:
	var from := target.cell
	var to := from + dir
	var blocker := piece_at(to)
	if not in_bounds(to) or terrain_at(to) == Terrain.ROCK or blocker != null:
		events.append({"t": "bump", "id": target.id, "cell": from, "into": to})
		_damage(target, BUMP_DAMAGE, events, true)
		if blocker != null:
			_damage(blocker, BUMP_DAMAGE, events, true)
		return
	_place(target, to)
	events.append({"t": "push", "id": target.id, "from": from, "to": to})
	if terrain_at(to) == Terrain.CHASM and not target.flying:
		events.append({"t": "fall", "id": target.id, "cell": to})

The rules finish instantly. The battle view then walks the event list and awaits a tween for each one. Chain reactions (a push into a barrel that explodes and kills something that explodes) need no special handling: they’re just more events in the list, in the order they happened.

2. Preview = run the real rules on a copy

Because the board is plain data, cloning it is cheap (duplicate the arrays, clone each unit). When the player points at a target, the game clones the board, runs the actual attack on the copy and draws the result: where everyone ends up, who takes damage, who dies.

func preview_attack(p: Piece, dir: Vector2i, dist: int = 1) -> Dictionary:
	var copy := board.clone()
	var events := copy.attack(copy.get_piece(p.id), dir, dist)
	return {"board": copy, "events": events}

There’s no separate “prediction” code to drift out of sync with the real rules, so the preview is always right (the stress tests check this on hundreds of random attacks anyway). Undo is the same trick: push a clone before each move, pop it to undo.

3. Enemies store a direction, not a target cell

This is the one that makes pushing feel good. An enemy’s announced attack is saved as “fire east” (plus a distance for artillery), not “hit cell (4, 2)”. When its turn comes, it fires that direction from wherever it now stands. So if you push an enemy one cell, its attack moves with it, and can end up hitting another enemy. Attacks fire in a fixed, numbered order, which the player can see, so “push the first one so it hits the second” is a plan you can make.

4. Enemy AI just tries everything

On an 8x8 board, brute force is fine. Each enemy tries every cell it can walk to and every way it can aim from there, works out what that attack would hit on the board as it stands, and scores it with a handful of weights: damage to the squad, kills, pushing someone into a chasm, a penalty for hitting its own allies, and a big penalty for standing where an ally’s announced attack will land. A whole enemy turn plans in about 3 ms. Tuning the AI is changing a few constants, and a seeded random tie-break means the same seed always plays out the same way, which makes bugs reproducible.

5. A bot that plays your side

Since the rules don’t need the screen, a simple squad bot can play hundreds of battles and full runs headless. The tests use it to check that the board stays valid after every action (no two units on one cell, nothing standing in a chasm, HP never above max), and it’s a quick way to see whether a balance change made the game easier or harder.


I’ve packaged this up as Tactics Kit, a complete tactics roguelite template for Godot 4: a squad of three, six enemies including a boss, pushes, bumps, chasms and explosive barrels, cargo to protect, a branching run map with shops and upgrades, mouse, keyboard and gamepad, and units, abilities and encounters you edit as resources in the Inspector:

There’s a free browser demo if you want to try the pushing first: Tactics Kit - Playable Demo by Spare Parts Studio

Happy to answer questions about any of it.