Network Audio Source
Drop-in networked wrapper for Unity's AudioSource. Add it to any GameObject with an AudioSource to sync audio across the network.
Setup
Add an
AudioSourcecomponent to your GameObject.Add
NetworkAudioSourceto the same GameObject (it auto-assigns the AudioSource viaReset()).If you plan to change clips at runtime or use
PlayOneShot, register thoseAudioClipassets in yourNetworkAssetsScriptableObject.
If the clip is baked into the prefab/scene and never changes at runtime, you do not need to register it in NetworkAssets.
Authority
The _ownerAuth toggle (Inspector) controls who can drive the audio:
Owner Auth (default): The owning client controls playback. If no owner is set, the server controls it.
Server Auth: Only the server can control playback.
All public setters and methods guard with IsController(_ownerAuth) and silently no-op on non-controllers.
Public API
Use NetworkAudioSource the same way you would use Unity's AudioSource:
Properties
clip
AudioClip
The clip to play. Must be in NetworkAssets if changed at runtime.
volume
float
Volume (0.0 - 1.0)
pitch
float
Pitch multiplier
loop
bool
Whether to loop
mute
bool
Whether muted
spatialBlend
float
2D (0.0) to 3D (1.0) mix
minDistance
float
Distance where volume stops increasing
maxDistance
float
Distance where attenuation stops
time
float
Playback position in seconds
isPlaying
bool
Read-only, current play state
Methods
Play()
Start playback with the current clip
Play(AudioClip)
Set clip and start playback
Stop()
Stop playback
Pause()
Pause playback
UnPause()
Resume from pause
PlayOneShot(AudioClip, float)
Fire-and-forget SFX (does not affect play state)
How It Works
Dirty Flag System
Each property setter and method sets a bit in a AudioDirtyFlags bitmask. On the next network tick, only the flagged fields are serialized and sent. If nothing changed since the last tick, nothing is sent.
Example: calling only volume = 0.5f sends 2 bytes (flags) + 4 bytes (float) = 6 bytes, not the full ~35 byte state.
Custom Serialization
The AudioSourceDelta struct implements IPacked for hand-written bit-level serialization. It writes the flags bitmask first, then only the fields whose bits are set. Booleans (loop, mute) are packed as single bits.
Channel Selection
The dirty flags are checked each tick to decide reliable vs unreliable delivery:
Play, Stop, Pause, UnPause
Reliable
Discrete state transition; a dropped packet means permanent desync
clip
Reliable
Discrete change; must arrive
volume, pitch, spatialBlend, etc.
Unreliable
Continuous tweaks (fades); next tick resends latest value if dropped
PlayOneShot
Reliable
Fire-and-forget SFX; must not be missed
Late joiner reconcile
Reliable
Full state snapshot; must arrive
If a tick contains both reliable and unreliable changes (e.g. volume = 0.5f + Play() in the same frame), the entire delta is sent reliably.
RPC Routing
Follows the same pattern as NetworkAnimator:
Server controller ->
ObserversRpc(excludeSender: true)directly to clients.Client controller ->
ServerRpcto server -> server applies locally ->ObserversRpc(excludeSender: true)to other clients.Late joiner ->
OnObserverAddedsendsTargetRpcwith full state to the new observer.
Playback Time
Time is only sent when explicitly changed (via
Play(),UnPause(), or thetimesetter).It is not auto-sent every tick while playing, avoiding constant bandwidth for background music.
On receiving end, if a time value arrives while playing and the local playback has drifted more than 0.1s, it snaps to the received time.
AudioClip Serialization
AudioClips are serialized via PurrNet's Packer<T> for UnityEngine.Object, which looks up the clip's index in NetworkAssets. This sends a small integer index, not the audio data.
If the clip is not in NetworkAssets, it resolves to
nullon the remote side.If you never change the clip at runtime, it is never serialized (the
Clipdirty flag is never set), so it doesn't matter whether it's registered.
Last updated