FMOD Unity Scripting: The Basics

A walk-through of the core functions of the FMOD Unity package.

Colin Vandervort
7 min readJan 2, 2020

There is a fair bit of documentation floating around on FMOD Unity integration and scripting, but much of it is scattered, disorganized and buried in forum posts or videos.

For my current project, I’ve spent a fair bit of time digging for specific, targeted, but generally simple solutions. This document will be a summary of these “basics,” with simple scripting examples and brief commentary on use cases. I won’t, however, be covering Unity functionality, and will be writing with a certain degree of assumed familiarity of FMOD Studio terminology and functionality.

My hope is that this documentation will alleviate the burden of digging for simple FMOD scripting solutions in the future.

Cheat Sheet:

A quick list of the common FMOD Unity functions.

Playing a Sound:

The following script will play a single sound event as a 2D sound (Stereo/non-positional), whenever the PlayASound function is called.

Additionally, placing the PlayOneShot() in void Awake(), void Start(), or void OnEnable() are simple ways to start playing a sound event when an object is created or enabled. An example use-case for this is projectile firing SFX. In the cases where projectiles are being created and destroyed, void Start() is usually the correct place to call the play event from, whereas if they’re being pre-instantiated (pooled) and enabled on use, OnEnable() becomes the correct alternative.

Throughout the rest of this article, I’ll assume you know which functions you’d like to call your sound events from; some clarity on the above possibilities has been included to assist anyone that’s just starting out with Unity scripting.

Positioning a Sound:

The first and simplest way to play a sound at a specified (fixed) location is to use the position (Vector3) overload for FMOD’s PlayOneShot function.

Note: In the example, I’m directly specifying a location, but you can also grab the location of an object using transform.position. The transform.position variable is also a Vector3.
Additionally,
PlayOneShot will not dynamically track object positions. It will anchor to the location that it is given when the function is called. Some of the following (heavier) options should be considered for dynamic positioning.

For many purposes, PlayOneShotAttached is the best solution for playing a sound that tracks an object’s position. It functions similarly to PlayOneShot, but instead of playing the sound at a fixed location, it will track the position of the object it’s attached to for the lifetime of the sound event.

PlayOneShot and PlayOneShotAttached generally cover the bulk of simple one-shot sound event management, however neither of these strategies allow easy access (or much of any access) to the sound events once they’ve begun. Many FMOD functions require or are greatly simplified by access to a sound event instance after it has been fired off. A few example scenarios include stopping loop playback, keying off sustain points, and setting FMOD parameters on specified sound events. I’ll be covering each of these below.

Tracking Event Instances:

In many cases (loops, cues, parameters, etc…), we need access to a sound event after playback begins. In these cases, instead of using the PlayOneShot functions, we’ll store an EventInstance reference by calling CreateInstance on the event name (string path). To begin playback of the event, we simply have to call start() on the EventInstance. Note that whenever a sound is instantiated with CreateInstance, it must also be released at some point, so as to avoid memory leaks. Releasing an EventInstance will mark the event for destruction, but it will only be destroyed once it has stopped playing.

Returning to our previous topic for a moment, creating and starting playback of an EventInstance still lacks positional data. If we wish to track the position of an object we can either attach the instance to the object using AttachInstanceToGameObject() or manually set an object and rigid body (to track position and velocity) using set3DAttributes(). Generally the first option is all that’s needed.

Note: Storing event instances on objects that may exist many times within a scene can come with a significant memory overhead. That being said, for sounds that are being fired off frequently, storing an instance of the event may reduce impact on performance.

Looping Sounds:

Another use for tracking event instances presents itself when looping the playback of sounds. FMOD handles looping sound events in exactly the same manner that it handles one-shot events. With that said, we’ll almost always want to stop a loop at some point. In order to do this, we need to either call stop on the bus/group that the sound is playing through (more on this later), destroy the object the sound is attached to or reload the scene or part of the scene that the object is in (rarely the best approach), transition out of the looping segment, or call a stop() event on the event instance itself.
The final two options tend to provide the most flexibility, and in both cases (usually) require an event instance reference to call the necessary functions on.

Stopping Sounds:

As mentioned above, looping sounds and one-shot sounds, as well as mixer snapshots are all treated as events, and can be stopped in an identical manner. This method requires an EventInstance reference, and can stop in one of two modes; an immediate stop mode, or a fade-out mode where the fade curve is a matching volume AHDSR “release” curve on the sound event in FMOD.

