references/shooting-and-feel.md
# FPS shooting model and feel (depth)
Detail behind `SKILL.md`. Engine-neutral pseudocode. For the camera/character body and the
raycast/physics APIs, defer to your engine 3D + physics skill; this file is the shooter glue.
## 1. Hitscan vs. projectile
| | Hitscan | Projectile |
|---|---|---|
| Travel | Instant ray to target | Object flies over time |
| Player skill | Pure aim/flick | Leading, prediction, arcs |
| Feel | Crisp, precise (rifles, pistols) | Dodgeable, readable (rockets, plasma) |
| Cost | One raycast per shot | Spawn + per-frame move + collision |
| Range design | Add damage falloff to limit reach | Speed/lifetime limit reach naturally |
| Counterplay | Cover, peeking | Dodging, sidestepping |
Many shooters mix both: hitscan for hip-fire/automatics, projectiles for heavies and lobs.
Slow projectiles enable big readable fights; hitscan rewards precision but can feel "unfair"
at long range without falloff.
## 2. Hitscan shot
```python
# Pseudocode. Cast from the camera along its forward vector; the first hit takes damage.
origin = camera.world_position
direction = camera.forward
direction = apply_spread(direction, current_spread) # see §4
hit = raycast(origin, direction, max_dist=RANGE, mask=SHOOTABLE)
if hit:
dmg = base_damage * falloff(hit.distance) # see §3
if hit.is_head: dmg *= HEADSHOT_MULT # e.g. 2.0
hit.actor.take_damage(dmg)
spawn_impact_fx(hit.point, hit.normal) # decal + particles = feedback
```
## 3. Damage falloff (range limiting for hitscan)
```python
# Full damage up to a near distance, then lerp down to a floor by a far distance.
def falloff(d, near=20.0, far=60.0, min_mult=0.4):
if d <= near: return 1.0
if d >= far: return min_mult
t = (d - near) / (far - near)
return lerp(1.0, min_mult, t)
```
## 4. Spread and recoil
- **Spread** is randomized inaccuracy (a cone). It grows while firing/moving and shrinks while
still/aiming. Good for automatics; keep the first shot accurate so single taps feel precise.
- **Recoil** is a (often patterned) camera kick. A *fixed* pattern is learnable and skill-based;
pure random recoil feels bad. Recover toward the original aim over time.
```python
# Spread: random offset within a cone whose half-angle = current_spread.
def apply_spread(dir, spread_rad):
yaw = rng.range(-spread_rad, spread_rad)
pitch = rng.range(-spread_rad, spread_rad)
return rotate(dir, yaw, pitch)
# On each shot: grow spread and kick the camera; recover every frame.
def on_fire():
camera.pitch -= recoil_pattern[shot_index % len(recoil_pattern)] # learnable kick
current_spread = min(current_spread + SPREAD_PER_SHOT, MAX_SPREAD)
def tick(dt):
current_spread = max(BASE_SPREAD, current_spread - SPREAD_RECOVER * dt)
camera.pitch = move_toward(camera.pitch, aim_pitch, RECOIL_RECOVER * dt)
```
## 5. Time-to-kill (TTK) — the master balance lever
```
shots_to_kill = ceil(target_hp / damage_per_shot)
TTK_seconds = (shots_to_kill - 1) / fire_rate_per_second # first shot at t=0
```
- **Low TTK** (fast kills): positioning/first-shot advantage dominate; twitchy, less forgiving.
- **High TTK**: tracking/sustained aim and team play matter; more time to react and reposition.
- Tune `damage_per_shot`, `fire_rate`, and `target_hp` together — they are one system. Headshot
multipliers create a skill-based *faster* TTK without lowering the body TTK.
## 6. Hit registration and (multiplayer) lag
- **Single-player:** raycast on the same frame as input; no networking concerns.
- **Multiplayer:** the shooter and the target see slightly different worlds. Common server-side
approach is **lag compensation** — the server rewinds other actors to where the shooter saw
them (using the shooter's timestamp/ping) before testing the hit. This is hard; for networking
plumbing defer to the engine multiplayer skill and keep authority on the server.
## 7. Enemy AI states (single-player)
A small state machine covers most PvE shooters:
```
IDLE/PATROL --(sees player)--> ALERT --(in range)--> ATTACK
ATTACK --(lost line of sight)--> SEARCH(last known pos) --(timeout)--> PATROL
any --(took damage from unseen)--> ALERT (turn toward source)
```
Use cover queries, burst-fire with reload pauses, and a reaction delay so enemies feel fair.
Implement the tree/FSM with `game-ai` and the engine's navigation (`unity-navmesh`,
`unreal-behavior-trees`, Godot navigation).
SKILL.md
---
name: fps-shooter
description: >
Build a first-person shooter: move+mouse-look controller, hitscan or projectile shooting, weapons,
health, and enemy AI. Use for an FPS, or tuning aim feel, time-to-kill, recoil, or spread.
---
# FPS Shooter
A playbook for first-person shooters — the look/move controller, the shooting model, weapon
feel, and combat. This is a **compositional** skill: it wires a 3D controller, input, and AI
into a shooter. It does not re-teach 3D nodes or raycasts; it defines the shooting model and
the feel knobs (TTK, recoil, spread) that decide whether the guns feel good.
## When to use
- Use when building a first-person game whose core verb is **aim and shoot** — arena shooter,
tactical FPS, PvE shooter, boomer-shooter.
- Use when deciding hitscan vs. projectile, tuning time-to-kill, recoil, spread, or aim feel.
**When *not* to use:** third-person/2D shooting → reuse the shooting model here but build the
camera/controller from the relevant genre. Wave survival with towers → `tower-defense`.
For the camera/character body itself, use `godot-3d-essentials` / `unreal-cpp-gameplay`.
## Core loop
**Scan → acquire a target → aim and fire → confirm the kill (feedback) → reposition / reload
/ advance.** The whole experience rests on the *aim-and-fire* micro-loop feeling crisp:
responsive look, clear hit feedback, and a death that reads instantly.
## Must-have systems
1. **First-person controller** — move (WASD/stick) + mouse/stick look, gravity, jump/crouch.
2. **Camera** — eye-height view, configurable FOV and sensitivity, recoil kick.
3. **Shooting model** — hitscan raycast and/or projectile spawn; one impact path for feedback.
4. **Weapons + ammo** — damage, fire rate, magazine, reload, switching.
5. **Health + damage** — HP, hit/headshot multipliers, death; player and enemy share the model.
6. **Enemy AI** — perceive → alert → attack → search; cover and reaction delays (`game-ai`).
7. **Feedback** — hitmarkers, impact decals/particles, hit sounds, screen shake, kill confirms.
8. **Objectives** — what you do besides shoot: clear, capture, survive, escort.
## Design knobs
| Knob | Effect | Sane default |
|------|--------|--------------|
| Time-to-kill (TTK) | lethality, forgiveness | Tune dmg × fire-rate × HP together (refs). |
| Hitscan vs projectile | aim skill type | Hitscan = flick; projectile = lead/dodge. |
| Damage falloff | range limiting | Full to ~20 m, floor by ~60 m. |
| Headshot multiplier | skill reward | ~1.5–2.0×. |
| Recoil pattern | learnable kick | Fixed pattern > pure random. |
| Spread (bloom) | suppress laser-accuracy | First shot accurate; grows while firing. |
| Fire rate / magazine / reload | rhythm, downtime | Reload = vulnerability window. |
| Mouse sensitivity / FOV | comfort, readability | Always expose both as options. |
| Aim assist (pad) | controller parity | Magnetism/slowdown near targets. |
## Patterns
### 1. Hitscan shot (instant ray, the workhorse)
```python
# Pseudocode. Cast from the camera; first hit takes damage scaled by range + headshot.
direction = apply_spread(camera.forward, current_spread)
hit = raycast(camera.world_position, direction, max_dist=RANGE, mask=SHOOTABLE)
if hit:
dmg = base_damage * falloff(hit.distance)
if hit.is_head: dmg *= HEADSHOT_MULT
hit.actor.take_damage(dmg)
spawn_impact_fx(hit.point, hit.normal) # decal + sound + hitmarker
```
### 2. Projectile shot (dodgeable, leads the target)
```python
# Pseudocode. Spawn a moving body; it deals damage on its own collision.
p = spawn(projectile_scene, at=muzzle.world_position)
p.velocity = camera.forward * PROJECTILE_SPEED
p.on_hit = lambda other, point: (other.take_damage(base_damage), explode_fx(point))
p.lifetime = RANGE / PROJECTILE_SPEED # despawn so shots don't live forever
```
### 3. Time-to-kill (balance the trio together)
```python
# Pseudocode. TTK falls out of HP, per-shot damage, and fire rate — tune as one system.
shots_to_kill = ceil(target_hp / damage_per_shot)
ttk_seconds = (shots_to_kill - 1) / fire_rate_per_second # first shot at t=0
```
## Pitfalls / failure modes
- **Look tied to frame rate or unscaled by `dt`** → sensitivity changes with FPS. Look should
be driven by raw mouse delta; movement integration uses `dt` (see `physics-tuning`).
- **Pure random recoil/spread** → feels uncontrollable and unfair. Use a learnable recoil
pattern; keep the first shot accurate.
- **Hitscan with no falloff** → pistols snipe across the map. Add range-based damage falloff.
- **No hit feedback** → players can't tell if shots land. Always show hitmarkers, impact FX, and
a distinct kill confirm.
- **Mismatched TTK** → too low feels twitchy/unfair; too high feels spongy. Tune damage,
fire rate, and HP as one system (Pattern 3).
- **Trusting the client in multiplayer** → cheating and "I shot first" disputes. Keep authority
server-side; use lag compensation (refs) and defer netcode to the engine multiplayer skill.
- **No FOV / sensitivity options** → motion sickness and accessibility failures. Always expose them.
## Composition (build it from these skills)
- **Controller + camera:** `godot-3d-essentials` (Godot) or `unreal-cpp-gameplay` / `unreal-blueprints`; Unity uses `unity-physics` + a character controller.
- **Input:** `input-systems` (or `unreal-enhanced-input`) for look/move, rebinding, and gamepad aim assist.
- **Shooting physics:** `godot-physics` / `unity-physics` for raycasts and projectile collision.
- **Enemies:** `game-ai` with `unity-navmesh` / `unreal-behavior-trees` / Godot navigation.
- **Camera & feel:** `camera-systems` for FOV/recoil kick and look smoothing; `game-feel` for hit-stop, screen shake, and impact juice.
- **Polish:** `audio-design` for weapon/impact sound; `shader-programming` for muzzle/impact VFX.
- **Process:** `prototype-fast` to validate aim feel before building content.
## References
- For hitscan vs. projectile trade-offs, damage falloff, recoil/spread, TTK math, hit
registration/lag compensation, and enemy AI states, read `references/shooting-and-feel.md`.