Weapons have finite ammunition tracked separately from weapon ownership; running dry forces behavioral change.
Firing depletes a finite ammo pool tracked separately from the weapon; running dry forces a reload.
Ammo is bookkeeping kept separate from weapon ownership: a per-type count that firing drains and pickups refill. Running dry forces a switch.
Store ammo by type, not by weapon — many guns can share “bullets.” Keyed lookups make refills and HUD reads trivial.
var ammo := { "bullets": 50, "shells": 12, "cells": 0 }
func has_ammo(type): return ammo.get(type, 0) > 0Firing decrements and emits ammo_changed; at zero, block the shot and signal empty so the player feels the dry-click.
signal ammo_changed(type, n)
func fire(type):
if ammo[type] <= 0: empty.emit(); return
ammo[type] -= 1; ammo_changed.emit(type, ammo[type])Pickups are Area2Ds that add to the count on overlap, then free themselves — the refill side of the loop.
func _on_body_entered(body):
GameState.ammo["shells"] += 8
queue_free()In short: ammo dict in GameState keyed by weapon type; decrement on fire; pickup items increment
33 catalogued game(s) use this mechanic, spanning 1986–1999.
▶ Explore Ammo Management interactively — see every game + the Godot system