Voice Listeners
VoiceListener makes enemies, NPCs, and game objects react to player voice based on distance and volume.
Basic Setup
Add a VoiceListener to any GameObject that should hear the player:
// 1. Select enemy GameObject
// 2. Add Component > VoiceListener
// 3. Set Base Hearing Range (e.g., 10 meters)
// 4. Hook OnPlayerHeard eventThe listener automatically:
- Finds the player’s VoiceReactController
- Calculates distance to player
- Determines if voice is audible based on volume and range
- Fires events when player is heard/lost
Hearing Ranges
VoiceListener uses volume-based dynamic hearing ranges:
[SerializeField] private VoiceListener listener;
void Start()
{
// Set base hearing range (when player is at "Loud" volume)
listener.baseHearingRange = 10f;
}Range Multipliers
Control how far different volume levels can be heard:
listener.whisperMultiplier = 0.2f; // Whisper: 2m (10 * 0.2)
listener.normalMultiplier = 0.5f; // Normal: 5m (10 * 0.5)
listener.loudMultiplier = 1.0f; // Loud: 10m (10 * 1.0)
listener.shoutMultiplier = 2.0f; // Shout: 20m (10 * 2.0)The effective hearing range is calculated automatically based on player’s current volume:
- Louder speech = larger hearing range
- Quieter speech = smaller hearing range
- Smooth interpolation between volume levels
Listener Properties
Current Hearing State
// Is the listener currently hearing the player?
if (listener.CanHearPlayer)
{
Debug.Log("Currently hearing player!");
}Distance Information
// Get distance to player
float distance = listener.DistanceToPlayer;
// Get effective hearing range (based on current player volume)
float range = listener.EffectiveHearingRange;
// Get last known player position
Vector3 lastPos = listener.LastKnownPlayerPosition;Volume Information
// Get the decibel level when player was heard
float db = listener.HeardDecibelLevel;Events
VoiceListener provides UnityEvents for integration with both code and Inspector.
OnPlayerHeard
Fires when player becomes audible:
void Start()
{
listener.OnPlayerHeard.AddListener(OnHeardPlayer);
}
void OnHeardPlayer(Vector3 position, float decibelLevel)
{
Debug.Log($"Heard player at {position} with {decibelLevel} dB");
// React to player voice
InvestigatePosition(position);
}Event Parameters:
position- Player’s 3D position when hearddecibelLevel- Volume in decibels (-60 to 0 dB)
OnPlayerLost
Fires when player becomes inaudible:
void Start()
{
listener.OnPlayerLost.AddListener(OnLostPlayer);
}
void OnLostPlayer()
{
Debug.Log("Lost the player");
// Return to normal behavior
ReturnToPatrol();
}OnDecibelLevelChanged
Fires when volume changes significantly while hearing player:
void Start()
{
listener.OnDecibelLevelChanged.AddListener(OnVolumeChanged);
}
void OnVolumeChanged(float newDecibelLevel)
{
Debug.Log($"Player volume changed to {newDecibelLevel} dB");
// React to volume changes
if (newDecibelLevel > -15f)
{
// Player started shouting - become more aggressive
IncreaseAggression();
}
}Line of Sight
Require line of sight to hear player (sound blocked by walls):
[SerializeField] private VoiceListener listener;
void Start()
{
listener.requireLineOfSight = true;
listener.occlusionLayers = LayerMask.GetMask("Walls", "Obstacles");
}When enabled:
- Raycasts from listener to player
- If raycast hits occlusion layer, player is not heard
- Perfect for realistic sound propagation
Multiple Listeners
VoiceReactManager efficiently updates all listeners in your scene.
Get All Listeners
// Get all registered listeners
var allListeners = VoiceReactManager.Instance.GetAllListeners();
Debug.Log($"Total listeners: {allListeners.Count}");Get Listeners Who Can Hear
// Get only listeners currently hearing the player
var hearingListeners = VoiceReactManager.Instance.GetListenersWhoCanHear();
foreach (var listener in hearingListeners)
{
Debug.Log($"{listener.gameObject.name} can hear the player");
}From VoiceReactController
[SerializeField] private VoiceReactController playerVoice;
void Update()
{
if (playerVoice.IsPlayerSpeaking)
{
// Get all enemies that can hear this player
var hearingEnemies = playerVoice.GetListenersWhoCanHear();
if (hearingEnemies.Count > 0)
{
Debug.Log($"{hearingEnemies.Count} enemies can hear you!");
}
}
}Performance Optimization
VoiceReactManager automatically optimizes listener updates:
Update Frequency
Control how often listeners check if they can hear:
var manager = VoiceReactManager.Instance;
// Update 30 times per second (default)
manager.updateFrequency = 30f;
// Lower frequency for better performance
manager.updateFrequency = 15f;
// Higher frequency for more responsive detection
manager.updateFrequency = 60f;Update Only While Speaking
Skip updates when player is silent:
// Only update when player is actively speaking (default: true)
manager.onlyUpdateWhileSpeaking = true;This optimization:
- Skips all listener updates when player is silent
- Reduces CPU usage significantly
- Recommended for most games
Common Patterns
Alert Meter
Build tension by tracking how many enemies hear you:
using UnityEngine;
using Peek.VoiceReact;
public class AlertSystem : MonoBehaviour
{
[SerializeField] private VoiceReactController playerVoice;
[SerializeField] private float maxAlertLevel = 100f;
private float currentAlert = 0f;
void Update()
{
int hearingEnemies = playerVoice.GetListenersWhoCanHear().Count;
if (hearingEnemies > 0)
{
// Increase alert based on number of enemies
currentAlert += hearingEnemies * Time.deltaTime * 10f;
}
else
{
// Decrease alert when not being heard
currentAlert -= Time.deltaTime * 5f;
}
currentAlert = Mathf.Clamp(currentAlert, 0f, maxAlertLevel);
if (currentAlert >= maxAlertLevel)
{
TriggerGameOver();
}
}
}Closest Listener
Find the closest enemy that can hear you:
VoiceListener FindClosestListener()
{
var hearingListeners = VoiceReactManager.Instance.GetListenersWhoCanHear();
if (hearingListeners.Count == 0)
return null;
VoiceListener closest = hearingListeners[0];
float closestDist = closest.DistanceToPlayer;
foreach (var listener in hearingListeners)
{
if (listener.DistanceToPlayer < closestDist)
{
closest = listener;
closestDist = listener.DistanceToPlayer;
}
}
return closest;
}Volume-Based AI States
Different reactions based on volume:
void OnHeardPlayer(Vector3 position, float decibelLevel)
{
if (decibelLevel > -10f)
{
// Shout - immediate attack
aiState = AIState.Attack;
agent.SetDestination(position);
}
else if (decibelLevel > -20f)
{
// Loud - investigate quickly
aiState = AIState.Investigate;
agent.speed = 5f;
agent.SetDestination(position);
}
else if (decibelLevel > -30f)
{
// Normal - cautious investigation
aiState = AIState.Investigate;
agent.speed = 3f;
agent.SetDestination(position);
}
else
{
// Whisper - look around but don't move
aiState = AIState.Alert;
LookTowards(position);
}
}Hearing Memory
Remember player position even after they go silent:
public class EnemyHearingMemory : MonoBehaviour
{
private VoiceListener listener;
private Vector3 lastKnownPosition;
private float memoryDuration = 10f;
private float memoryTimer = 0f;
void Start()
{
listener = GetComponent<VoiceListener>();
listener.OnPlayerHeard.AddListener(OnHeardPlayer);
listener.OnPlayerLost.AddListener(OnLostPlayer);
}
void Update()
{
if (memoryTimer > 0)
{
memoryTimer -= Time.deltaTime;
if (memoryTimer <= 0)
{
// Forgot player position
OnMemoryFaded();
}
}
}
void OnHeardPlayer(Vector3 position, float db)
{
lastKnownPosition = position;
memoryTimer = memoryDuration;
}
void OnLostPlayer()
{
// Still remember position for memoryDuration seconds
Debug.Log("Lost player, but I remember where they were...");
}
void OnMemoryFaded()
{
Debug.Log("Forgot where player was");
ReturnToPatrol();
}
}Debug Visualization
VoiceListener includes Scene view gizmos:
[SerializeField] private bool showDebugGizmos = true;Gizmos show:
- Current effective range (red when hearing, gray when not)
- Base hearing range (white wireframe)
- Line to player (when currently hearing)
- Volume range spheres (when selected):
- Blue = Whisper range
- Green = Normal range
- Orange = Loud range
- Red = Shout range
Remote Player Voice (Networking)
Process voice from remote players over network:
// Called when receiving voice data from network
public void OnRemotePlayerVoiceReceived(float normalizedVolume, float distance)
{
// Let listener process remote player voice
listener.ProcessRemotePlayerVoice(normalizedVolume, distance);
}See Networking for full multiplayer integration.