Lo que vas a lograr
- →Entender qué es Vivox y cómo se integra con Unity Gaming Services
- →Implementar voz por proximidad 3D que se atenúa con la distancia
- →Agregar un canal de radio global push-to-talk estilo Phasmophobia
- →Escribir el VoiceChatManager completo listo para tu juego
- →Configurar parámetros de audio 3D, mute y extras
Si jugaste Phasmophobia sabés de qué hablo: por defecto, solo te escuchan los jugadores que están cerca. Si querés hablar con alguien lejos, tenés que usar la radio, pero el fantasma también te puede escuchar.
En esta guía vas a implementar exactamente eso: voz por proximidad que se atenúa con la distancia y un canal de radio global push-to-talk. Vivox se encarga de todo lo complicado del audio. Vos solo le decís quién está dónde.
¿Qué es Vivox?
Vivox = el motor de voz de Unity
Vivox es el servicio de voice chat de Unity Gaming Services. Se ocupa de capturar el micrófono, comprimir el audio, transmitirlo por internet, posicionarlo en 3D y suprimir el ruido. No necesitás enviar audio por RPCs ni NetworkVariables porque Vivox tiene sus propios servidores.
Tu proyecto solo le dice "este jugador está en esta posición" y Vivox se encarga de que cada uno escuche a los demás con el volumen correcto según la distancia.
Arquitectura de canales
Para replicar el sistema de Phasmophobia necesitás dos canales de Vivox simultáneos:
| Canal | Tipo | Propósito |
|---|---|---|
| Proximidad | Positional (3D) | Voz que se atenúa con la distancia |
| Radio | Group (no-positional) | Radio global, todos escuchan a todos |
El jugador siempre está conectado a ambos canales. La diferencia es a cuál transmite:
Por defecto: proximidad
Transmite solo al canal 3D. Los demás te escuchan si están cerca.
Manteniendo V: radio
Transmite a ambos canales. Todos te escuchan sin importar la distancia.
Instalar Vivox
- 1En Unity: Package Manager > Unity Registry → buscar "Vivox" → instalar
com.unity.services.vivox(v16.9+) - 2En Edit > Project Settings > Services → vincular tu proyecto a Unity Dashboard (si no lo hiciste ya con Lobby/Relay)
- 3En el Unity Dashboard, habilitar Vivox en tu proyecto
El código del VoiceChatManager
Creá un script VoiceChatManager.cs y ponelo en el mismo GameObject que el NetworkManager (o uno que persista entre escenas):
using Unity.Services.Authentication;
using Unity.Services.Vivox;
using UnityEngine;
public class VoiceChatManager : MonoBehaviour
{
public static VoiceChatManager Instance { get; private set; }
[Header("Audio 3D")]
[SerializeField] private int audibleDistance = 30;
[SerializeField] private int conversationalDistance = 5;
[SerializeField] private float fadeIntensity = 1.0f;
[SerializeField] private AudioFadeModel fadeModel = AudioFadeModel.InverseByDistance;
[Header("Controles")]
[SerializeField] private KeyCode radioKey = KeyCode.V;
private bool _isConnected;
private bool _isTransmittingRadio;
private string _proximityChannel;
private string _radioChannel;
private void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Instance = this;
}
// Llamar despues de que UGS ya este inicializado y autenticado
public async void StartVoiceChat(string lobbyId)
{
_proximityChannel = $"proximity-{lobbyId}";
_radioChannel = $"radio-{lobbyId}";
await VivoxService.Instance.InitializeAsync();
await VivoxService.Instance.LoginAsync(new LoginOptions
{
DisplayName = AuthenticationService.Instance.PlayerId
});
// Canal de proximidad (3D positional)
var props = new Channel3DProperties(
audibleDistance,
conversationalDistance,
fadeIntensity,
fadeModel
);
await VivoxService.Instance.JoinPositionalChannelAsync(
_proximityChannel,
ChatCapability.AudioOnly,
props
);
// Canal de radio (global)
await VivoxService.Instance.JoinGroupChannelAsync(
_radioChannel,
ChatCapability.AudioOnly
);
// Por defecto, transmitir solo al canal de proximidad
await VivoxService.Instance.SetChannelTransmissionModeAsync(
TransmissionMode.Single,
_proximityChannel
);
_isConnected = true;
}
private void Update()
{
if (!_isConnected) return;
HandleRadioInput();
}
private async void HandleRadioInput()
{
if (Input.GetKeyDown(radioKey) && !_isTransmittingRadio)
{
_isTransmittingRadio = true;
await VivoxService.Instance.SetChannelTransmissionModeAsync(
TransmissionMode.All,
null
);
}
if (Input.GetKeyUp(radioKey) && _isTransmittingRadio)
{
_isTransmittingRadio = false;
await VivoxService.Instance.SetChannelTransmissionModeAsync(
TransmissionMode.Single,
_proximityChannel
);
}
}
public async void StopVoiceChat()
{
if (!_isConnected) return;
_isConnected = false;
await VivoxService.Instance.LeaveAllChannelsAsync();
await VivoxService.Instance.LogoutAsync();
}
private void OnDestroy()
{
StopVoiceChat();
}
}¿Qué hace cada parte?
- StartVoiceChat: Inicializa Vivox, hace login, se une a ambos canales (proximidad 3D + radio global) y configura la transmisión por defecto a solo proximidad.
- HandleRadioInput: Mientras mantenés la tecla V, cambia la transmisión a todos los canales. Al soltar, vuelve a solo proximidad.
- StopVoiceChat: Sale de todos los canales y desloguea de Vivox.
Actualizar la posición 3D
Para que el audio posicional funcione, Vivox necesita saber dónde está cada jugador. Hay que enviarle la posición periódicamente, no cada frame. Con 5 veces por segundo alcanza.
Agregá esto en tu script del jugador (donde tengas referencia al transform del jugador local):
private float _nextVivoxUpdate;
// Dentro de Update(), solo para el jugador local:
if (IsOwner && Time.time >= _nextVivoxUpdate)
{
_nextVivoxUpdate = Time.time + 0.2f; // 5 veces por segundo
VivoxService.Instance.Set3DPosition(gameObject, "proximity-{lobbyId}");
}¿Qué hace Set3DPosition?
Toma automáticamente la posición y rotación del GameObject para calcular desde dónde "habla" y desde dónde "escucha" el jugador. Vivox usa esa información para calcular el volumen en tiempo real.
Conectar con el flujo del juego
En tu ConnectionManager o donde manejes el flujo Host/Client, después de que la conexión de red esté establecida:
// Despues de conectar al juego
VoiceChatManager.Instance.StartVoiceChat(lobbyId);
// Cuando el jugador vuelve al menu
VoiceChatManager.Instance.StopVoiceChat();StartVoiceChat() después de SignInAnonymouslyAsync(). Vivox necesita que Authentication esté activo para funcionar.Configurar los parámetros de audio 3D
Los valores de Channel3DProperties controlan cómo se atenúa la voz con la distancia:
|<-- conversationalDistance -->|<-------- fade zone -------->|
(volumen 100%) (se atenúa gradualmente) silencio
^ audibleDistance
| Parámetro | Valor | Qué hace |
|---|---|---|
| audibleDistance | 30 | Rango máximo donde se escucha algo |
| conversationalDistance | 5 | Dentro de 5 unidades se escucha al 100% |
| fadeIntensity | 1.0 | Atenuación normal (subir a 2.0+ para caída más rápida) |
| fadeModel | InverseByDistance | Atenuación natural como el sonido real |
Modelos de atenuación
- InverseByDistance: caída natural tipo 1/distancia. Realista.
- LinearByDistance: caída uniforme. Más predecible para el jugador.
- ExponentialByDistance: caída agresiva. Se escucha fuerte de cerca y casi nada a distancia media.
audibleDistance a 15-20.Extras opcionales
Indicador visual de radio
Para que el jugador sepa cuándo está transmitiendo por radio, mostrá un icono en la UI:
// En tu script de HUD
[SerializeField] private GameObject radioIndicator;
private void Update()
{
radioIndicator.SetActive(Input.GetKey(KeyCode.V));
}Mute y control de volumen
// Mutear/desmutear micrófono
VivoxService.Instance.MuteInputDevice();
VivoxService.Instance.UnmuteInputDevice();
// Mutear/desmutear parlantes
VivoxService.Instance.MuteOutputDevice();
VivoxService.Instance.UnmuteOutputDevice();
// Ajustar volumen (0-100)
VivoxService.Instance.InputDeviceVolume = 50; // mic
VivoxService.Instance.OutputDeviceVolume = 70; // lo que escuchásSaber quién está hablando
Para mostrar un icono de "hablando" encima de cada jugador:
VivoxService.Instance.ParticipantAddedToChannel += (participant) =>
{
// participant.PlayerId -> ID del jugador
// participant.IsSpeaking -> bool en tiempo real
// participant.AudioEnergy -> float 0-1, volumen actual
};participant.IsSpeaking en Update para mostrar/ocultar un icono de voz sobre el jugador correspondiente.Push-to-talk para todo (no solo radio)
Si querés que el mic solo esté activo cuando apretás una tecla (como Discord PTT):
// Arrancar muteado despues del login:
VivoxService.Instance.MuteInputDevice();
// En Update:
if (Input.GetKeyDown(KeyCode.T))
VivoxService.Instance.UnmuteInputDevice();
if (Input.GetKeyUp(KeyCode.T))
VivoxService.Instance.MuteInputDevice();Modo "muerto" (no puede hablar)
Si un jugador muere y no debería poder hablar (como en Phasmophobia):
// Cuando muere: sacarlo del canal de proximidad
await VivoxService.Instance.LeaveChannelAsync(proximityChannel);
// Cuando respawnea: reconectarlo
await VivoxService.Instance.JoinPositionalChannelAsync(proximityChannel, ...);Problemas comunes
| Problema | Causa probable | Solución |
|---|---|---|
| No se escucha nada | Vivox no inicializado | Verificá que StartVoiceChat() se llame después de SignInAnonymouslyAsync() |
| Se escuchan todos igual | No se actualiza Set3DPosition | Verificá que solo el Owner lo llama y que el channel name coincide |
| Audio cortado | Set3DPosition cada frame | Limitá a 5 updates por segundo (cada 0.2s) |
| Canal no encontrado | Nombre diferente entre Host y Client | Usá el mismo lobbyId para generar los nombres |
| Radio no funciona | TransmissionMode no cambia | Verificá que SetChannelTransmissionModeAsync se completa sin error |
| Eco / feedback | Parlantes captan el mic | Vivox tiene supresión de eco por defecto, pero auriculares ayudan |
Flujo completo resumido
1. Jugador conecta al juego (Host o Client)
2. UGS ya inicializado (Authentication, Lobby, Relay)
3. VoiceChatManager.StartVoiceChat(lobbyId)
├── Vivox Login
├── Join canal "proximity-{lobbyId}" (3D positional)
├── Join canal "radio-{lobbyId}" (group)
└── Transmisión default: solo proximidad
4. En cada Update (5 veces/seg):
└── Set3DPosition del jugador local
5. Jugador habla:
├── Sin apretar nada → solo proximidad
└── Manteniendo V → todos lo escuchan
6. Jugador se desconecta:
└── VoiceChatManager.StopVoiceChat()
¿Y ahora qué?
Con esto tu juego ya tiene voice chat con audio posicional y radio. Es de esas features que cambian completamente cómo se siente jugar: no es lo mismo leer un chat que escuchar a tu compañero gritando que hay un enemigo atrás tuyo.
Si querés seguir y armar el flujo completo de un juego multiplayer (Relay, Lobbies, voice chat, optimización de latencia), en el curso premium lo cubrimos paso a paso:
Ver el curso completo de Multiplayer
6 módulos, 48 lecciones. Desde la base hasta dedicated servers, pasando por Relay, Lobbies, voice chat y optimización.
Ver el curso completoPreguntas Frecuentes
¿Vivox es gratis?
Sí, hasta 5,000 usuarios concurrentes por mes. Para desarrollo, cursos y juegos indie es más que suficiente.
¿Puedo usar Vivox sin Netcode for GameObjects?
Sí. Vivox tiene sus propios servidores de audio, independientes del networking de tu juego. Podés usarlo con cualquier solución multijugador (NGO, Mirror, Fusion, etc.).
¿Funciona en móviles?
Sí. Vivox soporta Windows, macOS, Linux, iOS, Android y consolas. El SDK maneja los permisos de micrófono automáticamente en cada plataforma.
¿Necesito un servidor dedicado para el voice chat?
No. Vivox usa sus propios servidores en la nube. Tu proyecto solo necesita indicar a qué canales conectarse y las posiciones de los jugadores.
¿Puedo tener chat de texto además de voz?
Sí. Vivox maneja texto con SendChannelTextMessageAsync() y ChannelMessageReceived. Podés usarlo en el mismo canal de voz.
¿Qué pasa si un jugador no tiene micrófono?
Puede escuchar a los demás sin problemas. Vivox maneja input y output de audio por separado.
Si tenés dudas sobre la implementación, preguntá en el Discord de Codearte.
Más contenido gratuito de Unity y desarrollo de juegos en el canal de YouTube.
¿Quieres crear tus propios juegos?
Aprende a programar videojuegos desde cero con nuestros cursos en Unity. Más de 3,000 estudiantes ya empezaron su camino.
Ver cursos gratuitos


