multiplayer game dev
Your Hit Detection Is Probably Fine. Your Feedback Isn't.
Back to Multiplay Guide
Game Design11 min read< 16ms feel

Your Hit Detection Is Probably Fine. Your Feedback Isn't.

TR
Tomás Reyes
#game feel#netcode#client-side prediction#feedback#multiplayer UX#Apex Legends#Valorant

I spent three months optimizing the netcode on a competitive shooter. Tick rate from 20 to 60. Lag compensation dialed in. Hit registration within one server frame. Player tests came back: "the gunplay feels off." One playtester said the shots "don't feel like they connect." Hit registration accuracy was 98.3%. The problem wasn't the netcode. It was that I hadn't touched the feedback layer at all.

This is the most common mistake I see in multiplayer game development. Teams chase hit registration accuracy, server tick rates, and interpolation timing while ignoring the part players actually feel: the immediate sensory response to their actions. The truth is uncomfortable: a game with mediocre netcode and excellent client-side feedback will feel better than a game with excellent netcode and mediocre feedback.

What Players Actually Experience

When you fire a weapon in a shooter, you're perceiving a chain of events:

  1. Muzzle flash and sound (should be instant)
  2. Camera kick or screen shake (should be instant)
  3. Hit marker or impact effect (should be instant or near-instant)
  4. Damage number or health change on the target (can have a small delay)
  5. The target reacting visually to being hit (can have a delay)

Items 1, 2, and 3 are what players consciously feel as "does this gun feel good." Items 4 and 5 are validation that the action had effect. Most developers implement 4 and 5 with server authority (correct) and then accidentally tie 1, 2, and 3 to the same server response loop (catastrophically wrong).

If your muzzle flash waits for server confirmation before rendering, you've just added your entire round-trip latency (call it 80ms for a player on a decent connection) to the most visible piece of feedback in your game. Players won't articulate this. They'll just say the gun feels "floaty" or "like shooting a nerf dart."

The Authority Split

Here's the principle that fixes this. Split your game's responses into two categories:

Outcomes (server-authoritative): did the bullet hit, how much damage, did the player die, was the action valid. These must be server-side. No compromise.

Feedback (client-side immediate): sound effects, particle systems, camera shake, controller rumble, hit markers, UI flashes. These fire immediately on the local client, no round-trip, no server confirmation required.

This isn't a security risk. Client-side particles don't affect game state. A client lying about its muzzle flash doesn't give it any advantage. You're just giving the player the sensory confirmation they need in under 16ms instead of in 80-150ms.

// BAD: Everything waits for the server
onFireButtonPressed() {
  sendToServer({ type: 'fire', origin, direction });
  // muzzle flash, sound, camera kick all tied to server response
}

onServerConfirmFire(response) {
  playMuzzleFlash(response.origin);   // 80ms late
  playGunshot(response.soundId);      // 80ms late
  applyCameraKick(response.kickAmount); // 80ms late
  if (response.hitConfirmed) showHitMarker(); // fine to wait for this
}

// GOOD: Feedback is immediate, outcome waits for server
onFireButtonPressed() {
  // Instant feedback — no server needed
  playMuzzleFlash(localOrigin);
  playGunshot('weapon_ak_fire');
  applyCameraKick(weapon.kickParams);

  // Send to server for authoritative outcome
  sendToServer({ type: 'fire', origin: localOrigin, direction: aimDirection });
}

onServerConfirmFire(response) {
  if (response.hitConfirmed) {
    showHitMarker();           // Correct to wait for server here
    playHitSound();            // You can wait for this too — it's secondary
  }
  // Server can also correct any misprediction silently
}

Case Study: Apex Legends vs Early Battlefield V

Apex Legends launched in February 2019 and immediately felt incredible to play. Players praised the gunplay before the meta had settled, before anyone understood the abilities, before the game had been optimized. The guns just felt right. Respawn had shipped massive-world shooters before (Titanfall 2) and understood feedback timing deeply. Every weapon has layered audio that triggers client-side instantly, tight camera shake that matches the weapon's personality, and a subtle controller vibration profile tuned per-gun.

Battlefield V launched three months earlier and had constant complaints about "floaty" gunplay in its first months, despite technically superior hit registration than many competitors. The weapon sounds were cinematic but the immediate feedback timing was off. EA DICE patched it significantly over the following year. The guns started feeling better without the hit detection changing much at all. Weapon audio mix and feedback timing were the primary fixes.

Same genre, similar tick rates (both around 30Hz at launch), dramatically different feel. The difference was overwhelmingly in the feedback layer.

Hit Markers: Client-Side by Default

Hit markers deserve specific attention because developers often implement them wrong by default.

There are two moments you could show a hit marker: when the client's local prediction says a bullet hit (instant), or when the server confirms the hit (80-150ms later). Almost every competitive shooter uses client-side prediction for hit markers. Valorant, CS2, Apex, Overwatch, all show the hit marker based on client-side hit detection, then reconcile with the server afterward.