It’s worth noting that if a sound or group of sounds needs to be stopped all at once, but you don’t have access to the references for any or all of them, a stop call can be called on all events on a given bus/mix group. I believe there’s a fair bit of computational overhead to this, but it is highly useful for infrequent calls such as a level or map change, or transitions between different game-states (eg. overworld to battle in a turn based game).

Calling Cues (keying off sustain points):

Although the uses can stem far beyond just this, one common use for sustain points in FMOD is to allow for looping of asynchronous audio clips. Regardless of the use case, a sustain point must be keyed off in order to continue past it within FMOD. To execute this, we simply call triggerCue() on the event with the cue. Again, an EventInstance reference is needed for this action.

Note: When triggering cues before the playhead has reached the next sustain point, that sustain point will be ignored. Each triggerCue() call will only key off the next sustain point in sequence, however multiple triggerCue() calls can be buffered. Because of this interaction, it is sometimes beneficial to use destination markers and transition regions when additional complexity or accuracy is required. Transition regions respond to FMOD parameter assignments, and wait for specific conditions to be met within those parameters.

Setting Parameters:

Parameters are the foundation of much of FMOD’s adaptive audio capabilities and could easily demand an article entirely of their own (this may happen in the future, if there’s a desire for it). This section, as well as the following, will assume you have some understanding of the FMOD parameter workflow, and will focus specifically on their scripting implementation strategies.

Despite their complexity, FMOD parameter scripting is relatively simple. As with cues, an EventInstance reference is required for local parameter access on individual FMOD events. — Beyond this, the event just needs to be playing. Starting an event, just set parameter on first frame after start call. This applies specifically to sounds that are started within script.

Note: setParameterByID() can be used in place of setParameterByName(), however capturing a parameter ID is often more work than simply querying by name, and requires a string query as well. That being said, once the ID has been captured, reusing it rather than querying for a string each function call may provide a performance boost. Additionally, there exists a plural setParametersByIDs() function that can act upon an array of parameter identifiers all at once.

Emitter References:

For sounds started from a Studio Event Emitter component, we instead, capture them with an EmitterRef variable. From there, we can begin playback using the emitter Target.Play() function, and can set editor-exposed parameters through Target.SetParameter().

Note: I personally don’t prefer using EmitterRef variables when I can avoid it, as despite the additional editor exposure, manual entry of case sensitive strings is still required, and there’s an additional burden of keeping track of the Params[] index value for the parameter that you’re seeking to make changes to.

Global Parameters:

Global parameters are a more recent addition to FMOD, and unlike local parameters, do not associate with any event in particular. Due to this, they also don’t require any references to event instances, and can effectively change FMOD Event properties from just about anywhere in your code. In my opinion, any parameters that don’t need to apply differently to separate copies of an event should be global. Sweeping project state changes should almost always be conveyed as global parameters, however parameters that are limited to a single instance of an event can also make good organizational use of globalization.

Unlike local parameters, global parameters can be accessed in the editor using the ParamRef attribute. Additionally, as with the local setParameter functions, global parameters can also be set by ID and by array of IDs.

Mixer/Group Snapshots:

Mixer snapshots, or group snapshots (as FMOD refers to them), function essentially identically to sound events, and can be thought of as a sort of global sound event. With that said, they behave more similarly to looping sounds than one-shot sounds in that they will persist until they are stopped, manually or forcibly. Fades can be applied to mixer snapshots via attack and release time (AHDSR) on the various “Snapshot Macros” “Intensity Meter,” as well as on any of the altered parameters within the snapshot (though less consistently when complexity is present). As with standard stop events, the release fades can also be bypassed using the immediate stop mode.

Event Callbacks:

Event callbacks are a unique beast. They can be used to track and respond to just about any state change within an event. Most notably to me, however, is their ability to track timeline markers and tempo. If you’re looking to build systems that respond to music, this is a good starting point.

Due to the complexity of this topic, I’ll save it for it’s own write-up. Here is an excellent summary by Alessandro Famà for beat and marker systems specifically.

Please reach out and let me know if anything in this document is confusing or feels incorrectly placed and I’ll do my best to get back to you and amend the document as needed. Best of luck!

Up to date as of FMOD version 2.00.05

--

--