Doppler effect FMODUnity Integration not working

<NOTE: I am using FMOD Unity 1.09. I am not sure whether or not this fix is still required for 1.10+>

Hi All,

I know this is a very old post now, but I found myself asking the exact same question yesterday, so thought I’d leave my solution here for others to try.
In order for your events’ doppler effects to work without rigidbodies, all that FMOD really needs is an FMOD.ATTRIBUTES_3D struct describing world-space velocity of the moving object, and the same for the listener.
To achieve this, we can overload some functions in the FMOD C# scripts.

First, create a class that has the same members as a Vector3. In C#, classes are reference types by default. Using a class instead of a struct will simplify our method overloading, and slightly reduce the amount of code we need to re-write. I declared my class in RuntimeUtils.cs:

public class CVector3
{
public float x = 0;
public float y = 0;
public float z = 0;
}

Find RuntimeUtils.To3DAttributes, and overload it so it can take an instance of our class as a parameter. Add the following to RuntimeUtils.cs:

    public static FMOD.ATTRIBUTES_3D To3DAttributes(Transform transform, CVector3 kinematicVelocity)
    {
        FMOD.ATTRIBUTES_3D attributes = transform.To3DAttributes();
        FMOD.VECTOR vel;
        vel.x = kinematicVelocity.x;
        vel.y = kinematicVelocity.y;
        vel.z = kinematicVelocity.z;

        attributes.velocity = vel;

        return attributes;
    }

Next, we are going to expand the AttachedInstance class (found in RuntimeManager.cs) so that it can accommodate our CVector3 reference:

class AttachedInstance
{
public FMOD.Studio.EventInstance instance;
public Transform transform;
public Rigidbody rigidBody;
public Rigidbody2D rigidBody2D;
public CVector3 kinematicVelocity; //<----------Add this line
}

We can now add the following overload of RuntimeManager.AttachInstanceToGameObject:

    public static void AttachInstanceToGameObject(FMOD.Studio.EventInstance instance, Transform transform, CVector3 kinematicVelocity)
    {
        var attachedInstance = new AttachedInstance();
        attachedInstance.transform = transform;
        attachedInstance.instance = instance;
        attachedInstance.rigidBody2D = null;
        attachedInstance.rigidBody = null;
        attachedInstance.kinematicVelocity = kinematicVelocity;
        Instance.attachedInstances.Add(attachedInstance);
    }

In RuntimeManager.cs, locate the Update() method. About half way down you will see this loop:

             for (int i = 0; i < attachedInstances.Count; i++)
             //...etc

At the bottom of the for loop, change this…

                if (attachedInstances[i].rigidBody)
                {
                    attachedInstances[i].instance.set3DAttributes(RuntimeUtils.To3DAttributes(attachedInstances[i].transform, attachedInstances[i].rigidBody));
                }
                else
                {
                    attachedInstances[i].instance.set3DAttributes(RuntimeUtils.To3DAttributes(attachedInstances[i].transform, attachedInstances[i].rigidBody2D));
                }

To this…

                if (attachedInstances[i].rigidBody)
                {
                    attachedInstances[i].instance.set3DAttributes(RuntimeUtils.To3DAttributes(attachedInstances[i].transform, attachedInstances[i].rigidBody));
                }
                else if (attachedInstances[i].rigidBody2D)
                {
                    attachedInstances[i].instance.set3DAttributes(RuntimeUtils.To3DAttributes(attachedInstances[i].transform, attachedInstances[i].rigidBody2D));
                }
                else if (attachedInstances[i].kinematicVelocity != null)
                {
                    attachedInstances[i].instance.set3DAttributes(RuntimeUtils.To3DAttributes(attachedInstances[i].transform,attachedInstances[i].kinematicVelocity));
                }

Here we are saying that our Doppler effect can fall back to kinematic velocity if there are no Rigidbodies associated with that instance.

We have sorted out everything we need to convert our CVector3 data into an FMOD.ATTRIBUTES_3D struct. However, we still need to fill in the kinematic velocity each frame.
To do this we need to make some changes to StudioEventEmitter.cs.

First, add and initialise the following class members to StudioEventEmitter:

CVector3 kinematicVelocity = null;
Vector3 positionLastFrame = Vector3.zero;

Next, locate the Play() method and find this piece of code:

            if (is3D)
            {
                var rigidBody = GetComponent<Rigidbody>();
                var transform = GetComponent<Transform>();
                instance.set3DAttributes(RuntimeUtils.To3DAttributes(gameObject, rigidBody));
                RuntimeManager.AttachInstanceToGameObject(instance, transform, rigidBody);
            }

