top of page

Jan Dzyr

Sound Designer & Production Sound Mixer

FMOD Area Emitter (Unity)

Writer's picture: Jan DzyrJan Dzyr

It's made for creating 3D sound sources based on large object or areas like forest, river etc, things that might not sound proper by just single 3D event placed in the world as it would be 'too directional' in a site that would feel not correct.


Script made and tested in Unity 2022.2.8f1

Script makes use of three elements:

  • FMOD Event - it has to be FMOD 3D event, and as such it works fine for all channel configuration

  • Collider (or if "useChildColliders" is active - set of child objects with colliders) - set as a trigger

  • FMOD listener (Player) - or more precisely it's position, here it's the same as player's as it's FPP game - it's also detected by 'Player' tag,


For TPP game:

  • Modify the names of variables: player, distanceToPlayer, playerPosition; for ones that are more suitable for your game

  • Awake function in its first part checks for 'player' object if it's assigned by user, if not it search for it by "Player" tag - modify it according to how you position listener on objects

How it works:

  • Basic functionality of AreaEmitter is updating the position of FMOD event based on the player position.

  • Point for updated position is based on the closest point of Collider which will be the Player's position if he is inside the area

  • When Player is inside Area the distance of attenuation should be below minimum one (position can be bit delayed - keep that in mind while setting up attenuation curve) Event will be played as it would be 2D - preserving the original channel configuration and spatialization.

  • Script keeps attention to the distance between Player and Emitter - if its above the maximum distance set in FMOD it will not update the position of event

  • Script visualize the emitter point in gizmos by a red sphere moving in the collider

  • Basic version uses single Collider component for the emitter point, but while using "useChildColliders" it allows to use more complex structure of multiple colliders placed on the child objects (keep in mind narrow corners, as one wall on the left, might be as close to the wall on right and make sound jump between them)



using FMOD.Studio;
using FMODUnity;
using UnityEngine;

public class FmodAreaEmitter : MonoBehaviour
{
    [Header("FMOD")]
    [SerializeField] private EventReference fmodEvent;

    [Header("Variables (can be detected automatically)")]
    [SerializeField] private GameObject player;
    [SerializeField] private Collider emitterCollider;

    [Header("Do you want to child colliders? Requires Rigidbody")]
    [SerializeField] private bool useChildColliders;
    [SerializeField] private Collider[] emitersChildColliders;

    private float distanceToPlayer;

    private Vector3 closestPoint;
    private Vector3 emitterPoint;
    private Vector3 playerPosition;

    private EventInstance AmbientEmitter;
    private EventDescription AudioDes;
    private float maxDistance;

    #region MonoBehaviour
    private void Awake()
    {   //In Awake script checks if it has neccesary objects to be operational, in other case it will disable itself                                            
        //it looks for Player (NOT CORRECT IF YOUR LISTENER POSITION IS NOT ON PLAYER) and collider/child colliders (depending on your use of single or multiple colliders in more complex shapes)

        if (player == null)
        {
            player = GameObject.FindGameObjectWithTag("Player");

            if (player == null)
            {
                Debug.LogWarning("FMOD Area Emitter could not detect a player. Will be disabled now.");
                GetComponent<FmodAreaEmitter>().enabled = !GetComponent<FmodAreaEmitter>().enabled;
            }
        }
        if (useChildColliders)
        {
            emitersChildColliders = GetComponentsInChildren<Collider>();

            if (emitersChildColliders == null)
            {
                Debug.LogWarning("FMOD Area Emitter could not detect a colliders on object. Will be disabled now.");
                GetComponent<FmodAreaEmitter>().enabled = !GetComponent<FmodAreaEmitter>().enabled;
            }
        }
        else if (emitterCollider == null)
        {
            if (TryGetComponent<Collider>(out emitterCollider) == false)
            {
                Debug.LogWarning("FMOD Area Emitter could not detect a colliders on object. Will be disabled now.");
                GetComponent<FmodAreaEmitter>().enabled = !GetComponent<FmodAreaEmitter>().enabled;
            }
        }
    }

    private void Start()
    {
        AmbientEmitter = RuntimeManager.CreateInstance(fmodEvent);

        AudioDes = RuntimeManager.GetEventDescription(fmodEvent);
        AudioDes.getMinMaxDistance(out float minDistance, out maxDistance);         //As we don't need the minimal value and there is no function to get only max distance, minDistance is just declared here and never used

        FindPointForEmitter();

        UpdateEmitter3dPosition();
        AmbientEmitter.start();
    }

    private void FixedUpdate()
    {
        playerPosition = player.transform.position;
        FindPointForEmitter();
        if (distanceToPlayer > maxDistance || distanceToPlayer < 0)                 //When Player is too far, there is no point for further calculations
            return;

        UpdateEmitter3dPosition();
    }

    private void OnDestroy()        //To make sure we will stop this sound from playing after we change level we got to stop it and release, if disabling function copy that also to "OnDisable", and start sound on "OnEnable"
    {
        AmbientEmitter.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
        AmbientEmitter.release();
    }
    #endregion

    private void FindPointForEmitter() //audio will behave like 3D source for which we shall provide a position that will be feeling more natural especially for large areas - the closest point between player and area
    {
        if (useChildColliders)
            GetDistanceToChildColliders();
        else
            GetDistanceToCollider();

        emitterPoint = closestPoint;
    }

    private void GetDistanceToCollider()
    {
        closestPoint = emitterCollider.ClosestPoint(playerPosition);
        distanceToPlayer = Vector3.Distance(closestPoint, playerPosition);
    }

    private void GetDistanceToChildColliders()
    {
        float x = 100f;         //closest distance among checked colliders
        float y = 0f;           //distance of collider checked at the moment
        Vector3 newClosestPoint = closestPoint;

        foreach (Collider edge in emitersChildColliders)        //The loop that basically checks which of the child colliders is closest to the player
        {
            Vector3 newPoint = edge.ClosestPoint(playerPosition);
            y = Vector3.Distance(newPoint, playerPosition);

            if (x > y)
            {
                newClosestPoint = newPoint;
                x = y;
            }
        }

        closestPoint = newClosestPoint;
        distanceToPlayer = x;
    }

    private void UpdateEmitter3dPosition()
    {
        AmbientEmitter.set3DAttributes(FMODUnity.RuntimeUtils.To3DAttributes(emitterPoint));
    }

    private void OnDrawGizmos()                 //This part simply allows us to see the emitter point in Unity with Gizmos enabled
    {
        Gizmos.DrawSphere(emitterPoint, 0.5f);
    }
}


Site created by: Jan Dzyr

bottom of page