James Liu

First-person systems study

FPS Demo

A hands-on study of the small technical decisions that make shooting and movement feel distinct in first-person games.

First-person weapon aiming at bottles in the FPS demo
Focus

Weapon feel & player movement

Tools & methods

Unity · C# · Probability · Player controls

Project

First-person systems study

Simple rules become a recognizable weapon feel.

A Unity systems study connecting shot dispersion, movement momentum, and accuracy to weapon feel.

Implementation details

The core loop of an FPS is familiar, so identity comes from the implementation details: how shots disperse, how quickly movement settles, and how accuracy responds to player intent.

A normal distribution creates natural shot dispersion.

Box–Muller sampling creates a tunable spread pattern with shots concentrated near the center.

Bullet directionAim stays intentional. Spread adds variation.
AimCamera ray → targetDirection from the bullet spawn
RandomnessBox–Muller offsetsNormally distributed X / Y values
TuningSpread intensityScale the random deviation
COMBINE
ShotFinal directionAim direction + scaled offsets
Technical details & source excerpt

The weapon uses the Box–Muller transform to generate horizontal and vertical offsets from a bivariate normal distribution. Most shots cluster near the center while a smaller number fall farther away, producing a more believable pattern than uniform randomness.

  • Raycasts establish the camera's intended target point
  • Spread intensity scales the generated deviation
  • The distribution remains centered and tunable
Centered bullet spreadWeaponController.cs · C#
Ray ray = Camera.main.ViewportPointToRay(
    new Vector3(0.5f, 0.5f, 0f)
);
Vector3 targetPoint = Physics.Raycast(ray, out RaycastHit hit)
    ? hit.point
    : ray.GetPoint(100f);

Vector3 direction =
    (targetPoint - bulletSpawn.position).normalized;

float u1 = 1f - UnityEngine.Random.value;
float u2 = 1f - UnityEngine.Random.value;
float normalX = Mathf.Sqrt(-2f * Mathf.Log(u1))
    * Mathf.Sin(2f * Mathf.PI * u2);
float normalY = Mathf.Sqrt(-2f * Mathf.Log(u1))
    * Mathf.Cos(2f * Mathf.PI * u2);

return direction + new Vector3(
    normalX * spreadIntensity,
    normalY * spreadIntensity,
    0f
);

The Box–Muller transform concentrates most shots near the reticle while still allowing occasional wider deviation—a more natural pattern than uniform offsets.

Precision requires deliberate counter-strafing.

Counter-strafing makes precise shots depend on deliberate movement control.

Movement & accuracyControl momentum to recover precision
MoveBuild momentumAccuracy responds to movement
Counter-strafeApply opposing inputBring movement to a stop
FireShoot while stationaryHighest precision
Implementation details

Movement preserves enough momentum that stopping accurately requires opposing input. Shooting is most precise when stationary, encouraging players to actively balance mobility, timing, and aim instead of treating movement and accuracy as separate systems.

Next projectThrough Other Eyes