Part 7: Ownership & Animation – The Definitive Guide

Animations are tricky in a Client-Side Prediction (CSP) model. Do you play them instantly and risk them being wrong? Or do you wait for the server and risk them feeling laggy? The answer depends on ownership and the gameplay context.

This guide explains the advanced logic used in our project to handle every animation case correctly.


The 4 Horsemen of CSP Context: isOwnerisServerisVerifiedisVerifiedAndReplaying

To make smart decisions about animation, you first need to understand the context you’re in. PurrNet provides several boolean properties that tell you exactly what’s happening.

  1. isOwner:
    • What it means: “Is this game instance the one that directly controls this object?”
    • Client: true for your own player character. false for everyone else’s character and all AI.
    • Server: true for all AI (since the server controls them). false for all player-controlled characters (even though it has authority, it doesn’t “own” their inputs).
  2. isServer:
    • What it means: “Is this code running on the server/host?”
    • Client: Always false.
    • Server/Host: Always true.
  3. isVerified:
    • What it means: “Is the simulation tick we are currently processing based on data that has been confirmed by the server?”
    • Client: Only true during a rollback and re-simulation (a.k.a. “replaying”). It’s false during normal, live prediction.
    • Server/Host: Always true during its normal simulation, because the server is the source of truth.
  4. isVerifiedAndReplaying:
    • What it means: This is a convenient property that essentially combines isVerified && isReplaying. It’s the most reliable way for a client to know: “Am I currently in a re-simulation process based on a server-corrected state?”
    • Client: true only during a replay. This is your go-to flag for triggering “correctness-first” logic.
    • Server/Host: false during normal simulation.

The Great Divide: UpdateView() vs. Simulate()

As mentioned in the previous guides, our project uses an architectural split to manage animations based on our needs.

ShouldUpdateAnimationsInView() → For Owned Objects

  • When it’s true: When the object is yours (isOwner is true).
  • Why: This is for maximum responsiveness. When you press a button, you want to see your character animate instantly. Since you are the source of the inputs, your predictions are highly likely to be correct. We run this in UpdateView() which uses the smooth, interpolated state, making your character’s movement feel fluid. The system architecture guarantees that UpdateView() is never called during a rollback, so we only need to check for ownership.

ShouldUpdateAnimationsInSimulate() → For Non-Owned Objects

  • When it’s true: When the object is not yours AND the server is running the code OR you are a client replaying a verified tick.
  • Why: This is for maximum correctness. You have no idea what another player or an AI is going to do. Their state is constantly being predicted (extrapolated) and corrected. If you animated them based on your potentially wrong prediction, you’d see them stutter and pop constantly. By waiting for a isVerifiedAndReplaying tick, you guarantee the animation you are about to play corresponds to what the server actually said happened.

The Trade-Off: Responsiveness vs. Correctness

You can’t always have both. Sometimes you must choose.

The Problem with Animating Non-Owned Objects in UpdateView()

UpdateView() runs every visual frame and uses an interpolated state. This state is a smooth “guess” between two past authoritative states sent by the server. If a client’s prediction about that object was wrong, the interpolated state is also a lie.

Scenario:

  1. Your client predicts an enemy AI will move left.
  2. The UpdateView() interpolates this predicted movement and plays the “Run Left” animation.
  3. The server’s state arrives and says, “Actually, the AI moved right.”
  4. Reconciliation happens. The AI model snaps to its correct position.
  5. Result: The player sees the AI animate left for a few frames before instantly appearing on the right and running right. It looks like a bug.

By animating non-owned objects only on verified ticks inside Simulate(), we avoid this visual lie. We accept a tiny delay (~50-100ms, the player’s latency) in exchange for the animation always matching the character’s true actions.

When to Break the Rules: The Case for Predicted Effects

Sometimes, instant feedback is more important than 100% accuracy. This is a deliberate design choice.

  • Hit/Damaged Animations: When you hit an enemy, you want to see them react now. In our project, the PlayerDamaged state is allowed to transition and play its animation immediately, even on a prediction. If the server later says the hit didn’t happen, the animation might be cut short by the rollback, but the initial responsive feedback is worth that small risk. It makes combat feel crunchy and impactful.
  • Death Animations: This is the opposite. A death animation is a final, critical state change. It would be incredibly weird and frustrating for a player to see an enemy start dying, only for it to snap back to life because the prediction was wrong (e.g., the server determined your shot missed).
    • Therefore, in our project, the PlayerDead state change and its animation MUST wait for server verification. We sacrifice a little responsiveness for absolute correctness on critical gameplay events.

📋 Animation Checklist

  • Is this my character? (isOwner)

[✅] Animate in UpdateView() for maximum responsiveness.

  • Is this another player’s character or an AI? (!isOwner)

[✅] Animate in Simulate() but guard it with isVerifiedAndReplaying (or isServer) for correctness.

  • Is this a low-impact, feedback-oriented animation (like a hit flash)?

[✅] Consider playing it predictively in UpdateView for all players to make the game feel more impactful. Accept the risk of it being rolled back.

  • Is this a critical, gameplay-defining animation (like death, a stun, or a powerful ultimate ability)?

[✅] Never play this predictively for non-owned objects. Always wait for the server’s authority.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *