2D shooting: guns as data
Arcwright's 2D shooter kit (lane 2d/shoot) gives any 2D object guns that aim, fire, kick, eject shells and hit with the same one-hit pipeline as melee (action_2d: damage once, hit-stop, flash, knockback). It works side-on (platformers, run-and-gun) and top-down (twin-stick arenas). Everything is data, so "give the hero a shotgun that knocks enemies back" is one line.
Give something a gun
entity Vex #2 {
body_2d { kind "player" ... }
action_2d { team "player" health 12 }
weapon_2d { weapon "shotgun" carried ["pistol", "shotgun"] tune ["knockback=1100"] }
}
Or ask the AI: weapon_2d {entity: "Vex", weapon: "shotgun", tune: ["knockback=1100"]}.
| weapon_2d field | meaning |
|---|---|
weapon, carried | in hand, and everything carried (Q / F / pad Y switch, 1-6 pick a slot) |
ammo | spare rounds ["shotgun=24", "all=200"] (unlisted: the weapon's own reserve, -1 endless) |
tune | this object's weapon changes: ["knockback=900", "shotgun.pellets=12", "auto=true"] |
control | auto (players aim with the pointer or right stick, others wait for their script), player, script |
aim_assist | 0...1: the right stick bends toward the closest enemy in a cone |
shoulder, arm, arm_length, arm_rect | where the arm turns, its picture (pointing right), the grip distance, where the picture lies |
gun_scale, gun_layer, draw_gun, face_aim, crosshair, aim_lead | drawing, facing, the crosshair, the camera's lean toward the aim |
pickup, pickup_ammo, pickup_radius | a gun on the ground; weapon "ammo" refills what the player carries |
Players: left mouse / J / the pad's right trigger fire, R reloads (empty guns reload on their own).
Weapons
Built-ins: pistol, smg, shotgun (8 pellets, spread, big push), rifle (hitscan, pierces 2), rocket (explosive, radial damage), laser (beam, pierces, bounces once), orb (slow glowing enemy bullets for patterns), turret, thug_pistol. weapon_2d_presets lists them and every setting.
A project weapon is Assets/Weapons/<name>.weapon.json: a base plus settings. A file named like a built-in re-skins it for that project (the Sandbox gives the pistol its Paper Forge picture this way):
{ "base": "shotgun", "pellets": 12, "knockback": 1400, "color": [1, 0.4, 0.2],
"image": "2D/paperforge/neon_shotgun/neon_shotgun@4x.png", "size": [70, 24], "grip": [0.343, 0.5], "muzzle": [0.971, 0.717] }
Settings: mode (projectile / hitscan), fire_rate, auto, damage, pellets, spread, bloom, bloom_max, speed_jitter, recoil, kick, camera_kick, shake, shake_time, self_push, magazine, reload, reserve, bullet_speed, drag, gravity, range, bullet_size, color, pierce, bounce, knockback, knock_up, hit_stop, hit_stop_max, hit_shake, hit_flash, explosion_radius, explosion_damage, explosion_knockback, muzzle_fx, impact_fx, flesh_fx, explosion_fx, flash, flash_color, light, casings, casing_color, casing_size, tracer_width, tracer_time, beam, sound, pitch, volume, reload_sound, empty_sound, image, size, grip, muzzle, body_color, telegraph, label.
Gunfeel, out of the box
Muzzle flash with a light pulse, the gun kicking up and back (recovering), the camera pushed back and shaken per weapon, spread bloom on quick fire, shells thrown out with physics (bouncing on the street, or with a fake height top-down), tracers and beams, bullets that are readable (streaks for fast rounds, dark-rimmed glowing orbs for enemy bullets), sparks and bullet holes per surface (tags metal, wood), pellets that land together as one heavy hit (one hit-stop), hit markers (red on a kill), explosions with a ring, flash, smoke, scorch and a short world stop. Effects are named recipes (muzzle_fx, impact_fx, ...) played through Shoot2D.fx, so the effects library can replace any of them; game feel settings (effects off) never change hits.
Enemies with guns (scripts)
self.aimAt(game.player, 400) // turn at 400 deg/s
if (self.weapon.ready) self.shoot({ burst: 3 }) // telegraphs first (laser sight + glint) when the weapon has telegraph
self.pattern('spiral', { count: 3, interval: 0.08, duration: 2.5, turn: 150, speed: 380 })
self.pattern('ring', { count: 20, bursts: 3 }) // also 'spread', 'aimed', 'wave'
game.explode([x, y], 180, 5, { team: '' }) // barrels: hurts everyone
world.lineOfSight(self.weapon.muzzle, game.player)
Kits in the Sandbox: kits/gun_thug.js (telegraphs, takes cover behind objects tagged cover, strafes; topdown=1 circles), kits/gun_turret.js, kits/gun_boss.js (three pattern phases and a HUD bar), kits/gun_barrel.js, kits/neon_hero.js.
Check it
shoot_2d_report {seconds: 10} plays the scene on a copy with a bot that picks up guns, aims and fires, and reports shots, hits, kills, accuracy, damage by weapon and script errors. TTRHeadless --shoot2d <project> <scene> <out> captures frames of the same bot playing. Scenes: Stress2D_Neon_Alley (side-on), Stress2D_Neon_Arena (top-down).