Skip to Content
DocsVoicereactNetworking Integration

Networking Integration

VoiceReact provides generic networking hooks that work with any Unity networking library.

Architecture

VoiceReact does not implement networking. Instead, it provides:

  • IVoiceNetworkAdapter interface for networking integration
  • VoiceNetworkData struct for voice event serialization
  • Hooks to send/receive voice events

You’re responsible for:

  • Implementing the adapter for your networking library
  • Serializing/deserializing VoiceNetworkData
  • Transmitting data over network
  • Authority and ownership logic

Supported Networking Libraries

VoiceReact works with:

  • Netcode for GameObjects (Unity’s official solution)
  • Mirror
  • Photon (PUN2, Fusion)
  • Fish-Net
  • Any custom networking solution

Quick Start

1. Enable Networking

using Peek.VoiceReact; [SerializeField] private VoiceReactController playerVoice; void Start() { playerVoice.enableNetworking = true; }

2. Create Network Adapter

Implement IVoiceNetworkAdapter for your networking library:

using UnityEngine; using Peek.VoiceReact.Network; public class MyNetworkAdapter : MonoBehaviour, IVoiceNetworkAdapter { public void SendVoiceEvent(VoiceNetworkData data) { // Serialize and send using your networking library MyNetworkManager.Instance.SendToServer(data); } public void OnRemoteVoiceEvent(VoiceNetworkData data) { // Handle voice event from remote player // (usually not needed - VoiceReact handles this internally) } }

3. Assign Adapter

void Start() { var adapter = GetComponent<MyNetworkAdapter>(); playerVoice.SetNetworkAdapter(adapter); }

That’s it! VoiceReactController will now send voice events through your adapter.

VoiceNetworkData Structure

Voice events are sent as VoiceNetworkData:

public struct VoiceNetworkData { public bool isPlayerSpeaking; // Is player currently speaking? public float decibelLevel; // Current dB level public Vector3 playerPosition; // Player's 3D position public string transcription; // STT result (if enabled) public float timestamp; // Event timestamp }

This struct is small and efficient for network transmission.

Netcode for GameObjects Example

Complete example using Unity’s official Netcode:

using Unity.Netcode; using UnityEngine; using Peek.VoiceReact; using Peek.VoiceReact.Network; public class NetcodeVoiceAdapter : NetworkBehaviour, IVoiceNetworkAdapter { private VoiceReactController voiceController; void Start() { voiceController = GetComponent<VoiceReactController>(); if (IsOwner) { // Only local player sends voice events voiceController.SetNetworkAdapter(this); } } public void SendVoiceEvent(VoiceNetworkData data) { if (!IsOwner) return; // Send to server using ServerRpc SendVoiceEventServerRpc( data.isPlayerSpeaking, data.decibelLevel, data.playerPosition, data.timestamp ); } [ServerRpc] void SendVoiceEventServerRpc( bool isSpeaking, float db, Vector3 position, float timestamp) { // Broadcast to all clients except sender SendVoiceEventClientRpc(isSpeaking, db, position, timestamp); } [ClientRpc] void SendVoiceEventClientRpc( bool isSpeaking, float db, Vector3 position, float timestamp) { if (IsOwner) return; // Don't process own events // Notify local VoiceListeners about remote player voice NotifyLocalListeners(isSpeaking, db, position); } void NotifyLocalListeners(bool isSpeaking, float db, Vector3 position) { if (!isSpeaking) return; // Calculate normalized volume for ProcessRemotePlayerVoice float normalizedVolume = Mathf.InverseLerp(-60f, 0f, db); // Find all local VoiceListeners var listeners = FindObjectsByType<VoiceListener>(FindObjectsSortMode.None); foreach (var listener in listeners) { float distance = Vector3.Distance(listener.transform.position, position); listener.ProcessRemotePlayerVoice(normalizedVolume, distance); } } public void OnRemoteVoiceEvent(VoiceNetworkData data) { // Not needed for this implementation } }

Mirror Example

Integration with Mirror networking:

using Mirror; using UnityEngine; using Peek.VoiceReact; using Peek.VoiceReact.Network; public class MirrorVoiceAdapter : NetworkBehaviour, IVoiceNetworkAdapter { private VoiceReactController voiceController; void Start() { voiceController = GetComponent<VoiceReactController>(); if (isLocalPlayer) { voiceController.SetNetworkAdapter(this); } } public void SendVoiceEvent(VoiceNetworkData data) { if (!isLocalPlayer) return; CmdSendVoiceEvent( data.isPlayerSpeaking, data.decibelLevel, data.playerPosition, data.timestamp ); } [Command] void CmdSendVoiceEvent(bool isSpeaking, float db, Vector3 position, float timestamp) { RpcBroadcastVoiceEvent(isSpeaking, db, position, timestamp); } [ClientRpc] void RpcBroadcastVoiceEvent(bool isSpeaking, float db, Vector3 position, float timestamp) { if (isLocalPlayer) return; // Process remote voice event ProcessRemoteVoice(isSpeaking, db, position); } void ProcessRemoteVoice(bool isSpeaking, float db, Vector3 position) { if (!isSpeaking) return; float normalizedVolume = Mathf.InverseLerp(-60f, 0f, db); var listeners = FindObjectsByType<VoiceListener>(FindObjectsSortMode.None); foreach (var listener in listeners) { float distance = Vector3.Distance(listener.transform.position, position); listener.ProcessRemotePlayerVoice(normalizedVolume, distance); } } public void OnRemoteVoiceEvent(VoiceNetworkData data) { // Not needed for this implementation } }

Photon PUN2 Example

Integration with Photon PUN2:

using Photon.Pun; using UnityEngine; using Peek.VoiceReact; using Peek.VoiceReact.Network; public class PhotonVoiceAdapter : MonoBehaviourPun, IVoiceNetworkAdapter { private VoiceReactController voiceController; void Start() { voiceController = GetComponent<VoiceReactController>(); if (photonView.IsMine) { voiceController.SetNetworkAdapter(this); } } public void SendVoiceEvent(VoiceNetworkData data) { if (!photonView.IsMine) return; // Send via RPC photonView.RPC( nameof(RPC_VoiceEvent), RpcTarget.Others, data.isPlayerSpeaking, data.decibelLevel, data.playerPosition, data.timestamp ); } [PunRPC] void RPC_VoiceEvent(bool isSpeaking, float db, Vector3 position, float timestamp) { // Process remote player voice if (!isSpeaking) return; float normalizedVolume = Mathf.InverseLerp(-60f, 0f, db); var listeners = FindObjectsByType<VoiceListener>(FindObjectsSortMode.None); foreach (var listener in listeners) { float distance = Vector3.Distance(listener.transform.position, position); listener.ProcessRemotePlayerVoice(normalizedVolume, distance); } } public void OnRemoteVoiceEvent(VoiceNetworkData data) { // Not needed for this implementation } }

Remote Voice Processing

VoiceListener provides ProcessRemotePlayerVoice() for network voice events:

public class NetworkVoiceReceiver : MonoBehaviour { void OnReceivedNetworkVoiceData(float volume, Vector3 remotePlayerPosition) { // Find all local listeners var listeners = FindObjectsByType<VoiceListener>(FindObjectsSortMode.None); foreach (var listener in listeners) { // Calculate distance to remote player float distance = Vector3.Distance( listener.transform.position, remotePlayerPosition ); // Let listener process remote voice listener.ProcessRemotePlayerVoice(volume, distance); } } }

This triggers the same OnPlayerHeard/OnPlayerLost events as local voice detection.

Speech-to-Text Networking

Include STT transcriptions in network data:

public class STTNetworkAdapter : NetworkBehaviour, IVoiceNetworkAdapter { private VoiceReactController voiceController; void Start() { voiceController = GetComponent<VoiceReactController>(); if (IsOwner) { voiceController.SetNetworkAdapter(this); // Subscribe to transcription events voiceController.OnTranscriptionReady.AddListener(OnTranscription); } } void OnTranscription(string text) { // Send transcription to other players SendTranscriptionServerRpc(text); } [ServerRpc] void SendTranscriptionServerRpc(string text) { // Broadcast transcription ReceiveTranscriptionClientRpc(text); } [ClientRpc] void ReceiveTranscriptionClientRpc(string text) { if (IsOwner) return; Debug.Log($"Remote player said: {text}"); DisplaySubtitle(text); } public void SendVoiceEvent(VoiceNetworkData data) { // Standard voice event handling // Transcription is sent separately via OnTranscription } public void OnRemoteVoiceEvent(VoiceNetworkData data) { // Not needed } }

Optimization Strategies

Event Throttling

Limit network traffic by throttling voice events:

public class ThrottledNetworkAdapter : IVoiceNetworkAdapter { private float lastSendTime = 0f; private float sendInterval = 0.1f; // 10 Hz public void SendVoiceEvent(VoiceNetworkData data) { // Only send every 100ms if (Time.time - lastSendTime < sendInterval) return; lastSendTime = Time.time; // Send to network SendToNetwork(data); } }

Only Send on State Change

Only send when speaking state changes:

private bool lastSpeakingState = false; public void SendVoiceEvent(VoiceNetworkData data) { // Only send on speaking state change if (data.isPlayerSpeaking != lastSpeakingState) { lastSpeakingState = data.isPlayerSpeaking; SendToNetwork(data); } }

Spatial Relevance

Only send to nearby players:

[ServerRpc] void SendVoiceEventServerRpc(bool isSpeaking, float db, Vector3 position) { // Find players in range var nearbyPlayers = GetPlayersInRange(position, maxHearingRange); foreach (var player in nearbyPlayers) { // Send only to relevant clients SendToClient(player.clientId, isSpeaking, db, position); } }

Latency Compensation

Account for network latency:

[ClientRpc] void ReceiveVoiceEventClientRpc(bool isSpeaking, float db, Vector3 position, float timestamp) { // Calculate latency float latency = Time.time - timestamp; if (latency > 0.5f) { // Discard old events return; } // Process voice event ProcessRemoteVoice(isSpeaking, db, position); }

Authority Validation

Ensure only the owner sends voice events:

public void SendVoiceEvent(VoiceNetworkData data) { // Validate authority if (!HasAuthority()) { Debug.LogWarning("Non-owner tried to send voice event!"); return; } SendToNetwork(data); }

Common Patterns

Voice Activity Indicator

Show which players are speaking:

public class VoiceActivityUI : MonoBehaviour { private Dictionary<ulong, bool> playerSpeakingStates = new(); [ClientRpc] void UpdateVoiceActivityClientRpc(ulong playerId, bool isSpeaking) { playerSpeakingStates[playerId] = isSpeaking; UpdateUI(); } void UpdateUI() { foreach (var kvp in playerSpeakingStates) { var indicator = GetIndicator(kvp.Key); indicator.SetActive(kvp.Value); } } }

Proximity Voice Chat

Implement spatial voice chat:

void ProcessRemoteVoice(Vector3 remotePosition, float db, float maxRange) { var listeners = FindObjectsByType<VoiceListener>(FindObjectsSortMode.None); foreach (var listener in listeners) { float distance = Vector3.Distance(listener.transform.position, remotePosition); if (distance <= maxRange) { // Player is in range - process voice float normalizedVolume = Mathf.InverseLerp(-60f, 0f, db); listener.ProcessRemotePlayerVoice(normalizedVolume, distance); } } }

Debugging

Enable logging for network voice events:

public void SendVoiceEvent(VoiceNetworkData data) { Debug.Log($"[Network] Sending voice event: speaking={data.isPlayerSpeaking}, dB={data.decibelLevel}"); SendToNetwork(data); } [ClientRpc] void ReceiveVoiceEventClientRpc(bool isSpeaking, float db, Vector3 position) { Debug.Log($"[Network] Received voice event: speaking={isSpeaking}, dB={db}, pos={position}"); ProcessRemoteVoice(isSpeaking, db, position); }

Best Practices

  1. Only send on state changes - Don’t spam the network with redundant data
  2. Throttle updates - Limit send rate (10-20 Hz is usually sufficient)
  3. Validate authority - Ensure only owners send voice events
  4. Use spatial relevance - Only send to players in hearing range
  5. Handle latency - Discard or interpolate old events
  6. Test with latency - Use Unity’s Network Simulator

Next Steps

Last updated on