Explorer
KNOW-PAT-101

FiveM - Sound System with Distance Attenuation : local, global, distance-based via NUI

Domaine
fivem
Type
pattern
Priorité
P2

Parent : [[INDEX-FIVEM-GAMEPLAY]]

FiveM - Sound System with Distance Attenuation : local, global, distance-based via NUI

Problème

Les sons dans FiveM doivent être spatialisés (entendu uniquement à proximité), et il faut éviter de charger des fichiers audio côté client sans contrôle serveur.

Solution

Système sonore via NUI SendNUIMessage avec 3 modes : local (une personne), global (tous), distance-based (Vdist), déclenché par events serveur.

-- server.lua : forwarding events
RegisterNetEvent('InteractSound_SV:PlayWithinDistance')
AddEventHandler('InteractSound_SV:PlayWithinDistance', function(maxDistance, soundFile, soundVolume)
    local src = source
    TriggerClientEvent('InteractSound_CL:PlayWithinDistance', -1, src, maxDistance, soundFile, soundVolume)
end)

RegisterNetEvent('InteractSound_SV:PlayOnAll')
AddEventHandler('InteractSound_SV:PlayOnAll', function(soundFile, soundVolume)
    TriggerClientEvent('InteractSound_CL:PlayOnOne', -1, soundFile, soundVolume)
end)

-- client.lua : distance check
RegisterNetEvent('InteractSound_CL:PlayWithinDistance')
AddEventHandler('InteractSound_CL:PlayWithinDistance', function(playerNetId, maxDistance, soundFile, soundVolume)
    local lCoords = GetEntityCoords(PlayerPedId())
    local eCoords = GetEntityCoords(GetPlayerPed(GetPlayerFromServerId(playerNetId)))
    local distIs = Vdist(lCoords.x, lCoords.y, lCoords.z, eCoords.x, eCoords.y, eCoords.z)
    
    if distIs <= maxDistance then
        SendNUIMessage({
            transactionType = 'playSound',
            transactionFile = soundFile,
            transactionVolume = soundVolume
        })
    end
end)

-- NUI HTML/JS (html/index.html)
window.addEventListener('message', function(event) {
    if (event.data.transactionType === 'playSound') {
        var audio = new Audio('sounds/' + event.data.transactionFile + '.ogg');
        audio.volume = event.data.transactionVolume || 0.5;
        audio.play();
    }
});

Pourquoi

  • Vdist est un calcul rapide de distance euclidienne
  • NUI HTML5 Audio est plus fiable que les natives GTA pour les fichiers custom
  • Le serveur contrôle QUI reçoit l'event (pas de broadcast inutile)

Quand l'utiliser

  • Sons d'ambiance (sirenes, klaxons, conversations)
  • Sons d'interaction (craft, ouverture de porte, notification proche)
  • Musique d'ambiance locale (bars, clubs)

Quand NE PAS l'utiliser

  • Musique de fond globale (preférer TriggerMusicEvent native)
  • Sons synchronisés précis (tirs d'armes → natives GTA)

Exemples

Correct

-- Son de klaxon avec attenuation par vehicule
RegisterNetEvent('vehicle:honk')
AddEventHandler('vehicle:honk', function(vehNetId)
    local src = source
    local veh = NetworkGetEntityFromNetworkId(vehNetId)
    local coords = GetEntityCoords(veh)
    
    -- Serveur broadcast à ceux dans le rayon
    for _, playerId in ipairs(GetPlayers()) do
        local ped = GetPlayerPed(playerId)
        local pCoords = GetEntityCoords(ped)
        if #(coords - pCoords) < 50.0 then
            TriggerClientEvent('InteractSound_CL:PlayWithinDistance', playerId, src, 50.0, 'honk', 0.8)
        end
    end
end)

Incorrect

-- Broadcast global sans distance → lag et immersion cassée
RegisterNetEvent('sound:play')
AddEventHandler('sound:play', function(sound)
    TriggerClientEvent('sound:playClient', -1, sound) -- Tout le monde entend tout
end)

Références

  • InteractSound/client/main.lua L69-80
  • InteractSound/server/main.lua L68-80