When a multiplayer feature starts in Mirror, RPCs should not be the first thing on the page.

RPCs are the part that looks exciting because they move across the network. They are also easy to reach for because they solve a visible problem fast: one machine says something happened, another machine reacts. That makes for a satisfying demo. It also makes for fragile architecture if RPCs end up carrying the rules, the state, and the consequences all at once.

A cleaner multiplayer flow starts with a more boring question: who owns the truth?

If that answer is unclear, the rest of the system starts to wobble. Input paths drift. UI becomes speculative. Cheating checks get scattered through unrelated scripts. Late-joining players miss important state. The codebase grows a ring of “temporary” network shortcuts that never really get removed.

Mirror works best when it is treated as a distribution layer, not as the place where game rules live.

The split that keeps the code honest

A useful Mirror project usually separates networked behavior into three categories:

  • Commands for requests from an owned client to the server
  • SyncVars or serialized state for persistent game state
  • RPCs for short-lived effects, not for truth

That split is not about style points. It is about keeping the direction of information clear.

The server owns the rules. The client sends intent. The rest of the clients observe the result. Once that separation is in place, the code becomes easier to reason about because each mechanism has one job instead of four.

A simple way to picture it is this:

[Client / owned object]
      input
        |
        v
     Command
        |
        v
[Server / rules + validation]
   |                   \
   |                    \-- RPCs: one-shot effects
   v
SyncVars / serialized state
   |
   v
[Other clients / observers]

The arrows matter more than the boxes. Commands travel upward as requests. SyncVars travel outward as state. RPCs fan out as brief side effects.

What authority means in practice

Mirror uses words like ownership and authority in ways that can sound interchangeable at first glance. They are not.

Ownership describes routing. If a client owns an object, that client has a path for sending intent related to that object. Authority describes whether the object or component is allowed to be modified through that route. Mirror’s own comments around SyncDirection, isOwned, and authority make this distinction more precise than a lot of networking tutorials do.

That difference matters.

A local player may own a character controller. That does not mean the client gets to decide what the character’s health, currency, or inventory should be. Ownership says who can speak for the object. Authority says which changes are allowed to happen through that channel. The server still decides whether the request is valid and whether the state should actually change.

That is the mental model worth keeping nearby. Ownership routes messages. Authority gates changes. Neither one makes the client the source of truth.

The server should own the rules

A multiplayer game gets brittle the moment the client starts deciding outcomes.

If the client can directly set position, health, inventory, or currency and then merely inform the server afterwards, the network is just a messenger for whatever the client chose to claim. The game may still appear to work in a local test, but the architecture has already lost the ability to defend itself.

A safer order is simple:

  1. the client sends intent
  2. the server validates the request
  3. the server mutates the authoritative state
  4. the server syncs the result back out

That sequence handles combat, pickups, doors, inventory changes, interactions, and most other gameplay systems that matter in a real networked game.

The key idea is that the request is not the outcome. A command is a request. A SyncVar or serialized state is the outcome. The code stays much more stable when those two are not mixed together.

RPCs are for moments, not facts

RPCs are useful, but they are easy to misuse.

A common mistake is to use an RPC to carry state. That works until the edge cases arrive. Late-joining clients miss the message. Packet loss makes the event vanish. Reconnects break the illusion. A piece of information that should have persisted was treated like a one-time announcement.

That is the boundary that should stay in mind:

  • use an RPC for a sound effect
  • use an RPC for a muzzle flash
  • use an RPC for a one-off animation cue
  • use an RPC for a temporary camera shake or burst of feedback

If the information needs to survive time, it belongs in synchronized state.

A health value is state. A death effect is an event. A keycard being owned is state. A pickup flash is an event. Mixing those categories is one of the fastest ways to create confusing multiplayer code.

RPCs should answer the question “what effect should happen now?” They should not answer “what is the game world right now?”

A small example with the right direction

A combat example makes the split easier to see.

using Mirror;
using UnityEngine;

public sealed class PlayerCombat : NetworkBehaviour
{
    [SyncVar(hook = nameof(OnHealthChanged))]
    private int health = 100;

    [Command]
    private void CmdRequestTakeDamage(int amount)
    {
        if (amount <= 0)
            return;

        health = Mathf.Max(health - amount, 0);

        if (health == 0)
            RpcPlayDeathEffect();
    }

    [ClientRpc]
    private void RpcPlayDeathEffect()
    {
        // one-shot effect only
    }