Change it to this:

            if (is3D)
            {
                var rigidBody = GetComponent<Rigidbody>();
                var transform = GetComponent<Transform>();
                
                if (!rigidBody)
                {
                    kinematicVelocity = new CVector3();
                    instance.set3DAttributes(RuntimeUtils.To3DAttributes(this.transform, kinematicVelocity));
                    RuntimeManager.AttachInstanceToGameObject(instance, transform, kinematicVelocity);
                }
                else
                {
                    instance.set3DAttributes(RuntimeUtils.To3DAttributes(gameObject, rigidBody));
                    RuntimeManager.AttachInstanceToGameObject(instance, transform, rigidBody);
                }
            }

…StudioEventEmitter will now send kinematic data if a Rigidbody is not found.

Next, add the following method to StudioEventEmitter.cs:

    void setKinematicVelocity()
    {
        //Get current velocity
        Vector3 currentVel;
        currentVel.x = kinematicVelocity.x;
        currentVel.y = kinematicVelocity.y;
        currentVel.z = kinematicVelocity.z;

        //Update to new velocity
        currentVel = Vector3.Lerp(currentVel, (this.transform.position - positionLastFrame) / Time.deltaTime, Time.deltaTime * 15); //A very short lerp prevents jitter

        //Reassign to CVector3 object
        kinematicVelocity.x = currentVel.x;
        kinematicVelocity.y = currentVel.y;
        kinematicVelocity.z = currentVel.z;

        //Store world position for next frame
        positionLastFrame= this.transform.position;
    }

Call the above method it in Update(), but ONLY if kinematicVelocity is non-null. If it is null, this indicates that a rigidbody was found, and kinematicVelocity should be ignored.

void Update()
{
if (kinematicVelocity != null)
{
setKinematicVelocity();
}
}

We’re almost done. However, Doppler effect should be calculated from the point of the listener, who may also be moving. We therefore need to apply similar changes to the StudioListener.cs script.

First, add the two members, and setKinematicVelocity() method, in the exact same manner as with StudioEventEmitter.cs:

public CVector3 kinematicVelocity = null;
Vector3 positionLastFrame;

    void setKinematicVelocity()
    {
        //Get current velocity
        Vector3 currentVel;
        currentVel.x = kinematicVelocity.x;
        currentVel.y = kinematicVelocity.y;
        currentVel.z = kinematicVelocity.z;

        //Update to new velocity
        currentVel = Vector3.Lerp(currentVel, (this.transform.position - positionLastFrame) / Time.deltaTime, Time.deltaTime * 15);

        //Reassign to CVector3 object
        kinematicVelocity.x = currentVel.x;
        kinematicVelocity.y = currentVel.y;
        kinematicVelocity.z = currentVel.z;

        //Store world position for next frame
        positionLastFrame = this.transform.position;
    }

…Call setKinematicVelocity() in the Update method for StudioListener, again checking for nulls:

void Update()
{
if (kinematicVelocity != null)
{
setKinematicVelocity();
}
//…etc
}

In the OnEnable() method, we are going to create a CVector3 IF there are no rigidbodies on the StudioListener game object:

    void OnEnable()
    {
        RuntimeUtils.EnforceLibraryOrder();
        rigidBody = gameObject.GetComponent<Rigidbody>();
        rigidBody2D = gameObject.GetComponent<Rigidbody2D>();

     //<-----Add this part here--------->
        if (!rigidBody && !rigidBody2D)
        {
            kinematicVelocity = new CVector3();
        }
     //<---------------------------------->

         //...etc
    }

In RuntimeManager.cs, we need to add one last overload. That is to let SetListenerLocation() work with CVector3 objects. Add this overload to RuntimeManager.cs:

    public static void SetListenerLocation(int listenerIndex, GameObject gameObject, CVector3 kinematicVelocity)
    {
        Instance.studioSystem.setListenerAttributes(listenerIndex, RuntimeUtils.To3DAttributes(gameObject.transform, kinematicVelocity));
    }

Finally, StudioListener.cs will also have a method called SetListenerLocation(). Remove it, and replace it with this version:

    void SetListenerLocation()
    {
        if (rigidBody)
        {              
            RuntimeManager.SetListenerLocation(ListenerNumber, gameObject, rigidBody);
        }
        else if (rigidBody2D)
        {
            RuntimeManager.SetListenerLocation(ListenerNumber, gameObject, rigidBody2D);
        }
        else
        {
            RuntimeManager.SetListenerLocation(ListenerNumber, gameObject, kinematicVelocity);
        }
    }

You should now have made all the adjustments to the FMOD Unity wrapper to allow you to supply your events with kinematic velocity.

If the above changes don’t work, I’ve likely missed something out… I’ve checked it over as much as I can right now, but my FMOD Unity install is heavily modified and I was trying to leave out other stuff that made no sense in this context, while still trying to remember all the steps required to do this.

Leave a reply if it helps you. Also leave a reply if it doesn’t, along with any errors, and I’ll fix it asap!

In any case, hopefully you understand what I’m doing here, and if so, you can make your own modifications depending on your needs.

Happy programming
Geoff