Unit command
Real-time strategy and tactics units: click, box and control-group selection, move, attack-move, patrol, hold, gather, carry and build orders, formations, a shared walk grid, rally points and two hooks to change orders.
Selectable units that take orders (units.command) #
What it is for #
units.command is the core of a real-time strategy or tactics game. A unit is any entity that carries the component commandable (faction, stats, order queue, what it carries); a building, or anything else the player may select, carries selectable { faction, radius }. All the state of a unit lives in its components, so a save holds it and a load carries on exactly. The local player's faction selects (click, drag box, control groups) and orders (move, attack, gather, build...). The kit draws nothing itself: marks() tells every renderer what to mark (a ring under each selected unit or building, a health bar over each selected or damaged one). It leaves the models, the animation and the camera to the engine, and the rules of production, economy and opponents to the kits built on top of it.
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "units.command" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "units.command",
config: { "faction": "blue", "spacing": 2.8 } }
The kit needs game.engine, combat.health, combat.damage and input.actions. Models come from the asset slot rts_unit (one per unit type, chosen by the variant).
First example #
<!-- test: kits/game_engine/host/tests/parity.rs::rust_host_matches_the_typescript_traces --> The fixture units.command.orders runs it: with faction set to blue, units are spawned, selected, then ordered. A spec needs a faction and a type; a missing one answers null instead of an id.
{ "op": "call", "service": "units.command", "method": "spawn",
"args": [{ "faction": "blue", "type": "soldier", "health": 100, "speed": 4,
"attack": { "damage": 10, "range": 1.5, "cooldown": 1 } }, [0.2, 0.1]] }
{ "op": "call", "service": "units.command", "method": "order",
"args": [["e1", "e2", "e3"], { "type": "move", "point": [14.3, 12.7] }] }
The three units walk to the point in formation. Adding { "queue": true } as the third argument puts the order after the ones they already have.
How it plays #
- Select: left click or tap on one of your units or buildings; Shift adds or removes. Drag a box to take every unit inside. Ctrl with a digit 1 to 5 sets a control group, the digit selects it. Z selects the whole army (workers left out). A click on an enemy shows it without ordering anything.
- Order: right click, or tap with units selected. On an enemy: attack. On a resource node: workers gather. On one of your construction sites: workers build. On a drop-off: a loaded worker carries back. Elsewhere: move in formation. Shift queues. M, A and P arm move, attack-move and patrol for the next click (Escape disarms); S stops, H holds position. With buildings selected, a right click sets their rally point.
- Mini-map: a left click moves the camera, a right click orders the selection there.
Settings and defaults #
| Setting | Type | Default | Meaning |
|---|---|---|---|
faction | text | empty | the faction the local player commands (empty: the team of the player entity, or what a game kit sets) |
cellSize | number | 1 | metres per cell of the walk grid (0.5 to 4) |
pickRadius | number | 1.2 | how far from a unit, beyond its own radius, a click still selects it (0 to 6) |
spacing | number | 2.4 | formation spacing in unit radii (1.5 to 6) |
corpseSeconds | number | 1.5 | a dead unit lies this long before it is removed (0 to 30) |
A unit spec (spawn) takes faction, type, name, health, speed, radius (default 0.45, at least 0.2), sight (default 9), attack { damage, range, cooldown, windup?, projectileSpeed? }, worker { carry, seconds }, passive, supply, tags, slot, variant, scale and color (a mini-map marker).
Services, events, variables #
units.command(service):- units:
player(),setPlayer(faction),spawn(spec, at, yaw?)(answers the id),units(faction?),unit(id),factionOf(id),hostile(a, b). - selection:
selection(),select(ids, { add }),pick(point),selectAt(point, { add }),selectBox(corners, { add }),selectArmy(),clearSelection(),group(n),setGroup(n, ids?),recallGroup(n),marks(). - orders:
order(ids, order, { queue })with an order{ type, point?, target? }wheretypeismove,attack_move,attack,hold,patrol,gather,returnorbuild;command(point, target?, { queue, ids, type })(what a click does);stop(ids?),hold(ids?);arm(mode),armed(),onArmed(mode, handler)(a placement kit takes the next click),removeArmed(id). - walk grid:
blocked(x, z),clear(from, to),footprintFree(at, size, yaw?),nearestOpen(at),setBounds(min, max),bounds(),stats().
- units:
- Events:
units.spawned,units.selected{ ids, faction },units.ordered{ ids, type, point?, target?, queued },units.rally{ ids, point, target },units.group{ group, ids },units.idle,units.deposited{ unit, faction, item, count },units.lost. Listens tocombat.damaged,combat.killedandminimap.clicked. - Variables:
units.selected(how many are selected) andunits.armed(the order waiting for a click). - Input actions:
units.select,units.order,units.queue,units.group_mod, one group action per digit 1 to 5,units.cmd_move,units.cmd_attack,units.cmd_patrol,units.cmd_stop,units.cmd_hold,units.select_army,units.cancel; the player can remap them.
Rules #
- Walking: the ground is a grid. Entities tagged
blocks_pathwith asizeblock the cells under their footprint. A unit walks straight when the line to its goal is clear, else down a flow field computed once per goal and shared by the group. Units push each other apart, slide along walls and never enter a blocked cell. - Fighting: an idle unit with an attack takes on the nearest hostile it sees (fighters, then workers, then buildings) and goes home once it is too far. Attack-move and patrol fight on the way, hold only strikes what is in reach, a struck idle unit strikes back. Strikes go through
combat.attackswhen installed, else straight tocombat.damage. Factions are hostile unlessfaction.relationssays otherwise. - Gathering: a worker walks to a
resource.nodesnode, works for itsseconds, carries the load to the nearestdropoffof its faction, banks it witheconomy.stockpilewhen a kit serves it, and goes back. - Building: a worker next to an entity with
construction { progress, seconds }raises its progress; several build faster. The kit that placed the site finishes it. - Death: a unit killed through combat plays its death and is removed after
corpseSeconds.
Hooks and modding #
- Hook
unit.order: payload{ ids, unit, count, faction, type, point?, target?, queued }, asked once per order given to a group (player, script or AI) before any unit takes it.typeis mutable betweenmove,attack_moveandpatrolwhen the order goes to a point,queuedis mutable, and cancelling gives no order and nounits.ordered. A hold asks it too; a stop does not. - Hook
unit.rally: payload{ ids, building, count, faction, point, target? }, asked before the selected buildings get a rally point; cancelling leaves their rally points as they were. - A mod may change
pickRadius,spacingandcorpseSeconds.
{ "id": "march_fights", "hook": "unit.order", "match": { "type": "move" }, "set": { "type": "attack_move" } }
Engines #
The kit runs on the web and in the native Rust host. The web page draws the drag box, a ring and a health bar under each selected thing, and health bars over damaged units, as a ui.webui fragment.
When it does not do what you expect #
- A click selects nothing: the unit's
factionis not the one infaction(an enemy is shown but not selected), or the click is farther thanpickRadius. spawnanswersnull: the spec has nofactionor notype, or the position is not an array of numbers.- Units do not walk around a building: it lacks the tag
blocks_pathor asize. - An order does nothing: a hook cancelled it, or none of the ids is a living unit of the player.
- A worker does not gather: no
resource.nodesnode of its item, or nodropofffor its faction.
Limits #
Targets are the web and the native Rust host only. The kit does not train units, spend money or play an opponent: those belong to the kits that build on it.
Versions #
Kit 1.3.0 (adds marks()). Service units.command 1.0.0.