Transformation System

v1.0.0

Multiplayer

The optional Netcode for GameObjects module: authority, setup, roles and damage flow.

Out of the box, this asset can run a real match. One player hosts, others join, and actual prop players hide from actual hunters. The module compiles only when com.unity.netcode.gameobjects is installed — without that package it is entirely absent: no errors, no stubs, and the core stays exactly the network-free asset it is without it.

The Architecture in One Paragraph

Every networked feature is a pure component paired with a replicator. The pure component — Transform Controller, Prop Stat Sheet, Round System, Prop Whistle — never knows the network exists. The replicator sits next to it on the same prefab, subscribes to its events on the way out, calls its entry points on the way in, and is dormant offline: without a listening NetworkManager, OnNetworkSpawn never fires and the prefab behaves exactly as it does in a singleplayer scene. One prefab, every mode.

  Pure component                    Replicator (next to it, same prefab)
  --------------                    ------------------------------------
  PropTransformController  --events-->  PropTransformReplicator  --> the wire
                          <--calls---                            <-- the wire
  PropStatSheet            --------->   PropStatReplicator
  RoundSystem              --------->   RoundReplicator     (server-authoritative)
  PropWhistle              --------->   PropWhistleReplicator

  Presentation - highlights, transform FX, own-view hiding, camera
  framing - is never replicated. Each peer decides what its player sees.
The standing assumption: every peer ships identical assets. IDs resolve against each machine's own Prop Registry, stats align by sheet index, and scenes hold the same props. That is automatically true when everyone runs the same build.

Who Owns What

StateAuthorityWhat travels
Disguise — Prop Transform ReplicatorOwnerThe PropDefinition ID, 32 characters. Every machine rebuilds the clone, hitbox and FX locally from its own scene.
Stats — Prop Stat ReplicatorOwnerCurrent and max per stat, index-aligned with the sheet. A damage instruction run on any other machine is routed to the owner instead of applied.
Round — Round ReplicatorServerPhase, round number, the phase deadline as server time, the outcome, and eliminations as events. Props Alive is never sent — each peer derives it.
Whistle — Prop Whistle ReplicatorOwnerThe action only. Each machine's cooldown timer starts itself.
Transform / animationOwnerClient Network Transform and Owner Network Animator — owner-authoritative, because the Game Creator character motor runs where the input is.
Props Alive is computed on demand, never counted. Replicating it as a number is how the two copies start disagreeing — send registration and elimination events and let each peer derive it, exactly as the local code does. The same reasoning applies to Is Alive and to the eliminated mark.

Setup

Three steps, and two of them are what you would do for any Game Creator module.

  1. Install the Netcode package

    Package Manager → Install by name → com.unity.netcode.gameobjects. Do this before installing the samples: their prefabs carry multiplayer components, and a prefab opened and saved while its scripts are missing can lose them.

  2. Install the samples

    Everything arrives wired — the player and hunter prefabs, the Network Manager and the Host/Join menu, the round object, and a networked sample scene. If you skipped step 1, a dialog offers to install the package once the samples are in; take it before opening any of those prefabs.

  3. Open the Setup window and clear any red row

    In a fresh project exactly one is red: the sample scenes are not in Build Settings. That is project settings rather than assets, so no package can deliver it. One click fixes it.

Then press Play in the menu scene. Host puts you in the arena as the hunter; Join, from a second instance, arrives as a prop player.

Roles. The Network Manager's default player prefab is the prop player, so every joiner hides; the launcher's Host Player Prefab is the hunter, so the host hunts. Clear that field and everyone spawns as a prop player.
Networked behaviour cannot be play-tested in one editor. Use two built players, or an editor-cloning tool such as ParrelSync, on the same machine: host in one instance and join 127.0.0.1 from the other.

Spawn Points and the Hunt-Phase Release

Add a Player Spawn Point to one GameObject in the arena with role Prop Player, and one in the hunters' staging area with role Hunter. Spawned players are placed at their role's point automatically — on spawn and on every scene load — with a small scatter radius so simultaneous joiners do not stack.

To release hunters into the arena when the hunt begins, put this trigger on the hunter prefab:

Trigger:     On Round State Changed
Condition:   Is Round Phase = Hunt
Instruction: Move To Spawn Point   Target = Self   Spawn Point = Prop Player
Not On Update. An On Update trigger conditioned on the hunt phase re-teleports the character every frame for the entire phase, which reads as the hunter being frozen at the spawn.

Placement is network-correct end to end: the character moves through the Game Creator driver (a motor-driven character silently undoes plain transform writes), and on a networked character the owner performs a proper network teleport, so other machines see a jump instead of an interpolated slide through the level. On machines that do not own the character the instruction deliberately does nothing — the replicated result is the mover.

Bringing Your Own Character

The sample prefabs ship wired; yours will not be. Right-click your prefab → Transformation System → Wire For Multiplayer → Prop Player — or Hunter, or Round System for a round object. It adds exactly the components listed on the Components page, is idempotent, and logs what it changed.

One thing it cannot decide for you: which of your Game Creator Triggers read input. Move those onto a child GameObject and list that child under the bootstrap's Owner Only Objects, or every machine's copy of your character fires them when you press the key. Highlighting, inspecting and whistling are already inert on remote copies; transform triggers are the case that still needs this, because remote disguises are applied through the controller and it therefore cannot be disabled.

