Make a Top-Down Shooter With Safe Projectile Hits
Build Signal Sweep: normalised movement, rate-limited shots, enemy collisions and a finite wave.

Thumbnail: an actual matching workshop game. It is a gameplay reference, not evidence that a save, inventory, network or native reverse-engineering integration has been demonstrated.
Difficulty: beginner to intermediate. Prerequisites: basic variables, functions and a test copy of your project. Reference: Forge workshop 1.0, browser ES modules and Node 24 checks. Engine-specific translation is a separate integration.
What you will build
Survive one twelve-enemy wave with three health and rate-limited fire.
Play Signal Sweep →Download complete MIT workshop- Extract into a new folder and run
node check.mjsfrom that folder. - For the browser games, run
python3 -m http.server 8080with Python 3 and openhttp://localhost:8080/index.html. Do not open modules with file://. - Read the full setup instructions and the original code licence. Keep your existing game on a separate test branch.
The JavaScript behaviour checks and native C comparison can run independently of the browser. Ghidra UI, physical-phone and your engine integration are separate checks. This is a small reference, not a certification of your project.
Explore the related game idea · Start with Build a Game · Improve your existing game
Five practical steps
Prove movement before shooting
Choose Top-down shooter and Start. Use arrows/WASD or direction buttons. Divide the input vector by its length so diagonal movement matches horizontal speed. Clamp the player to the arena. The artwork is intentionally geometric while logic is tested.
Expected result: Movement is equally fast in straight and diagonal directions.
Check it: Compare one second right with one second diagonal; total distance should be approximately equal.
stepObserve the inputs → check the state → compare the resultGive shots a cooldown
Hold Shoot or Space to fire. The simulation owns the 0.22-second cooldown and each projectile position. Movement and firing use elapsed simulation time; a 120Hz display must not double the fire rate. Avoid one independent interval per projectile.
Expected result: A bounded bullet stream moving upward.
Check it: Hold fire for a fixed interval at two update schedules and compare the shot count.
Consume hits exactly once
Mark a bullet and enemy dead when they collide. The next collision checks ignore dead objects; clean arrays after resolution. Removing array entries while iterating can skip neighbours, and a still-live bullet can wrongly kill several enemies in one frame.
Expected result: One projectile causes one accepted hit.
Check it: Put two enemies at the same bullet location; only one is defeated by that bullet.
Bound damage and the wave
Contact starts one second of invulnerability; an escaped enemy also costs health. Twelve finite spawns lead to a win when the wave clears or a loss at zero health. The example intentionally keeps escapes separate from contact invulnerability.
Expected result: A finite game with readable health and outcome.
Check it: Test overlapping enemies, an escape during invulnerability, zero health and no enemies between spawn intervals.
Upgrade one verified system
Add a second firing direction, one pickup or clearer hit feedback. Keep simulation decisions independent from particles. Faster projectiles can tunnel through targets; switch to swept tests or your engine physics before raising speed beyond what this fixed-step radius test can handle.
Expected result: A playable original wave shooter and one controlled extension.
Check it: Retest diagonal speed, pause, repeated restart, hidden-tab resume and keyboard/touch after the upgrade.
Read and reuse the actual code
shooter.mjs: complete source · Standalone behavioural checks · Shared browser runner and input. Original BINX Forge code, MIT; the package includes every required file and its notice.
Inspect complete shooter.mjs
export function create(){return {x:320,y:280,bullets:[],enemies:[],spawn:0,cool:0,remaining:12,kills:0,hp:3,invulnerable:0,ended:false,won:false};}
export function step(s,i,dt){
if(s.ended)return;
let dx=Number(!!i.right)-Number(!!i.left),dy=Number(!!i.down)-Number(!!i.up),len=Math.hypot(dx,dy)||1;
s.x=Math.max(12,Math.min(628,s.x+dx/len*190*dt));s.y=Math.max(12,Math.min(348,s.y+dy/len*190*dt));s.cool-=dt;s.spawn-=dt;s.invulnerable=Math.max(0,s.invulnerable-dt);
if(i.fire&&s.cool<=0){s.bullets.push({x:s.x,y:s.y,dead:false});s.cool=.22;}
if(s.remaining>0&&s.spawn<=0){s.enemies.push({x:80+(12-s.remaining)%5*110,y:-15,dead:false});s.remaining--;s.spawn=.8;}
for(const b of s.bullets)b.y-=360*dt;
for(const e of s.enemies){e.y+=75*dt;for(const b of s.bullets)if(!b.dead&&!e.dead&&Math.hypot(e.x-b.x,e.y-b.y)<16){b.dead=true;e.dead=true;s.kills++;}if(!e.dead&&Math.hypot(e.x-s.x,e.y-s.y)<22){e.dead=true;if(s.invulnerable<=0){s.hp--;s.invulnerable=1;}}else if(!e.dead&&e.y>380){e.dead=true;s.hp--;}}
s.bullets=s.bullets.filter(b=>!b.dead&&b.y>-20);s.enemies=s.enemies.filter(e=>!e.dead);
if(s.hp<=0||(!s.remaining&&!s.enemies.length)){s.ended=true;s.won=s.hp>0;}
}
Code rights: the included MIT notice permits use, modification and distribution, including commercial games and source products, with the copyright and licence notice. Added third-party files have their own terms. Read the official licence & usage terms.
Common failures and fixes
One bullet kills a whole group
Consume the bullet on the first accepted hit and skip dead bullets thereafter.
Bullets pass through enemies
The radius test is discrete; bound speed or use swept collision when travel per step grows.
Diagonal movement is faster
Normalise the direction vector before multiplying by speed.
Make Your Game Better
Change one system after the baseline works. Keep the free reference and compare the same inputs before and after.
Add an aimed weapon
Reuse one input-action map and measure projectile travel against target size.
Add bounded effects
Pool only after measuring allocations; visual effects must never decide health or score.
Optional building blocks from original creators

Godot Engine contributors · Original screenshot ↗ · CC BY 4.0 ↗. Resized and compressed.
Godot Engine contributorsGodot
Free, open-source game editor for 2D and 3D projects, with scene, animation and interface tools.

UI Pack
Interface artwork for buttons, panels and sliders.
Inspect exact formats and editions on each resource page. Pay for a resource only when it fills an identified gap; the free code exercise needs no paid engine or pack.
Licensing summary, not legal advice. Before publishing or selling, check exact terms for finished-game use, reselling files and including files in a source/template product separately.
Learn at the original sources
Sources checked 11 October 2026. The numbered plan is original Forge instruction; linked documentation does not imply every engine or device has been tested.
Take this guide to your AI
Review and copy into your assistant. Nothing is sent automatically. These prompts include the complete focus source, full-package links, checks and compatibility limits.
Review a prompt or select its text manually.