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)—pcan speak intochand hears it (talker ⇒ also a listener).AddListener(p, ch)—phearsch(no talk rights).SetTransmitChannel(p, ch)— sendp's voice tochinstead 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?):
objectmust be a networked object (it needs a network identity, so every client resolves the same prop). Any networked prop works.maxDistance(default20) — metres; the voice fades to silence past it.radioFilter(defaultfalse) — 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 theRouteToSourcehalf. Leave Output Source empty to emit from the object itself, or point it at a childAudioSourceto 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.