The reconciliation might occasionally produce a false hit marker (client predicted a hit, server said miss). In practice this is rare, and it's far less disruptive than the alternative: every hit marker arriving nearly 100ms after the shot, making every weapon feel like it has an artificial delay. Players tolerate the occasional false positive far better than consistent feedback lag.

In Unity with Mirror networking, the pattern looks like:

[Client]
void HandleShot(Vector3 origin, Vector3 direction) {
  // Client-side raycast for immediate feedback
  if (Physics.Raycast(origin, direction, out RaycastHit hit, maxRange)) {
    if (hit.collider.CompareTag("Player")) {
      hitMarkerUI.ShowHitMarker(); // Instant, no server wait
    }
  }
  // Send to server for authoritative damage
  CmdProcessShot(origin, direction);
}

[Command]
void CmdProcessShot(Vector3 origin, Vector3 direction) {
  // Server-side lag compensation + authoritative damage
  ServerHitDetection.Process(connectionToClient, origin, direction);
}

Sound Design Is Netcode

This sounds like a joke. It isn't.

Doom Eternal is a single-player game, but it demonstrates the principle perfectly. Every enemy hit in Doom Eternal produces an immediate, satisfying crunch sound. That audio response is what makes the combat feel weighty. Remove the sound and the same hits feel hollow, even with identical visual effects.

In multiplayer, sound feedback has to fight against network latency. The fix is simple and almost universally applicable: fire all impact sounds client-side based on local prediction. Don't wait for the server to tell you that the bullet hit before playing the impact sound. Play it when your local raycast says it hit. If the server disagrees and rolls back the hit, you might have played a false impact sound once. That's fine. Nobody notices the occasional false positive. Everyone notices when every impact sound is 100ms late.

Games that do this well: Quake (the original, which had to solve this problem in 1996 on 56k modems), Counter-Strike 2 (tight immediate audio even at 128-tick), and Dead by Daylight (which has mediocre tick rate but excellent immediate action feedback).

Controller Rumble and Haptics

If you're shipping on console or supporting controller on PC, haptic feedback is one of the most powerful feel levers you have, and it's one of the most commonly neglected.

PS5's DualSense adaptive triggers can simulate weapon tension, recoil, and reload resistance. This is all client-side. It fires immediately on button press, not when the server acknowledges the action. If you're tying haptics to server responses, you're doing it wrong.

Even basic rumble matters. A weapon that rumbles on fire feels fundamentally more impactful than an identical weapon with no rumble. In multiplayer, this should fire client-side immediately. Dying also benefits from haptics (the brief, heavy rumble when you take a fatal hit). Again: client-side, immediate, based on local prediction.

Camera Shake and Screen Effects

Camera shake when firing is applied to the local player's camera client-side. There's no network component here. But many developers accidentally couple the camera shake to the server-confirmed fire event rather than the local input event.

The same applies to screen effects: edge vignettes when taking damage, red flash for hits, scope sway. All of these should fire immediately on local events when possible, or on local predictions when the event itself is network-dependent.

One thing to watch: don't apply camera shake to remote players' actions on the local client. If you shake the camera every time any player fires near the local player, based on the received server state, you'll get camera shake that arrives at the wrong time relative to the visual of the shot. Either don't shake the camera for remote fire events, or calculate a local prediction of when remote players are firing based on their visible animations.

Animation Blending for Networked Characters

Remote characters (other players) present a specific feel problem. Their positions arrive at the tick rate (30-60 Hz), but players move at 60+ FPS. Naive interpolation between received positions creates visually smooth movement, but it looks mechanical. Human movement has subtle acceleration and deceleration that gets lost when you're just lerping between server-reported positions.

The solution is root motion blending: use animation-driven movement for remote characters rather than pure position updates. The character's animation drives the apparent motion, and the server position updates are used to anchor the animation. Unreal Engine's Character Movement Component does a version of this natively. In Unity, you can achieve it by driving the animator's parameters (speed, direction) from received velocity data rather than snapping the transform.

This isn't strictly a feedback issue, it's an interpolation issue, but the result is the same: characters that feel like they have weight and momentum rather than sliding on rails between server positions.

Where to Start

If your multiplayer game's feel is getting complaints, audit these four things before you touch your netcode:

  1. Sound timing: Are impact, fire, and ability sounds tied to server response? Move them client-side.
  2. Hit markers: Are they server-confirmed? Add client-side prediction.
  3. Camera and controller feedback: Are they coupled to network events? Decouple them.
  4. Primary particle effects: Muzzle flash, ability effects, movement dust. All should fire client-side immediately.

I'd bet that fixing these four things improves your feel ratings more than halving your server tick rate ever would. The netcode textbooks cover hit registration and lag compensation extensively. Nobody covers feedback timing, and it's the thing players actually feel. Now you know where to look.