Skip to content

Voice API

By default everyone is in proximity voice — you hear the people near you, in 3D. The Voice.* API lets a script route voice on purpose: put players on teams that only hear each other, set up a PA announcer everyone hears, make a private party channel, and drop people back to proximity when you're done. It can even make a channel come out of a prop — a hand radio, a PA horn, an intercom — so voice plays from an object in the world (Routing voice to props).

It's server‑authoritative — these calls run on the server (the same channel manager the voice relay routes through), so membership and routing always stay in sync. There's no client‑side voice scripting: capture, encoding, and transport are the engine's job, not yours — you only direct who hears whom.

Player ids are the same strings Player.GetPlayerId() returns, so you can pass a player straight from the Player API into these calls.

The model: channels, talkers, listeners

A channel has talkers (whose voice goes into it) and listeners (who hear it). Each player also has one transmit channel — where their own voice goes (proximity by default).

  • AddTalker(p, ch)p can speak into ch and hears it (talker ⇒ also a listener).
  • AddListener(p, ch)p hears ch (no talk rights).
  • SetTransmitChannel(p, ch) — send p's voice to ch instead of proximity.

proximity (the default everyone starts in) is spatial — 3D, distance‑based. Channels you create are flat 2D by default (listeners hear talkers at an even level, regardless of distance) — right for a PA or a team comm. You can make a channel spatial too — see Area voice — when you want a zone where members hear each other positionally.

Teams (the common case)

Players who should only talk to and hear their own team — one call each:

Voice.SetTeam(redPlayerId,  "team-red");    // talks to AND hears only team-red
Voice.SetTeam(bluePlayerId, "team-blue");

SetTeam(p, ch) is shorthand for AddTalker(p, ch) + SetTransmitChannel(p, ch).

A PA / announcer everyone hears

Voice.CreateChannel("pa", "broadcast");
foreach (/* each player id */) Voice.AddListener(playerId, "pa");
Voice.AddTalker(hostId, "pa");
Voice.SetTransmitChannel(hostId, "pa");      // host now broadcasts to everyone on "pa"

Area voice — a 3D zone

