Okay, This Is Starting to Look Like a Game

Four mechs, branching turns, and a real battle HUD

I have been much better about working on my game than writing updates about working on my game. That's a pretty decent tradeoff if you need to make one. Better than the other way around, at least.

In my last update, I had just learned the basics of shaders. Since then, I've built reusable materials for mechs and terrain, added tactical overlays and props, expanded the battle from a movement experiment into an actual 2v2 fight, put all four mechs on the board, and started building the game's real battle interface.

Not only has that been a frankly ridiculous amount of work, but today has been the first time I've been able to run the project, look at it, and think, "Oh wow, that looks suspiciously like a video game."

Hey, that kinda looks like a vidja game

Making the Shader Useful

The shader from my previous post started as a learning exercise. It had some colors, some fake wear, and some knobs I could fiddle with in Godot's inspector.

That got turned that into a shared mech paint system. The player and enemy teams use separate material instances, but both materials use the same underlying shader. This means I can change the team colors, wear amount, wear scale, roughness, and exposed metal appearance without creating a completely separate shader for every mech.

I also built a shared world-space terrain shader and created seven material variants for the different terrain types in the prototype: salvage, sand, highland, mountain floor, ocean, savannah, and swamp.

Mech paint lab

Terrain lab

Then I created another reusable shader for tactical overlays. The same basic shader can now produce separate materials for:

  • Hovered hexes
  • Legal targets
  • Movement paths
  • Weapon range
  • Line of sight

These are reusable and can be toggled on and off or reconfigured by the presentation layer as the player considers an action.

Finally, because apparently no abandoned industrial battlefield is complete without an OSHA violation, I imported a barrel model off of Poly Haven, brought it through Blender, exported it into Godot, and made a shared material that shifts it into the game's muted ochre palette while keeping its original normal-map detail.

Terrain lab

Yet another prop in my arsenal.

Making Turns Branch

The last time I wrote about connecting Swift and Godot, clicking a hex caused the Swift rules engine to answer one question: "Can the player reach this tile?" A useful milestone, no doubt, but not much of a turn.

The battle engine now supports a flexible action phase. A unit can move, attack, play cards, and end its turn without being forced through one rigid sequence. It tracks which actions the unit has already taken, which targets are legal, whether it can afford a card, and why a particular action is unavailable.

That last part is especially important. The interface shouldn't have to recreate the game's rules in order to decide whether a button should be enabled. Instead, it asks the Swift engine for a description of what is currently possible, modeled primarily through these two models:

/// Engine-derived availability for the actions one unit can currently request.
struct BattleActionAvailability: Hashable, Equatable {
    /// Unit whose available actions were evaluated.
    let actor: UnitID
    /// Whether the unit can commit movement.
    let movement: Status
    /// Whether the unit can commit an attack.
    let attack: Status
    /// Availability for every card in the unit's current hand.
    let cardStatuses: [CardStatus]
    /// Whether the unit can explicitly end its turn.
    let endTurn: Status
}

/// Whether one action can currently be requested.
enum Status: Hashable, Equatable {
    /// The action can be requested.
    case available
    /// The action is blocked for a specific rules reason.
    case unavailable(BlockReason)

    /// Whether the action can currently be requested.
    var isAvailable: Bool {
        self == .available
    }
}

The battle engine provides an BattleActionAvailability for a given unit, and is the source of truth for all actions a user can take on a given turn. So Godot can ask, "Can this unit move?" or "Can this card be played?" and receive either .available or a structured reason that it cannot happen, such as insufficient energy, no legal targets, or the unit having already attacked.

Movement now also has a proper preview-and-confirm flow. The first click asks the engine for an exact movement preview, including the path, cost, final position, and facing. Clicking the previewed destination again submits that original request for revalidation and commits the move. It doesn't yet actually move the 3D model across the board, but I'm getting there!

I also split the automated enemy turn into individual decisions. Instead of the autopilot consuming an entire turn in one invisible lump, it produces one draw, move, attack, or end-turn decision at a time. Right now those steps still resolve immediately, but later the presentation layer will be able to animate each one without having to reverse-engineer what the rules engine just did.

The Scene is Set... programmatically

Each unit is loaded from the authoritative Swift battle state, matched to the correct 3D chassis, painted for its team, placed on its authored hex, and rotated to face the correct direction. It was a huge inflection point getting that to work. The result is not only four visible mechs, but four mechs that can be reliably refreshed from the current battle state without causing reality to collapse. Progress!

An Actual Battle HUD

The latest phase of work has been building the battle HUD. Man oh man this has been satisfying.

I started with a separate Godot scene containing anchored regions for the initiative strip, active-unit information, target inspector, feedback, action buttons, card hand, weapon choices, and battle outcome. That shell now displays live battle information.

The initiative strip (along the top of the screen) lists every living unit in turn order, marks the active unit, and dims units that have already acted during the current round. The active-unit panel displays the pilot and mech names, health, energy, heat, and remaining actions.

Hovering over the board populates the target inspector (top right) with the hex coordinate and terrain information. If a living unit occupies that hex, the inspector displays its pilot, mech, team, and current stats instead.

The local action controls (along the bottom) also disappear during an enemy turn. This sounds small, but it is one of those details that begins turning a debug screen into an interface meant for a human being.

As I write this, I'm in the middle of populating the card hand. Cool schtuff.

Hey, that kinda looks like a vidja game

The cards do not actually perform their actions yet, when clicked. That's the next part.

For the moment, I'm just allowing myself to enjoy the deeply satisfying experience of seeing actual game data appear inside something that looks like a game interface.

The Less Photogenic Work

Not everything I've worked on makes a good screenshot.

I added project-specific Swift formatting and linting tools so the growing codebase stays consistent. I repaired the build after upgrading to Xcode 27 changed which Swift compiler was being used. I created lore reference documents for the setting and technology so that ideas stop disappearing into old conversations. I also improved my roadmap dashboard so progress is based on estimated checkpoints instead of pretending every giant development stage is the same size.

None of that produces a cool GIF, but all of it reduces the number of exciting new ways Future Me can become angry with Past Me... and maybe adds a few.

Next Up

The next step is to connect the Move, Attack, card, and End Turn controls to the engine-owned action availability.

After that comes the proper selection state machine: movement targets, enemy targets, weapon choices, ally card targets, cancellation, stale-highlight cleanup, battle outcomes, and contextual guidance.

Then I can finally move on to animation, effects, audio, camera emphasis, and all the other things that make correct game state feel like something actually happened.

The end-of-September deadline I gave myself is now hilariously close. I don't know exactly how much of the remaining plan I will finish by then. My little private roadmap manager site that I had gippity build for me says the prototype is 71% finished (that's a really specific number, hahaha), but who knows. Humans are terrible at estimating, and so far I'm inclined to think AI is even worse.

Nonetheless, the project is no longer a large pile of domain models wearing a trench coat and insisting that it is a video game.

There are four robots on a battlefield. They take turns. They move, shoot, play cards, and appear in a real interface.

I'll take it.

Next up: making the buttons do things. Revolutionary. Please, stay calm.

← Back to writing