    private void OnHealthChanged(int oldValue, int newValue)
    {
        // update UI here
    }
}

The syntax is not the interesting part.

The important part is the direction of control. The client does not set health directly. It asks the server to apply damage. The server decides whether that request is acceptable, mutates the authoritative value, and Mirror distributes the result. The UI hook updates from the state change rather than from a guessed local copy.

That gives three useful properties right away:

  • invalid input can be rejected on the server
  • late joiners can still receive the current state
  • UI can stay tied to real data instead of duplicated logic

That is a much safer foundation than trying to keep a few “helpful” local copies in sync by hand.

Commands, SyncVars, and RPCs each earn their place

Each mechanism solves a different part of the problem.

Commands

Commands are best when a client owns the object and wants to request an action from the server. They are a good fit for player input, interaction requests, ability triggers, and other client-originated intentions.

SyncVars and serialized state

SyncVars are the obvious choice for values that should exist no matter when a client arrives. Health, team, ready state, match phase, ownership flags, and a lot of UI-driving numbers belong here. If a player disconnects and reconnects, the state should still make sense.

RPCs

RPCs are for brief side effects. They are the right choice when something should happen now, but does not need to persist as a rule of the world.

The practical payoff is that each network feature becomes easier to classify. When a new bit of multiplayer behavior appears, the question is not “how do these APIs work?” The question is “is this a request, persistent state, or a transient effect?” That is a much easier decision to make correctly.

Where client authority still makes sense

Client authority is not the enemy. It is just easy to overtrust.

Mirror’s own direction-based comments make the point clearly: the important issue is not whether a client can speak for an object, but whether the flow of data still makes sense. A client-owned object can be a perfectly good place for local input, local prediction, or owner-only state as long as the server still has room to validate what comes back.

That usually fits cases like these:

  • a local player controller
  • input-driven interaction probes
  • owner-only cosmetic state
  • private UI state tied to the local player

It does not fit shared truth.

If two or more players care about the same outcome, the server should usually own that outcome. Otherwise the code is relying on trust where it should be relying on verification.

The pieces that usually go wrong

The same mistakes show up in multiplayer code over and over again.

Mistake 1: the client writes reality

If the client changes the real state directly, the server becomes a witness instead of an authority. That is a bad sign even before cheating becomes a concern.

Mistake 2: RPCs carry persistent state

If a value needs to survive reconnects or late joins, it should not live in an RPC. A message can be missed. State should not.

Mistake 3: UI is treated like truth

Local UI is allowed to predict or reflect state, but it should not become the source of truth. A health bar is not health. A label is not inventory. A menu is not the game.

Mistake 4: validation is deferred until “later”

Later usually means never. The server should validate the request at the moment it receives it.

Mistake 5: authority and ownership are conflated

This one causes a lot of confusion. A client can own an object without being allowed to define the game world. Routing is not trust.

A practical checklist for Mirror code

When a new feature needs networking, the following checklist usually keeps the design on the rails:

  • Does this value need to survive reconnects or late joins? Use state.
  • Is this a request from the owning client? Use a Command.
  • Is this only a momentary effect? Use an RPC.
  • Can the request be cheated? Validate it on the server.
  • Does the client merely observe the result? Keep it observing.

If that checklist feels repetitive, good. Multiplayer code benefits from repetition because the same categories keep coming back.

Why this shape ages well

Mirror code that starts with authority tends to age better because it avoids making the client a parallel truth source.

That matters when a game grows. New features rarely arrive in isolation. A simple pickup becomes a scoreboard entry, a notification, a sound effect, and a persistence rule. A movement system becomes animation, interpolation, spectator logic, and anti-cheat checks. When the core direction of state is already clear, those additions stay manageable.

The opposite pattern is expensive. If the game starts with RPCs everywhere, the codebase eventually spends more time explaining itself than shipping features. The network layer becomes a web of special cases. Debugging becomes archaeology.

Authority-first architecture is not glamorous. It is just the shape that keeps multiplayer logic boring enough to survive real production work.

Part 1 ends with a simple rule

Treat Mirror as a way to distribute already-decided game truth.

The client should request. The server should decide. The state should sync. The RPC should decorate, not define. That one habit prevents a surprising amount of pain later.

Next part

This is Part 1.

Part 2 can stay close to the same structure and push it into a concrete player-action loop: input, validation, sync, owner-only UI, and the point where an RPC is still the right tool instead of a temptation.

References