What Makes an FPS Feel Good?
An FPS can have perfect netcode, beautiful graphics, and balanced weapons, but if the moment-to-moment shooting doesn't feel satisfying, players leave. Game feel in shooters is the sum of hundreds of small decisions: screen shake intensity, recoil recovery speed, hit marker duration, audio delay between shot and impact.
This guide breaks down those decisions.
Weapon Feel: The Core Loop
The Shot Cycle
Every weapon fire follows a cycle:
Input → Fire Animation → Muzzle Flash + Sound → Recoil → Projectile/Trace →
Hit Detection → Hit Feedback (marker + sound + effect) → Recoil Recovery → Ready
Every step must feel instantaneous and connected. Delay at any point breaks the feedback loop.
Recoil Systems
Pattern-based recoil (CS-style): Weapons have a deterministic spray pattern. Skilled players learn to counter-steer. High skill ceiling, competitive-friendly.
// Pattern-based recoil
FVector2D GetRecoilOffset(int32 ShotIndex) const
{
if (ShotIndex < RecoilPattern.Num())
return RecoilPattern[ShotIndex];
return RecoilPattern.Last(); // Sustain final pattern
}
Random recoil (Halo-style): Recoil is randomized within a cone. Easier to learn, less punishing, less competitive depth.
FVector2D GetRecoilOffset() const
{
float VerticalKick = FMath::RandRange(MinVerticalRecoil, MaxVerticalRecoil);
float HorizontalKick = FMath::RandRange(-MaxHorizontalRecoil, MaxHorizontalRecoil);
return FVector2D(HorizontalKick, VerticalKick);
}
Hybrid recoil (modern standard): Base pattern + random variation. Deterministic enough to learn, random enough to stay challenging.
Recoil Recovery
After firing stops, the crosshair should return toward center:
- Recovery speed: Fast (arcade feel) vs slow (tactical feel)
- Full vs partial recovery: Does it return to exact center or just partway?
- Recovery curve: Linear feels mechanical; ease-out feels natural
Screen Shake
Weapon-specific screen shake sells the power fantasy:
- Pistol: Minimal (1-2 pixel amplitude, 50ms duration)
- Assault rifle: Moderate per-shot shake (2-3px, 40ms)
- Shotgun: Heavy single punch (5-8px, 100ms)
- Sniper: Long recovery sway (3-5px, 300ms)
- Explosion: Intense with falloff by distance
Always provide a "reduce screen shake" accessibility option.
Hit Feedback
The most satisfying moment in an FPS is confirming a hit:
- Hit marker: Brief crosshair flash or X indicator (50-100ms)
- Headshot marker: Distinct visual (different color, shape) + distinct sound
- Kill confirmation: Larger/different marker + distinct audio + possible screen flash
- Damage numbers (optional): Floating numbers showing damage dealt
Audio feedback is half the equation: Each hit should produce a satisfying impact sound. Headshots need a distinct, more impactful variant. Kill confirms need a final punctuation sound.
Time-to-Kill (TTK)
TTK defines the pace of your game:
| TTK | Style | Example | Consequence |
|---|---|---|---|
| Under 200ms | Hardcore tactical | Escape from Tarkov, Rainbow Six | Positioning and information dominate |
| 200-500ms | Fast competitive | Call of Duty, Apex Legends | Aim and reaction time matter most |
| 500-1000ms | Arena shooter | Halo, Overwatch | Tracking aim and ability usage matter |
| 1000ms+ | RPG/ability based | Destiny, The Division | Build and strategy matter most |
Movement Systems
Base Movement
The character controller defines the game's flow:
// Key movement parameters
UPROPERTY(EditDefaultsOnly, Category = "Movement")
float MaxWalkSpeed = 600.f;
UPROPERTY(EditDefaultsOnly, Category = "Movement")
float MaxSprintSpeed = 900.f;
UPROPERTY(EditDefaultsOnly, Category = "Movement")
float JumpZVelocity = 420.f;
UPROPERTY(EditDefaultsOnly, Category = "Movement")
float AirControl = 0.35f; // 0=no air control, 1=full
UPROPERTY(EditDefaultsOnly, Category = "Movement")
float GroundFriction = 8.f; // Higher = stops faster
UPROPERTY(EditDefaultsOnly, Category = "Movement")
float BrakingDecelerationWalking = 2048.f;
Advanced Movement Mechanics
Modern FPS games include movement mechanics that raise the skill ceiling:
Slide: Momentum-preserving crouch slide
void AMyCharacter::StartSlide()
{
if (GetVelocity().Size() > SlideMinSpeed && bIsOnGround)
{
GetCharacterMovement()->MaxWalkSpeedCrouched = GetVelocity().Size() * SlideMomentumMultiplier;
Crouch();
bIsSliding = true;
// Slide decays over SlideTime seconds
}
}
Mantle: Automatic ledge grab
- Trace forward and upward from the character
- If a surface is found within reach height
- Smoothly move the character up and over
- Should feel fast and automatic, not interrupt flow
Wall-run: Lateral movement on vertical surfaces
- Check for suitable wall with trace
- Apply gravity reduction while on wall
- Limited duration (1-2 seconds)
- Exit with jump for wall-jump combo
Movement and Weapon Accuracy
Movement should affect weapon accuracy:
| State | Accuracy Modifier | Rationale |
|---|---|---|
| Standing still | 1.0x (baseline) | Stable platform |
| Walking | 0.9x | Slight movement penalty |
| Sprinting | 0.3x (or disabled) | Can't aim well while sprinting |
| Jumping/Falling | 0.4x | Airborne inaccuracy |
| Crouching | 1.1x (bonus) | Stable low stance |
| Sliding | 0.5x | Moving but lower profile |
| After landing | 0.7x (brief) | Landing disruption |
Competitive Map Design
Map Flow Patterns
Three-Lane: The competitive standard
[Spawn A] ─── [Lane 1 (Close)] ─── [Spawn B]
│ │
├──── [Lane 2 (Mid/Open)] ──────┤
│ │
└──── [Lane 3 (Close)] ──────────┘
Lanes offer different engagement distances and playstyles. Connectors between lanes create flanking opportunities.
Asymmetric: Attack/Defense
[Attackers] → [Choke 1] → [Site A]
↓
[Mid Control] → [Site B]
↑
[Attackers] → [Choke 2] → [Flank Route]
Defenders start with positional advantage. Attackers need coordination to overcome choke points.
Engagement Distances
Design sight lines for your weapon meta:
- Close range (0-10m): Tight corridors, rooms with multiple entry points
- Medium range (10-30m): Open areas with partial cover
- Long range (30m+): Open sight lines with head glitch positions
Every map should offer areas suited to each weapon class.
Cover Design
Good cover has:
- Head glitch height: ~130cm. Player can see over while body is protected
- Width: Enough to hide behind, not enough for two players
- Depth: Can be flanked from the side
- Destructibility (optional): Cover that breaks over time forces repositioning
Bad cover:
- Complete protection with no flank angle (creates camping)
- Too thin (doesn't actually block damage)
- Too tall (can't see over, useless for engagement)
Spawn Design
- Spawns should face away from enemy territory
- No sight line should reach a spawn point
- Multiple exit routes from spawn (prevent spawn camping)
- Safe run-out time: 3-5 seconds of cover before first possible enemy contact
Audio Design for FPS
Spatial Audio
Players rely on audio for enemy positioning. Get it right:
- Footsteps: Distinct per-surface, attenuated by distance, occluded by walls
- Gunshot direction: Clear spatial positioning (surround sound or HRTF)
- Reload sounds: Audible to nearby enemies (risk/reward)
- Ability audio cues: Enemies hear your abilities (counterplay opportunity)
Audio Mixing Priority
1. Player's weapon (always audible, centered)
2. Incoming damage (hits, near-misses)
3. Enemy weapons (spatial, distance-attenuated)
4. Friendly weapons (reduced volume)
5. Abilities and effects
6. Ambient and music (lowest priority, ducked during combat)
Implementation in UE5
Hit Detection: Hitscan vs Projectile
Hitscan (instant trace):
void AWeapon::FireHitscan()
{
FHitResult Hit;
FVector Start = GetMuzzleLocation();
FVector End = Start + GetAimDirection() * MaxRange;
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Weapon))
{
ApplyDamage(Hit);
SpawnImpactEffect(Hit);
}
}
Use for: Assault rifles, SMGs, pistols (standard combat distances)
Projectile (physical bullet):
void AWeapon::FireProjectile()
{
AProjectile* Bullet = GetWorld()->SpawnActor<AProjectile>(
ProjectileClass, GetMuzzleLocation(), GetAimRotation());
Bullet->SetVelocity(GetAimDirection() * MuzzleVelocity);
Bullet->SetDamage(BaseDamage);
}
Use for: Rockets, grenades, bows, slow-firing weapons with visible projectiles
Hybrid: Hitscan for close range, projectile simulation for long range (accounts for travel time without the CPU cost of always simulating projectiles).
Designing an FPS is an exercise in game feel engineering. Every number affects the experience — recoil magnitude, movement speed, TTK, audio timing. Prototype fast, playtest constantly, and iterate on the numbers until the shooting feels right. Everything else builds on that foundation.