← Projects · Turn-based tactics / puzzle

Push Comes to Shove

Deterministic grid tactics. Read the board, push enemies into pits and into each other, and set up the perfect turn before they get theirs.

Unity 6 · URP 2D · Solo — design & programming · 2026-03 – now

Enemy turn: a goblin's green movement path is revealed before it acts
Fig. 1 — enemy turn: one enemy's intent at a time (web build)

What I built

my contribution — nothing here is third-party work

  • Grid model built from a Tilemap: `RuntimeCellData` per cell with walkable / hazard flags from `CellData` ScriptableObjects, 0-based indexing independent of the tilemap origin.
  • Cardinal breadth-first reachability and shortest-path routing that treats walls, hazards and occupied cells as blockers — units walk around obstacles tile by tile instead of teleporting.
  • Push resolution: pushes resolve farthest-first; hitting a wall or the map edge deals collision damage, hitting another unit damages both, and a unit shoved onto a hazard tile is destroyed.
  • `Attack` as a ScriptableObject strategy (Melee, Ranged, Line): each decides what it can target and which cells it hits, so the player's attack mode and the enemy AI share one rule set. Instant `Execute` for tests, animated coroutine for play (swing → projectile → hit → counter-attack).
  • Melee retaliation, no friendly fire, and typed weaknesses that add +50% damage.
  • Enemy AI: attack if a clear shot exists, otherwise move to the best firing cell, otherwise advance; the whole enemy turn is a paced coroutine that reveals each enemy's intent (green route, red cells) just before it acts.
  • Turn manager with deployment and battle phases, a two-action-per-unit economy, win/lose per level objective (clear the board or survive N turns), result overlay and restart.
  • State-pattern interaction modes (place, move, attack) with phase-gated buttons; roster and enemy info cards built from code; frame-by-frame sprite animator; hit-flash via a custom sprite shader and MaterialPropertyBlock.
  • 23 EditMode tests over grid maths, pathing, pushes, retaliation, weaknesses and the AI's friendly-fire check, using a test-only grid constructor.
  • Design docs: vision and pillars, mechanic comparison against Into the Breach, King's Bounty and Octopath Traveler, and a staged plan for the HD-2D look.

The game

Push Comes to Shove is a small turn-based tactics game on an isometric grid. You deploy a squad of four on your side of the board, then alternate turns with the enemy. Each unit has two actions a turn: move along a cardinal path, or attack. Melee units strike adjacent enemies and get struck back; archers shoot in straight lines; some attacks push. Pushing is the point: shove an enemy into a wall for extra damage, into an ally so both get hurt, or into a pit to remove it outright. Enemies don’t telegraph before your turn — on their turn they reveal their intent one at a time, and then act. Clear the board (or survive the required number of turns) to win.

The design pillar is one line: read the board, set up the perfect turn. Damage is fixed, not rolled, so every outcome is yours to compute.

The first version followed Into the Breach: every enemy telegraphed its next move before my turn, so each turn was a puzzle against known threats. In June 2026 I replaced that with an on-turn reveal. Enemies act on their own turn, one at a time, and each shows its intended move or target for a beat before carrying it out, which is closer to King’s Bounty’s “watch the enemy act”. The telegraph-based Break mechanic went with it. What the player plans against now is everything the board shows: positions, attack ranges, weaknesses and fixed damage.

Technical breakdown

1. Grid, pathing and pushes — all tested

The grid is a plain C# class over a 2D array of cells, with a second internal constructor that builds it without a Tilemap. That constructor is what the EditMode tests use: they build a 5×5 grid in memory, block some cells with throwaway units and assert on reachability, shortest paths and push outcomes. Movement is a breadth-first flood in four directions that stops at walls, hazards and occupied cells, so the highlighted “reachable” area is exactly the set of tiles the unit can actually walk to. Pushes walk the affected cells farthest-first so a chain of shoves resolves without a unit being moved onto a cell that is about to be vacated.

A push moves its target one tile at a time and stops at the first thing in the way. Each outcome is a few lines:

private static void PushUnit(Grid grid, RuntimeCellData cell, Vector2Int direction, int distance)
{
    for (int step = 0; step < distance; step++)
    {
        Unit unit = cell.Unit;
        if (unit == null)
            return;

        RuntimeCellData dest = grid.GetCellAt(
            cell.GridPosition.x + direction.x,
            cell.GridPosition.y + direction.y);

        bool blockedByWall = dest == null || !dest.IsWalkable;

        if (blockedByWall)
        {
            unit.TakeDamage(CollisionDamage, cell);
            return;
        }

        if (dest.IsOccupied)
        {
            unit.TakeDamage(CollisionDamage, cell);
            dest.Unit.TakeDamage(CollisionDamage, dest);
            return;
        }

        cell.Unit = null;
        dest.Unit = unit;
        unit.transform.position = dest.WorldPosition;
        cell = dest;

        // Shoved into a hazard (pit / water) — the unit is destroyed.
        if (cell.IsHazard)
        {
            unit.TakeDamage(unit.CurrentHP, cell);
            return;
        }
    }
}

Attack.cs — PushUnit, unedited. CollisionDamage is 1.

The four outcomes of a push A line attack hits an enemy and pushes it one tile. Into a free tile it slides. Into a wall or the map edge it stops and takes 1 collision damage. Into another unit both take 1 collision damage. Into a pit or water it is destroyed. HIT, THEN PUSH WHAT HAPPENS A E Free tile: it slides one tile Line Attack deals 1 damage and pushes 1; chains resolve farthest-first A E Wall or map edge: it stops and takes 1 collision damage on top of the hit A E U Another unit: both stop both take 1 collision damage, whichever side they are on A E Pit or water: it falls in destroyed outright, whatever health it has left
Fig. — the four outcomes of PushUnit for one push step. A = attacker, E = enemy, U = any other unit.

2. One attack rule set for player and AI

Each attack type is a ScriptableObject with two questions: can I aim at that cell? and which cells do I hit? The player’s attack mode uses them to draw the red preview; the enemy AI uses the same two calls to decide whether a shot is clear (and refuses shots that would hit a teammate). Resolution lives in the base class: snapshot targets, apply damage and weakness bonus, apply pushes, then let a surviving adjacent melee defender counter once. There is an instant path for tests and a coroutine path for play that plays the swing, flies the projectile, lands the hit and then the counter.

3. A readable enemy turn

The enemy turn is a coroutine that iterates enemies in board order. For each action it decides (attack if clear, else move to the best firing cell, else advance toward the nearest player), draws that intent on the board, waits a beat so you can read it, resolves it with the same animated attack path the player uses, and clears the indicator. Only one enemy’s intent is ever on screen, so routes never overlap into a blob. The turn manager gates player input until the coroutine finishes.

What’s next

A second level and audio. The web build has been on itch.io since October 2026. The HD-2D look pass (post-processing, depth backdrop, grounding shadows, animated sprites) is done; lit sprite materials are still to come.

Gallery

Credits

third-party assets used — with thanks

AssetAuthorLicence
Isometric Sandbox tile sheetKenneyCC0
Humans — Top Down Pixel Art Character AssetsSanctum PixelPurchased; commercial use allowed, no resale
Orc — Top Down Pixel Art CharactersSanctum PixelPurchased; commercial use allowed, no resale
Elf and Goblin — Top Down Pixel Art CharactersSanctum PixelPurchased; commercial use allowed, no resale
PixelBattleText (animated damage numbers, bundles the Alphapix font)Psychic FurFree, Unity Asset Store EULA
Pixellari font (UI)Zacchary Dempsey-Plante100% free, credit requested