For a zone where everyone inside hears each other positionally (3D, distance‑based, like proximity but limited to the zone's members), create a spatial channel and add the people in the zone. The third CreateChannel argument forces spatial:

Voice.CreateChannel("cave", "team", true);   // true = 3D positional (an area voice zone)
// as players enter the zone (e.g. in your trigger handler):
Voice.SetTeam(playerId, "cave");              // they talk to + hear "cave", in 3D
// as they leave:
Voice.RemoveMember(playerId, "cave");         // back to proximity

Only the channel's members hear it (unlike global proximity), but they hear each other by distance — that's the difference between "area voice" and a flat PA/team channel.

Routing voice to props — radios, PA speakers, intercoms

You can make a channel's voice come out of an object in the world — a handheld radio, a PA horn, an intercom box. Anyone transmitting on that channel is then heard 3D from that prop, with distance falloff. Think of it as prop proximity voice.

Voice.RouteToSource("radio", radioProp);              // "radio" now emits from radioProp (20 m falloff)
Voice.RouteToSource("radio", radioProp, 15);          // 15 m falloff
Voice.RouteToSource("radio", radioProp, 15, true);    // + a radio/telephone band-pass timbre

RouteToSource(channelId, object, maxDistance?, radioFilter?):

  • object must be a networked object (it needs a network identity, so every client resolves the same prop). Any networked prop works.
  • maxDistance (default 20) — metres; the voice fades to silence past it.
  • radioFilter (default false) — apply a walkie‑talkie / telephone band‑pass (a ~300–3400 Hz "comms" timbre).

Route one channel to several props (call it for each) — PA horns all over a venue, or every player's handheld — and every talker on the channel is heard from all of them. Membership still decides who hears it: a player only hears a routed prop if they're a listener on that channel.

Voice.ClearRoute("radio", radioProp);   // stop routing the channel to that one prop
Voice.ClearChannelRoutes("radio");      // remove every prop route for the channel

Dual — hear the mouth and the prop

On its own, routing moves the channel's voice to the prop: a talker is heard from the radio, not from their own mouth. Turn on dual to hear both — the speaker's real proximity voice and the prop:

Voice.SetChannelDual("radio", true);                 // mouth + prop
// or set it up front, as the 4th CreateChannel argument:
Voice.CreateChannel("radio", "team", false, true);   // id, type, spatial, dual

Dual is what makes a hand radio feel right: people near you hear your actual voice, and people near a receiver hear it come out of the radio.

Recipe — a hand radio

Everyone can hear any receiver; whoever holds a handset transmits on the channel:

// once, at world start:
Voice.CreateChannel("radio", "team", false, true);      // dual: mouth + receiver
Voice.RouteToSource("radio", radioReceiver, 15, true);  // heard from the receiver, radio timbre

// as each player joins — so they can hear any receiver they walk up to:
Voice.AddListener(playerId, "radio");

// when a player keys / picks up the handset:
Voice.SetTeam(holderId, "radio");                       // holder transmits on "radio"
// when they release / drop it:
Voice.ClearTransmitChannel(holderId);                   // back to plain proximity

Recipe — a venue PA

An announcer at a mic; the whole crowd hears the horns:

Voice.CreateChannel("pa", "broadcast", false, true);    // dual, so the booth also hears them live
Voice.RouteToSource("pa", horn1);
Voice.RouteToSource("pa", horn2);
foreach (/* each player id */) Voice.AddListener(playerId, "pa");
Voice.SetTeam(announcerId, "pa");                       // announcer transmits on "pa"

No‑code option

You can build all of this with components, no script:

  • Voice Speaker (SSVoiceSpeaker) — drop it on the prop and set Channel Id to your channel (e.g. radio), a Max Distance, and optionally Radio Filter. That's the RouteToSource half. Leave Output Source empty to emit from the object itself, or point it at a child AudioSource to fix the emitter position.
  • Voice Zone (VoiceZone) — a box/sphere that puts players inside it on a channel (talk, or listen‑only). Tick Dual on a talk zone for the mouth‑and‑prop effect.

A fully no‑code venue PA: a talk Voice Zone (Broadcast, Dual ticked) around the mic, a listen‑only Voice Zone over the crowd (same Channel Id), and a Voice Speaker on each horn (same Channel Id).

Creating channels

Voice.CreateChannel("team-red");                    // type defaults to "team" (flat 2D)
Voice.CreateChannel("pa", "broadcast");
Voice.CreateChannel("cave", "team", true);          // spatial (3D) — area voice
Voice.CreateChannel("radio", "team", false, true);  // flat 2D, dual (mouth + a routed prop)
Voice.DestroyChannel("team-red");

CreateChannel(id, type?, spatial?, dual?)type is one of team, party, broadcast (alias pa), global, or proximity (anything unrecognized falls back to team). The optional spatial flag overrides positioning: true = 3D positional (area voice), false = flat 2D; omit it to default by type (only proximity‑type channels are 3D). The optional dual flag (default false) also plays talkers on proximity — pair it with routing to a prop so a channel is heard from the speaker and the prop. You can usually skip CreateChannel entirely: adding the first talker/listener auto‑creates the channel (flat 2D), and channels are auto‑removed once empty. Use CreateChannel when you want a specific type, spatial, or dual up front.

Fine‑grained membership

Voice.AddTalker(p, "ch");            // p talks into ch (and hears it)
Voice.AddListener(p, "ch");          // p hears ch
Voice.RemoveMember(p, "ch");         // remove p from ch (talker + listener)
Voice.SetTransmitChannel(p, "ch");   // p's voice goes to ch
Voice.ClearTransmitChannel(p);       // p's voice goes back to proximity

Reading voice state

Inspect the current routing so a script can make decisions — check before adding, drive conditional logic, show who's on what:

string ch  = Voice.GetTransmitChannel(p);     // where p's voice goes now ("proximity" if unassigned)
bool talks = Voice.CanTalk(p, "team-red");    // may p speak into team-red?
bool hears = Voice.IsListener(p, "pa");       // does p hear "pa"? (proximity -> always true)

Back to normal

Voice.ResetVoice(p);    // remove p from every channel + clear transmit -> plain proximity

ResetVoice is your safe recovery hatch — call it when a match ends, or in OnPlayerLeft, so nobody is left stuck in a stale channel.

Server‑side, string ids

Every Voice.* call takes the string player id from Player.GetPlayerId() and runs on the server, so it takes effect consistently for everyone. The API intentionally exposes only channel routing — not the underlying voice capture/transport.

Quick reference

Call Effect
Voice.CreateChannel(id, type="team", spatial?, dual?) create a channel (team · party · broadcast/pa · global · proximity); spatial true = 3D area voice, false = flat 2D; dual true = also heard on proximity
Voice.SetChannelDual(channelId, dual) toggle dual — talkers on the channel are also heard on proximity (mouth + a routed prop)
Voice.DestroyChannel(id) destroy a channel
Voice.AddTalker(playerId, channelId) player speaks into the channel and hears it
Voice.AddListener(playerId, channelId) player hears the channel
Voice.RemoveMember(playerId, channelId) remove player from the channel
Voice.SetTransmitChannel(playerId, channelId) send player's voice to the channel
Voice.ClearTransmitChannel(playerId) send player's voice back to proximity
Voice.SetTeam(playerId, channelId) shorthand: talk to and hear only that channel
Voice.ResetVoice(playerId) back to plain proximity; clears all channel membership
Voice.GetTransmitChannel(playerId) → string the channel the player's voice goes to (proximity if unassigned)
Voice.CanTalk(playerId, channelId) → bool may the player speak into the channel?
Voice.IsListener(playerId, channelId) → bool does the player hear the channel?
Voice.RouteToSource(channelId, object, maxDistance=20, radioFilter=false) play the channel's voice 3D from a networked prop (radio / PA); radioFilter adds a comms band‑pass
Voice.ClearRoute(channelId, object) stop routing the channel to that prop
Voice.ClearChannelRoutes(channelId) remove every prop route for the channel

→ Player ids come from the Player API. For an NPC's spoken (TTS) voice, see AI NPCs › Voice.