Similar to the previous hand collision tutorial, we want to give a sound to objects that collide with the world. Dropping a book or a can to the floor or to a wall and hearing a sound based on the speed of the object can indeed be satisfying. In this tutorial we will learn how to do exactly that by using FMOD, Unity and writing a code in C#. The results of this tutorial can be previewed in this example video:
Preparation in FMOD
As an example I will use the audio interaction on a book. But you can follow this tutorial using an object that you like. I created a new event and named it book_collision. I then created a continuous parameter called Force with a range from 0 to 1 and placed my samples inside that game parameter, automating the volume along these values:
Add a Resonance Audio Source to the master track similar to the last tutorial:
Preparation in Unity
I’m using Oculus’s OVRPlayerController in combination with the LocalAvatar, AvatarGrabberLeft and AvatarGrabberRight prefabs to handle the grabbing interaction of objects. That is easy to set up and just works. For reference, the player GameObjects setup looks like this in my hierarchy:
Create a cube and scale it similar to the shape of a book. I downloaded a free model from the Asset Store for reference:
We now add a Rigidbody, a Box Collider and a OVRGrabbable component to the GameObject. The Rigidbody needs to use gravity, as we want to throw the book around. The OVRGrabbable component enables the grabbing behavior for this particular object. We need to create and assign tags to the objects our book or cube will collide with. I gave the whole room the tag RoomCollision and assigned the tag Table to the table.
Finally we create a new script for the audio collision behaviour. I named it AudioObjectCollision.cs and added it to the GameObject.
C# code for the collision behaviour
Here begins the fun part again! Let’s start by declaring a few variables:
FMOD.Studio.EventInstance instance;
[FMODUnity.EventRef]
public string fmodEvent = null;
[SerializeField]
private string[] collisionTag = null;
[SerializeField]
private bool useParameter;
[SerializeField]
private string parameterName;
We declared the usual FMOD instance and a path to the event we want to play. We create an array of String variables in which we can specify all the objects having a specific tag that will react to the collision with our object. We declare the bool variable useParameter to check if we want to pass a parameter to the audio playing and a string named parameterName to able to set the name of our event parameter directly in the inspector.
Similar to the hand collision tutorial, we calculate the “impact volume” of an object by using a Cubic Ease-Out method that will return a more consistent float value that ranges between 0f to 1f depending on how fast the object collides with the world. We will use that value to drive the Force parameter:
private float CalculateImpactVolume(float speed)
{
float volume;
volume = CubicEaseOut(speed);
return volume;
}
private float CubicEaseOut(float velocity, float startingValue = 0, float changeInValue = 1)
{
return changeInValue * ((velocity = velocity / maxCollisionVelocity - 1) * velocity * velocity + 1) + startingValue;
}
After that we use the OnCollisionEnter() callback method to detect a collision and play the sound:
private void OnCollisionEnter(Collision collision)
{
foreach (string col in collisionTag)
{
if (collision.gameObject.tag == col)
{
float parameterValue = CalculateImpactVolume(collision.relativeVelocity.magnitude);
if (parameterValue < minCollisionVolume)
{
return;
}
instance = FMODUnity.RuntimeManager.CreateInstance(fmodEvent);
instance.set3DAttributes(FMODUnity.RuntimeUtils.To3DAttributes(gameObject));
if (useParameter)
{
instance.setParameterByName(parameterName, parameterValue);
}
instance.start();
instance.release();
}
}
}
With the foreach
statement we loop through the tags we will insert in the inspector. We check if the collision object tag equals each string in collisionTag. If that is true we create the FMOD instance, set the position of the sound to the GameObject’s position using set3DAttributes()
, check if we are using a parameter, and if that is true, we calculate the impact volume using the magnitude of the relative velocity of our collision object. We set the parameter, start and release the instance.
Let’s have a look at the script inside the inspector:
We can see the variables we declared at the start of the script: in the Collision Tag string array we insert the tags of the objects the book should collide with. We check the Use Parameter if we want to have a dynamic sound based on the velocity of the object, insert the parameter name inside Parameter Name. We can play with the Min Collision Volume and Max Collision Velocity variables to tweak the play back behaviour of our sound.
Go into play mode, throw your object around and listen how the volume changes based on the velocity of the object. If you want to avoid picking up the object again or restarting the scene, you can add a simple script to reset the position of the object with a button press:
private Vector3 originalPosition;
void Start()
{
originalPosition = gameObject.transform.position;
}
void Update()
{
if (OVRInput.Get(OVRInput.Button.One, OVRInput.Controller.LTouch))
{
gameObject.transform.position = originalPosition;
}
}