Proposal: builtin Shooter controller — top-down rotatable movement, weapons registry, projectiles/bullet-hell (base + extensible) #60
Labels
No labels
area:ci
area:docs
area:input
area:net
area:rendering
area:repo
area:stdlib
area:tooling
area:types
cleanup
dx
priority:high
priority:low
priority:medium
proposal
status:in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: workshopsoft/ludic#60
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of the builtin-controllers layer (#57). Conforms to the six-lever extensibility contract. Planning only — no implementation.
Context
A top-down shooter controller: a character that moves independently of where it aims (twin-stick / mouse-aim), a rotatable body, and a projectile system general enough for bullets, lasers, grenades, homing missiles, and bullet-hell patterns. Modeled on twin-stick classics (Nuclear Throne, Enter the Gungeon), Godot top-down demos, and bullet-hell frameworks (Danmakufu-style pattern emitters). Reuses #57 collision + faction; feeds directly into NPC AI (#NPCAI) enemies and RPG (#RPG) real-time combat.
Layered design
Layer 1 — the top-down mover + aim (data-first)
Movement and facing are decoupled — the defining trait of the genre.
aim_modecovers the real control schemes as data: mouse-aim, right-stick, aim-where-you-move, and auto-aim (nearest enemy viaQuery.nearest) — the last one reused constantly by AI-controlled shooters.turn_rate0 = instant (arcade), >0 = gradual (usesAngle.*shortest-arc rotation) for heavier feel.esys_topdown_move,esys_topdown_aim(only the activeaim_modebranch runs),esys_topdown_facing→sprite/rotation.Layer 2 — weapons (data-driven registry)
A weapon is data, not a subclass — a registry entry like
Anim.clip, so games/mods add guns with zero code:kind∈ {projectile, hitscan/beam, melee-arc}.patternselects an emitter (single, spread, ring, arc, spiral) — apolicyfor bullet-hell.esys_weaponhandles fire-rateCooldown, ammo, reload, and burst/auto/charge fire modes; reads a"fire"input action (or an AI intent flag, so the same system drives player and NPC guns).Layer 3 — projectiles (the reusable core)
esys_projectile(phase Update): integrate, TTL countdown, collision vs.Colliderlayers/faction, pierce/bounce counters, homing steer (viaAngle.*/Query.nearest). Pooled/dense so thousands of bullets stay cache-friendly and deterministic (bullet-hell needs this).Areatrigger +Query.withinon hit.cancellable ProjectileSpawned(mutable — modifiers/buffs adjust damage/count here),ProjectileHit {proj, target},cancellable DamageAboutToApply(shared with #57),ProjectileExpired,Exploded.Layer 4 — combat scaffolding (opt-in)
Life/Statsfrom #57;Death/Hit/Healevents; hit-flash + knockback via events.Query.nearest(faction, pos)for auto-aim and AI.Pickup+ RPGInventoryif present (weapons as items).Camera.follow+ aim-lookahead (offset toward cursor) +Camera.shakeon fire/hit (all exist inCamera.*).property Spawner { pattern, rate, budget }+WaveStarted/Clearedevents (shared with NPC AI #NPCAI — arena/horde modes).How a developer extends each area
Weapon.def(...)— no codepatternemitter value, or@On(ProjectileSpawned)to spawn extras@On(DamageAboutToApply)mutate amount;@On(ProjectileHit)homing/bounce/piercefields; deeper = own handler@After(esys_projectile)disable system esys_weapon, own firing handler; keep projectile systemaim_mode: 3(nearest-enemy)turn_rate > 0,strafe: 0@On(WaveCleared)/ driveSpawnerDeterminism / Ludic ties
Random.stream+ input replay).world_save/snapshot captures in-flight bullets → rollback netcode for co-op shooters.Angle.*(auto-wrap),Vector/Query.nearest/Query.within,Camera.*,Cooldownall already exist.Weapon/Projectilesystems serve player and NPC (AI writes the"fire"intent instead of input) — no duplicate enemy-gun code.Phasing
TopDownmover +aim_mode(mouse/stick/move-dir) + facing/rotation. Depends on #57 collision.Weaponregistry +esys_weapon(fire rate/ammo/reload/burst) +Projectile+esys_projectile(single-shot, TTL, collision, damage) + core events.examples/games/shooter.ludic(twin-stick arena) forkable template.References
bevy_rapier-style projectile pooling; SDL/GodotArea2Doverlap for AoE.Amendment — ships as an external package
Per the decision on #57, this controller ships as an external, versioned source package (Ludic modules, compiled into the consumer's binary), not as compiler/
runtime/nativestdlib and not as a precompiled OS binary. Rationale (wasm target, determinism, compile-time ECS) and the binary-mod escape hatch are on #57.Depends on #62 (package distribution + package-declarable namespaces/components/engine-systems) — the mechanism that lets this register its component schema and engine-owned systems without a compiler edit. The six-lever extensibility contract it conforms to stays in the language core.
Shipped —
ludic.shooter(commit02a8bcc)A top-down shooter source package on the six-lever contract (#57), built on the engine
Body/Colliderand theludic.gameplayFaction/Combat/Stats foundation.TopDowndecouples movement from aim —aim_mode: 0 mouse / 1 right-stick / 2 move-direction / 3 nearest-enemy auto-aim, with aturn_ratefor tank-style gradual rotation (lever 1/6).Weapon.def("shotgun", fire_rate, damage, speed, spread, pellets, pattern)— add a gun with zero code — plus data-drivenWeapon.set_pierce/Weapon.set_homing. Patterns: single / spread cone / ring / spiral (bullet-hell).esys_weaponreads awant_fireintent, so the same weapon fires for a player (input) and an NPC (#61 AI sets the flag) — no duplicate enemy-gun code.Projectile+esys_projectileis a self-contained deterministic pool: integrate, TTL, faction-filtered hit throughCombat.damage, pierce, and homing that curves onto the nearest enemy. Every step is observable:cancellable/mutable ProjectileSpawned,ProjectileHit,ProjectileExpired.Spawner— a budgeted, throttled wave director emittingSpawnRequested/WaveCleared(shared with NPC-AI arenas).Fixed-point + fixed listener order → bullet patterns replay exactly. Example
examples/games/shooter_demo.ludic— 11 self-checks: movement, aim, faction damage, friendly-fire immunity, spread, kill via Combat, ring, homing, wave spawner — a regression case inx test. Docs:docs/CONTROLLERS.md.Closing as done.