Also move the camera rig and the HUD canvas under the prefab and list them in Owner Only Objects; list input-reading components that cannot move to a child under Owner Only Behaviours. Leave Auto Register As Prop on for hiders and turn it off on a hunter prefab.

Authoring Rules for Networked Scenes

One Prop Definition per distinct look

The wire carries the definition ID, and a remote machine rebuilds the disguise from the first active world prop with that definition. Two different-looking props sharing one definition means a player disguised as one can appear as the other on remote screens. A chair, a table and a crate are three definitions, all in the registry.

Do not mark cloneable props Static

A static-batched mesh can render wrongly when its object is cloned at runtime — the disguise looks fine in the editor and breaks in a build.

Role-specific scene UI needs a visibility component

A canvas exists on every machine, so a hunter panel authored in the scene shows on prop players' screens too. Put a Network Role Visibility component on the canvas — next to the panel, never on it, since a deactivated object cannot run its own components — point it at the panel and choose who sees it. Offline it does nothing, so singleplayer scenes stay as authored.

Remote copies are inert by construction

On machines that do not own a player, the bootstrap disables that copy's Prop Highlight, Hunter Interaction and Prop Whistle, and the disabled components refuse input calls — so your clicks cannot fire someone else's hunter, and your whistle key cannot whistle through someone else's character.

Aiming needs no wiring

Hunter Interaction and Prop Highlight resolve their Ray Origin at use time: an explicit assignment wins, otherwise the main camera — so the ray leaves the camera along its forward, exactly the centre of the screen, and a crosshair is honest in every mode, including for characters spawned at runtime from a prefab that cannot reference a scene camera.

Only the host starts rounds

Keep the Start Round instruction in a host-driven trigger. On a client the call is simply ignored, with a console note. By default, joins are refused once a round is past Waiting — "The round has already started." — so a hunter cannot materialise mid-hunt.

How Damage Flows in a Networked Match

Author damage prop-side, which is the pattern the samples already use:

On Was Found    ->  Deal Damage   Target = Self   Amount = <your number>   (prop player prefab)
On Wrong Guess  ->  Deal Damage   Target = Self   Amount = <your number>   (hunter prefab)

The hunter inspects on their own machine; the disguise they strike is the replicated clone, so correct and wrong guesses evaluate locally with no round-trip. The resulting Deal Damage lands on whatever machine ran the trigger — and the stat replicator delivers it to the owner, whose write then mirrors back to everyone. Vital depletion fires on every machine; each one's Report Prop Eliminated either counts (host) or is forwarded (clients), and duplicates are idempotent by design.

On Was Found fires on the machine whose hunter guessed — it is not forwarded to the found player's machine. For "you were found" feedback on the victim's own screen, react to On Vital Depleted or On Stat Changed, which replicate through the stats.

Scope & Limits

Included

  • Host / join by address
  • Replicated disguise, stats, round and whistle
  • Owner-authoritative transform and animation
  • Role-keyed spawn points and role visibility
  • A lobby-style menu flow with a status label
  • Right-click Wire For Multiplayer on your own prefab
  • A networked sample scene, ready to play

Deliberately not included

  • Relay, NAT traversal and matchmaking — these need per-project accounts and keys no asset can ship for you
  • Session discovery
  • Server-side anti-cheat

Out of the box the module is direct-connect: the host opens a UDP port and clients enter an address — which covers LAN, VPN tools and port-forwarded hosts. The seam for adding a backend is small: authenticate, exchange the address (or relay allocation) through your service, then call NetworkSession.StartClient / StartHost exactly as the launcher does.

The topology is owner-authoritative, the standard trust model for co-op and party games — the same one the round lock, capacity gate and server-decided eliminations already enforce at the edges. A fully server-authoritative competitive topology would need the character motor to run host-side, which Game Creator's motor does not do.

Using Another Stack

The core contains no networking code, so Photon, Fusion or your own transport goes around it without editing anything in the package. The events are your outbound hooks; the entry points are your inbound ones. The shipped module is the reference implementation of exactly these seams.

Owner                                    Everyone else
-----                                    -------------
player picks a prop
TransformInto(worldProp)          ->     TransformIntoById(id)
  OnTransformComplete fires              OnTransformComplete fires

server starts the round
StartRound(prep, hunt)            ->     StartRound(prep, hunt)
  OnRoundStateChanged fires              OnRoundStateChanged fires

server applies damage
SetStat(health, value)            ->     SetStat(health, value)
  OnVitalDepleted fires (if empty)       OnVitalDepleted fires (if empty)
  ReportPropEliminated             ->    ReportPropEliminated

Two details worth knowing before you write that layer:

  • Prefer SetStat to ModifyStat when replicating. A dropped or duplicated ModifyStat leaves two peers permanently apart; a repeated SetStat is harmless.
  • Unsubscribe from the static OnAny… events on teardown. With domain reload disabled, a handler from the previous session survives, throws from inside the invocation list, and silently skips every subscriber after it. The package clears its own statics on subsystem registration; do not rely on the scene going away.
TransformInto(PropDefinition) sets Is Disguised without creating a disguise — that is the state-only overload for a peer that will build the visuals itself. It is also why HasVisibleDisguise exists: a hunter scores a correct guess only against a disguise that is actually in the scene, so a state-only peer cannot be "found" for standing around looking like themselves.