<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sound Design on DAD 424 Sound Design for Games</title><link>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/tags/sound-design/</link><description>Recent content in Sound Design on DAD 424 Sound Design for Games</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Tue, 21 Oct 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/tags/sound-design/index.xml" rel="self" type="application/rss+xml"/><item><title>Building Dynamic Footstep Sounds in Unity with FMOD and Raycasting</title><link>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/lectures/week-9/automating-audio-with-parameters-2/</link><pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate><guid>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/lectures/week-9/automating-audio-with-parameters-2/</guid><description>Create adaptive footsteps in Unity with FMOD using raycasts, surface tags, and an enum-backed parameter that maps directly to FMOD.</description><content>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Creating immersive game soundscapes often comes down to subtle details—like the sound of footsteps adapting to different surfaces. In this tutorial, we’ll expand upon previous FMOD parameter work by integrating Unity’s physics system to detect materials underfoot. Using &lt;strong>raycasting&lt;/strong> and &lt;strong>tags&lt;/strong>, we’ll dynamically alter footstep sounds to reflect the player’s environment—be it grass, stone, puddles, or earth.&lt;/p>
&lt;p>This guide walks through each step: tagging surfaces, scripting raycasts, and linking an enum-backed FMOD parameter for a responsive, data‑driven footstep system. If you follow along from top to bottom, you’ll go from a blank scene to fully adaptive footsteps.&lt;/p>
&lt;hr>
&lt;h2 id="recap-where-we-left-off">Recap: Where We Left Off&lt;/h2>
&lt;p>In the previous session, we created a simple FMOD event that played footstep sounds through Unity animation triggers. We:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Created a footstep event&lt;/strong> in FMOD with a “Material” parameter.&lt;/li>
&lt;li>&lt;strong>Wrote a Unity C# script&lt;/strong> to instantiate, play, and destroy the event on each step.&lt;/li>
&lt;li>&lt;strong>Linked animation events&lt;/strong> to ensure footstep sounds triggered precisely in sync.&lt;/li>
&lt;/ol>
&lt;p>Now, we’ll enhance that system so footsteps automatically change depending on what surface the player is walking or running across.&lt;/p>
&lt;hr>
&lt;h2 id="step-1-setting-up-tags-in-unity">Step 1: Setting Up Tags in Unity&lt;/h2>
&lt;p>Tags let you label objects and detect them in scripts. Each surface type gets a unique tag.&lt;/p>
&lt;ol>
&lt;li>Navigate to &lt;strong>Edit → Project Settings → Tags &amp;amp; Layers&lt;/strong>.&lt;/li>
&lt;li>Under &lt;strong>Tags&lt;/strong>, click &lt;strong>+&lt;/strong> and add:
&lt;ul>
&lt;li>Earth&lt;/li>
&lt;li>Stone&lt;/li>
&lt;li>Puddle&lt;/li>
&lt;li>Grass&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="alt text" src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-9/automating-audio-with-parameters-2/tags.png">&lt;/p>
&lt;p>Assign these tags to relevant scene objects—terrain, stairs, platforms, etc. Apply the tag to the collider you expect the raycast to hit (often on the same GameObject that has the Mesh Collider or Box Collider).&lt;/p>
&lt;p>&lt;img alt="alt text" src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-9/automating-audio-with-parameters-2/tag-stone.png">
Tagging even a few surfaces is enough to test. Later, we&amp;rsquo;ll expand this system to cover more materials using Physics Materials or Terrain Textures.&lt;/p>
&lt;hr>
&lt;h2 id="step-2-script-framework">Step 2: Script Framework&lt;/h2>
&lt;p>Start by defining an enum for surface types and the core fields/methods in your player script. Add this to your player controller (or a dedicated audio component on the player).&lt;/p>
&lt;blockquote>
&lt;p>An enum is a new data type we haven&amp;rsquo;t used yet. It lets us define a set of named constants, which we can use to represent discrete values (like surface types). This makes our code more readable and less error-prone than using raw integers or strings.&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> UnityEngine;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> FMODUnity;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> FMOD.Studio;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">enum&lt;/span> MaterialType { Earth = &lt;span style="color:#ae81ff">0&lt;/span>, Stone = &lt;span style="color:#ae81ff">1&lt;/span>, Puddle = &lt;span style="color:#ae81ff">2&lt;/span>, Grass = &lt;span style="color:#ae81ff">3&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">F_Player&lt;/span> : MonoBehaviour
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e"> [SerializeField]&lt;/span> &lt;span style="color:#66d9ef">private&lt;/span> LayerMask groundLayerMask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e"> [SerializeField]&lt;/span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> rayDistance = &lt;span style="color:#ae81ff">1.5f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> MaterialType currentMaterial;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> RaycastHit hitInfo;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Rigidbody rb;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> Awake()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> Update()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Step 4 will explain this debug line&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Debug.DrawRay(transform.position, Vector3.down * rayDistance, Color.red);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> PlayRunEvent(&lt;span style="color:#66d9ef">string&lt;/span> eventPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#66d9ef">string&lt;/span>.IsNullOrEmpty(eventPath)) &lt;span style="color:#66d9ef">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaterialCheck();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EventInstance run = RuntimeManager.CreateInstance(eventPath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuntimeManager.AttachInstanceToGameObject(run, gameObject, rb);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Step 6: we&amp;#39;ll create/start the FMOD event and set the parameter&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> MaterialCheck()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Steps 3–5: raycast and set currentMaterial&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Notes:&lt;/p>
&lt;ul>
&lt;li>Enum names must match tags exactly: &lt;code>Earth&lt;/code>, &lt;code>Stone&lt;/code>, &lt;code>Puddle&lt;/code>, &lt;code>Grass&lt;/code>.&lt;/li>
&lt;li>&lt;code>groundLayerMask&lt;/code> should include only layers containing walkable geometry.&lt;/li>
&lt;li>&lt;code>rayDistance&lt;/code> defaults to 1.5m; adjust to your character height.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="step-3-raycasting-to-detect-surfaces">Step 3: Raycasting to Detect Surfaces&lt;/h2>
&lt;p>The script casts a ray downward from the player to find the surface beneath:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> (Physics.Raycast(transform.position, Vector3.down, &lt;span style="color:#66d9ef">out&lt;/span> hitInfo, rayDistance, groundLayerMask))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// hitInfo.collider is the surface collider we tagged&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>transform.position&lt;/code> = Player position&lt;/li>
&lt;li>&lt;code>Vector3.down&lt;/code> = Direction toward the ground&lt;/li>
&lt;li>&lt;code>rayDistance&lt;/code> = Ray length (default 1.5m)&lt;/li>
&lt;li>&lt;code>groundLayerMask&lt;/code> = Limit detection to your environment layers (e.g., &lt;code>Environment&lt;/code>, &lt;code>WaterGeometry&lt;/code>)&lt;/li>
&lt;/ul>
&lt;p>In the Inspector, assign the &lt;code>groundLayerMask&lt;/code> to include only layers with tagged ground geometry.&lt;/p>
&lt;p>If you find gaps on stairs or steep terrain, consider a small sphere cast:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> (Physics.SphereCast(transform.position, &lt;span style="color:#ae81ff">0.1f&lt;/span>, Vector3.down, &lt;span style="color:#66d9ef">out&lt;/span> hitInfo, rayDistance, groundLayerMask))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Slightly more forgiving than a thin ray&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="step-4-visualize-the-ray">Step 4: Visualize the Ray&lt;/h2>
&lt;p>The script draws the ray each frame for easy debugging:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>Debug.DrawRay(transform.position, Vector3.down * rayDistance, Color.red);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>View this in the Scene window as you move the player to ensure correct origin and length.&lt;/p>
&lt;hr>
&lt;h2 id="step-5-tags--enum-mapping">Step 5: Tags → Enum Mapping&lt;/h2>
&lt;p>Inside &lt;code>MaterialCheck()&lt;/code> and &lt;code>Raycast&lt;/code> if statement, add this code to convert the detected tag into the enum value:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> (System.Enum.TryParse(hitInfo.collider.tag, &lt;span style="color:#66d9ef">out&lt;/span> MaterialType material))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentMaterial = material;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>TryParse: Attempts to map the tag (e.g., &amp;ldquo;Stone&amp;rdquo;) to &lt;code>MaterialType.Stone&lt;/code>.&lt;/li>
&lt;li>Safe update: Only assigns &lt;code>currentMaterial&lt;/code> when parsing returns true; otherwise the previous value remains.&lt;/li>
&lt;li>Optional default: If you want an explicit fallback when parsing fails, add an else:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> (System.Enum.TryParse(hitInfo.collider.tag, &lt;span style="color:#66d9ef">out&lt;/span> MaterialType material))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentMaterial = material;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentMaterial = MaterialType.Earth; &lt;span style="color:#75715e">// default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="step-6-fmod-parameter-setup">Step 6: FMOD Parameter Setup&lt;/h2>
&lt;p>The script sets the parameter via, which you&amp;rsquo;ll add inside &lt;code>PlayRunEvent&lt;/code> before starting the event:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>run.setParameterByName(&lt;span style="color:#e6db74">&amp;#34;Material&amp;#34;&lt;/span>, (&lt;span style="color:#66d9ef">float&lt;/span>)currentMaterial);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="step-7-full-script">Step 7: Full Script&lt;/h2>
&lt;p>Here’s the complete script with all steps combined:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> UnityEngine;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> FMODUnity;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> FMOD.Studio;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">enum&lt;/span> MaterialType
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Earth = &lt;span style="color:#ae81ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Stone = &lt;span style="color:#ae81ff">1&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Puddle = &lt;span style="color:#ae81ff">2&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Grass = &lt;span style="color:#ae81ff">3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">F_Player&lt;/span> : MonoBehaviour
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e"> [SerializeField]&lt;/span> &lt;span style="color:#66d9ef">private&lt;/span> LayerMask groundLayerMask;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e"> [SerializeField]&lt;/span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> rayDistance = &lt;span style="color:#ae81ff">1.5f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> MaterialType currentMaterial;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> RaycastHit hitInfo;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Rigidbody rb;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> Awake()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> Update()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Debug.DrawRay(transform.position, Vector3.down * rayDistance, Color.red);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> PlayRunEvent(&lt;span style="color:#66d9ef">string&lt;/span> eventPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#66d9ef">string&lt;/span>.IsNullOrEmpty(eventPath)) &lt;span style="color:#66d9ef">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaterialCheck();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EventInstance run = RuntimeManager.CreateInstance(eventPath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuntimeManager.AttachInstanceToGameObject(run, gameObject, rb);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.setParameterByName(&lt;span style="color:#e6db74">&amp;#34;Material&amp;#34;&lt;/span>, (&lt;span style="color:#66d9ef">float&lt;/span>)currentMaterial);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.start();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.release();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> MaterialCheck()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (Physics.Raycast(transform.position, Vector3.down, &lt;span style="color:#66d9ef">out&lt;/span> hitInfo, rayDistance, groundLayerMask))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Debug.Log(&lt;span style="color:#e6db74">&amp;#34;Hit material: &amp;#34;&lt;/span> + hitInfo.collider.tag);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (System.Enum.TryParse(hitInfo.collider.tag, &lt;span style="color:#66d9ef">out&lt;/span> MaterialType material))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentMaterial = material;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>&lt;em>References:&lt;/em>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.unity3d.com/ScriptReference/Physics.Raycast.html">Unity Raycasting Documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.unity3d.com/ScriptReference/Vector3.html">Unity Vector3 Directions&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://fmod.com/resources/documentation-api">FMOD Studio API Reference&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.unity3d.com/ScriptReference/Color.html">Unity Color Struct&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>Driving FMOD Parameters from Unity: Teleport Sound Example</title><link>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/lectures/week-8/automating-audio-with-parameters-1/</link><pubDate>Mon, 13 Oct 2025 00:00:00 +0000</pubDate><guid>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/lectures/week-8/automating-audio-with-parameters-1/</guid><description>Introduction In this tutorial, you’ll build a reactive teleportation sound system by controlling FMOD parameters from Unity using C#. You’ll learn to:
Create and automate parameters in FMOD Instantiate and attach FMOD events in Unity Dynamically set parameter values via C# scripts Trigger sound transitions via Unity collision events FMOD Parameters are made up of a range of values that can be adjusted in real-time inside our game. This allows us to create dynamic audio that responds to gameplay events, such as a player entering a teleporter.</description><content>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>In this tutorial, you’ll build a reactive teleportation sound system by controlling FMOD parameters from Unity using C#. You’ll learn to:&lt;/p>
&lt;ul>
&lt;li>Create and automate parameters in FMOD&lt;/li>
&lt;li>Instantiate and attach FMOD events in Unity&lt;/li>
&lt;li>Dynamically set parameter values via C# scripts&lt;/li>
&lt;li>Trigger sound transitions via Unity collision events&lt;/li>
&lt;/ul>
&lt;p>FMOD Parameters are made up of a range of values that can be adjusted in real-time inside our
game. This allows us to create dynamic audio that responds to gameplay events, such as a player entering a teleporter.&lt;/p>
&lt;hr>
&lt;h2 id="fmod-setup">FMOD Setup&lt;/h2>
&lt;p>Before diving into code, we’ll first build the &lt;strong>teleporter event&lt;/strong> in FMOD.
This event demonstrates how to use both &lt;strong>built-in&lt;/strong> and &lt;strong>user parameters&lt;/strong> to control sound behavior dynamically.&lt;/p>
&lt;h3 id="goal">Goal&lt;/h3>
&lt;p>We’re designing an event that:&lt;/p>
&lt;ul>
&lt;li>Loops a magical hum sound for a teleporter object.&lt;/li>
&lt;li>Uses the built-in &lt;strong>Distance&lt;/strong> parameter to fade out low frequencies as the listener moves away.&lt;/li>
&lt;li>Uses a custom &lt;strong>Trigger&lt;/strong> parameter to fire a secondary teleport sound when the player enters the teleporter.&lt;/li>
&lt;/ul>
&lt;p>When done, your FMOD setup will let Unity control when the teleport sound plays—while FMOD automatically handles distance-based EQ changes.&lt;/p>
&lt;hr>
&lt;h3 id="1-create-the-event">1. Create the Event&lt;/h3>
&lt;ol>
&lt;li>Open &lt;strong>FMOD Studio&lt;/strong>.&lt;/li>
&lt;li>In the &lt;strong>Assets Browser&lt;/strong>, find &lt;em>Teleporter Loop Stereo 01&lt;/em> under
&lt;code>Audio Bin → Interactables → Teleporters&lt;/code>.&lt;/li>
&lt;li>Create a new event named &lt;strong>Teleporter&lt;/strong> in the &lt;code>SFX/Interactables&lt;/code> folder.&lt;/li>
&lt;li>Drag &lt;em>Teleporter Loop Stereo 01&lt;/em> onto &lt;strong>Track 1&lt;/strong> at the beginning of the timeline.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="2-loop-region-and-asynchronous-playback">2. Loop Region and Asynchronous Playback&lt;/h3>
&lt;ol>
&lt;li>Create a &lt;strong>loop region&lt;/strong> around the first second of the timeline.
This keeps the playhead cycling near the start while the loop plays.&lt;/li>
&lt;li>Select your sound instrument and:
&lt;ul>
&lt;li>Enable &lt;strong>Async&lt;/strong> playback. This ensures the sound plays to completion even if the playhead remains in the loop region.&lt;/li>
&lt;li>Enable &lt;strong>Loop While Playing&lt;/strong>.&lt;/li>
&lt;li>Enable &lt;strong>Cut&lt;/strong> (cut playback when untriggered) — so when the loop region deactivates, playback stops immediately.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;details>
&lt;summary>Sound Instrument Settings: Async, Loop While Playing, and Cut&lt;/summary>
&lt;h3 id="async">Async&lt;/h3>
&lt;p>Controls whether a sound keeps playing after the playhead leaves its region.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>ON&lt;/strong> – plays to completion; good for one-shots like explosions.&lt;/li>
&lt;li>&lt;strong>OFF&lt;/strong> – stops when the region ends; use for loops tied to the source (e.g., footsteps, engines).&lt;/li>
&lt;/ul>
&lt;h3 id="loop-while-playing">Loop While Playing&lt;/h3>
&lt;p>Makes the instrument repeat while it remains triggered.&lt;/p>
&lt;ul>
&lt;li>Combine with &lt;strong>Async ON&lt;/strong> to loop freely until untriggered.&lt;/li>
&lt;li>Combine with &lt;strong>Async OFF&lt;/strong> to loop only within the region.&lt;/li>
&lt;/ul>
&lt;h3 id="cut">Cut&lt;/h3>
&lt;p>Determines what happens when an async loop is untriggered.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>ON&lt;/strong> – stops immediately; use for UI or speech.&lt;/li>
&lt;li>&lt;strong>OFF&lt;/strong> – plays out or fades naturally; use for ambiences.&lt;/li>
&lt;/ul>
&lt;p>&lt;em>Note:&lt;/em> In current FMOD versions, &lt;strong>Cut&lt;/strong> mainly affects async looping instruments.
For overlap control, use &lt;strong>Polyphony = 1&lt;/strong> or &lt;strong>Steal Oldest&lt;/strong> instead.&lt;/p>
&lt;hr>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Example&lt;/th>
&lt;th>Async&lt;/th>
&lt;th>Loop&lt;/th>
&lt;th>Cut&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Explosion&lt;/td>
&lt;td>ON&lt;/td>
&lt;td>OFF&lt;/td>
&lt;td>OFF&lt;/td>
&lt;td>Rings out naturally&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Footsteps&lt;/td>
&lt;td>OFF&lt;/td>
&lt;td>ON&lt;/td>
&lt;td>ON&lt;/td>
&lt;td>Stops instantly&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ambience&lt;/td>
&lt;td>OFF&lt;/td>
&lt;td>ON&lt;/td>
&lt;td>OFF&lt;/td>
&lt;td>Continuous loop&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>UI click&lt;/td>
&lt;td>ON&lt;/td>
&lt;td>OFF&lt;/td>
&lt;td>ON&lt;/td>
&lt;td>Prevents overlap&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/details>
&lt;hr>
&lt;h3 id="3-add-and-automate-the-distance-parameter">3. Add and Automate the Distance Parameter&lt;/h3>
&lt;p>The &lt;strong>Distance&lt;/strong> parameter is one of FMOD’s built-in parameters.
There are six built-in parameters: Distance, Direction, Speed, Orientation, Elevation, and Cone Angle.&lt;/p>
&lt;p>Built-in parameters automatically update based on the listener’s position relative to the sound source in 3D space.&lt;/p>
&lt;p>See more information here: &lt;a href="https://www.fmod.com/docs/2.03/studio/parameters-reference.html#built-in-parameters">FMOD Built-In Parameters&lt;/a>&lt;/p>
&lt;p>&lt;strong>To add and automate Distance:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Click the “+” icon → choose &lt;strong>Add Parameter Sheet → New Parameter&lt;/strong>.&lt;/li>
&lt;li>Name it &lt;strong>Distance&lt;/strong> and choose the &lt;strong>Built-In&lt;/strong> type for &lt;strong>Distance&lt;/strong>.&lt;/li>
&lt;li>On your track, insert a &lt;strong>3-Band EQ&lt;/strong> effect (right-click near the track fader → Add Effect → EQ → 3 Band EQ).&lt;/li>
&lt;li>Right-click the &lt;strong>Low Gain&lt;/strong> knob on the EQ and choose &lt;em>Add Automation&lt;/em>. Then click &lt;strong>Add Curve&lt;/strong> and select &lt;strong>Distance&lt;/strong>.
&lt;ol>
&lt;li>You have now connected your Distance parameter to the low-frequency gain control on the EQ.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>You’ll see a red automation curve appear under the track.&lt;/li>
&lt;li>Draw the curve so that as Distance increases, the low end fades out.&lt;/li>
&lt;/ol>
&lt;p>This simulates real-world behavior—low frequencies carry farther, so as the player gets closer, you regain that low-end presence.&lt;/p>
&lt;hr>
&lt;h3 id="4-create-the-custom-trigger-parameter">4. Create the Custom Trigger Parameter&lt;/h3>
&lt;p>Now we’ll add the parameter Unity will control.&lt;/p>
&lt;ol>
&lt;li>In the Parameters tab, click “+” → &lt;em>Add New Parameter&lt;/em>.&lt;/li>
&lt;li>Choose &lt;strong>Discrete&lt;/strong> type (whole-number values).&lt;/li>
&lt;li>Name it &lt;strong>Trigger&lt;/strong>.
&lt;ul>
&lt;li>Discrete values are ideal for toggling between on/off or state-based conditions.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Set its range:
&lt;ul>
&lt;li>&lt;strong>Min:&lt;/strong> 0&lt;/li>
&lt;li>&lt;strong>Max:&lt;/strong> 1&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>In your event:
&lt;ul>
&lt;li>Place the second sound (&lt;em>Teleporter Walk Through 01&lt;/em>) under the same track or on a new one.&lt;/li>
&lt;li>Go to the Loop region and open its &lt;strong>Trigger Conditions&lt;/strong> panel.&lt;/li>
&lt;li>Set it to &lt;strong>play when Trigger = 1&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>For the looping hum track:
&lt;ul>
&lt;li>Open its &lt;strong>Condition Sheet&lt;/strong> and add a condition to &lt;strong>disable playback when Trigger = 1&lt;/strong>.&lt;/li>
&lt;li>Add a short fade-out automation for smooth transition.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>When Trigger switches from 0 to 1, the looping hum fades, and the teleport sound plays once.&lt;/p>
&lt;p>&lt;img src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-8/automating-audio-with-parameters-1/trigger-condition.png">&lt;/p>
&lt;hr>
&lt;h3 id="5-fine-tune-spatialization">5. Fine-Tune Spatialization&lt;/h3>
&lt;ol>
&lt;li>On the &lt;strong>Master Track&lt;/strong>, open the &lt;strong>Spatializer&lt;/strong> effect.&lt;/li>
&lt;li>Adjust &lt;strong>Min Distance&lt;/strong> and &lt;strong>Max Distance&lt;/strong> values so the attenuation curve fits your in-game scale (e.g., smaller values for tight interior scenes, larger for outdoor).&lt;/li>
&lt;li>Preview using the 3D Preview window in FMOD to confirm how the sound falls off with distance.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="6-save-and-build">6. Save and Build&lt;/h3>
&lt;p>Finally:&lt;/p>
&lt;ul>
&lt;li>Save the project.&lt;/li>
&lt;li>Assign your event to the &lt;strong>Master Bank&lt;/strong>.&lt;/li>
&lt;li>Build your banks (&lt;code>File → Build → Build Banks&lt;/code>).&lt;/li>
&lt;/ul>
&lt;p>You’re now ready to load this event in Unity.&lt;/p>
&lt;hr>
&lt;h3 id="7-test-in-fmod-before-unity">7. Test in FMOD Before Unity&lt;/h3>
&lt;p>Play your event in FMOD:&lt;/p>
&lt;ul>
&lt;li>Drag the &lt;strong>Distance&lt;/strong> slider to watch the EQ automation respond.&lt;/li>
&lt;li>Set &lt;strong>Trigger = 1&lt;/strong> to hear the teleport sound override the loop.&lt;/li>
&lt;/ul>
&lt;p>You should observe:&lt;/p>
&lt;ul>
&lt;li>The low-end fading out naturally as Distance increases.&lt;/li>
&lt;li>The teleport sound firing instantly when Trigger hits 1.&lt;/li>
&lt;li>The loop stopping cleanly when the parameter condition disables it.&lt;/li>
&lt;/ul>
&lt;p>This confirms everything is wired properly before moving to C# integration.&lt;/p>
&lt;hr>
&lt;h2 id="unity-scene-setup">Unity Scene Setup&lt;/h2>
&lt;ul>
&lt;li>Find the teleporter in the scene. Its the &lt;code>TransitionStart&lt;/code> game object.&lt;/li>
&lt;li>Make sure the teleporter has a &lt;strong>Box Collider&lt;/strong> (or whatever shape), set to &lt;em>Is Trigger&lt;/em>.&lt;/li>
&lt;li>Place your player (e.g. “Ellen”) in the scene. This will make our sounds easier to test.
&lt;ul>
&lt;li>Find Ellen and change her transform position to &lt;code>X: -145, Y: 21.3, Z: 43&lt;/code>.&lt;/li>
&lt;li>Alternatively, copy the position of the &lt;code>TransitionStart&lt;/code> object then paste it into Ellen’s transform, then adjust the X position by -5 to start just outside the trigger.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Disable or remove other objects so they won&amp;rsquo;t interfere during testing by unchecking their active state in the &lt;strong>Inspector&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="c-script-f_teleportercs">C# Script: &lt;code>F_Teleporter.cs&lt;/code>&lt;/h2>
&lt;p>Create a script under your &lt;code>Scripts/FMDO Scripts&lt;/code> folder named &lt;code>F_Teleporter.cs&lt;/code> and attach it to your teleporter object (the same object holding the trigger collider).
Here’s the full script:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> UnityEngine;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">F_Teleporter&lt;/span> : MonoBehaviour
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FMOD.Studio.EventInstance tHum;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> Start()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tHum = FMODUnity.RuntimeManager.CreateInstance(&lt;span style="color:#e6db74">&amp;#34;event:/SFX/Interactable/Teleporter&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FMODUnity.RuntimeManager.AttachInstanceToGameObject(tHum, gameObject, GetComponent&amp;lt;Rigidbody&amp;gt;());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tHum.start();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Update is called once per frame&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> OnTriggerEnter(Collider other)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Could also use other.CompareTag(&amp;#34;Player&amp;#34;) for more robust checking if the Player game object had the tag &amp;#34;player&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (other.name == &lt;span style="color:#e6db74">&amp;#34;Ellen&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tHum.setParameterByName(&lt;span style="color:#e6db74">&amp;#34;Trigger&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#66d9ef">false&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> OnDestroy()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tHum.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tHum.release();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="key-points">Key Points&lt;/h3>
&lt;ul>
&lt;li>&lt;code>CreateInstance(path)&lt;/code> must match exactly the FMOD event path.&lt;/li>
&lt;li>&lt;code>AttachInstanceToGameObject(...)&lt;/code> aligns spatial 3D behavior (so position/distance is tracked).&lt;/li>
&lt;li>&lt;code>setParameterByName(&amp;quot;Trigger&amp;quot;, 1f, false)&lt;/code> changes your &lt;code>Trigger&lt;/code> parameter.
&lt;ul>
&lt;li>The &lt;code>false&lt;/code> flag means you don’t bypass seek speed — FMOD seeks based on your parameter settings.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>release()&lt;/code> frees memory and ensures no audio leaks after object destruction.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="testing--debugging">Testing &amp;amp; Debugging&lt;/h2>
&lt;ol>
&lt;li>Hit &lt;strong>Play&lt;/strong> in Unity.&lt;/li>
&lt;li>Move the listener close to the teleporter — you should hear the ambient loop with full low frequencies.&lt;/li>
&lt;li>As you back away, low frequencies fade (due to the Distance → EQ automation).&lt;/li>
&lt;li>Walk into the trigger: the teleport sound plays, and the loop stops or fades.&lt;/li>
&lt;/ol>
&lt;p>If things don’t work:&lt;/p>
&lt;ul>
&lt;li>Confirm the collider is set to &lt;em>Is Trigger&lt;/em>.&lt;/li>
&lt;li>Double-check your player object’s &lt;strong>name&lt;/strong> (or change the script to use a tag).&lt;/li>
&lt;li>Ensure the FMOD event path is correct and that the event’s bank is loaded.&lt;/li>
&lt;li>In FMOD Studio, verify your parameter mapping and that &lt;code>Trigger = 1&lt;/code> is correctly wired.&lt;/li>
&lt;/ul></content></item><item><title>Simplifying Sound Playback in Unity with FMOD’s PlayOneShot Command</title><link>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/lectures/week-8/using-playoneshot/</link><pubDate>Mon, 13 Oct 2025 00:00:00 +0000</pubDate><guid>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/lectures/week-8/using-playoneshot/</guid><description>Learn how to use FMOD’s PlayOneShot and PlayOneShotAttached commands in Unity to efficiently trigger one-off sound effects through colliders and triggers.</description><content>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>In game audio, efficiency matters. When designing sound interactions in Unity using FMOD, there are many cases where you only need a sound to play once—like when a player picks up an item or activates a switch. Writing full event-handling code for each instance can quickly become cumbersome.&lt;/p>
&lt;p>That’s where FMOD’s &lt;strong>PlayOneShot&lt;/strong> command comes in. This function provides a fast, elegant way to trigger short, non-looping sound effects in your game world without manually managing event instances. In this guide, we’ll walk through how to use &lt;strong>PlayOneShot&lt;/strong> and &lt;strong>PlayOneShotAttached&lt;/strong>, explain how Unity’s trigger system ties in, and unpack key technical concepts like Vectors, Colliders, and Layers.&lt;/p>
&lt;hr>
&lt;h2 id="what-is-playoneshot">What Is PlayOneShot?&lt;/h2>
&lt;p>FMOD’s &lt;code>PlayOneShot&lt;/code> command allows you to play an FMOD event anywhere in your game with a single line of code. When the command is called, it automatically plays the event and then cleans up after itself when the sound finishes—no need to store or release the event manually.&lt;/p>
&lt;p>This makes it ideal for simple, one-off sound effects such as button clicks, weapon pickups, or door openings.&lt;/p>
&lt;p>For instance, if we want to trigger a short sound effect when a player picks up their melee weapon, &lt;code>PlayOneShot&lt;/code> lets us do it with minimal setup.&lt;/p>
&lt;hr>
&lt;h2 id="setting-up-the-event-in-fmod">Setting Up the Event in FMOD&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Create a new 3D Action event&lt;/strong> called &lt;code>Staff Collect&lt;/code> and add your audio file (for example, &lt;code>Switch Activated 01&lt;/code>).&lt;/li>
&lt;li>Assign the event to your &lt;strong>Master Bank&lt;/strong>.&lt;/li>
&lt;li>Organize it neatly under &lt;strong>SFX → Interactables&lt;/strong>.&lt;/li>
&lt;li>Save your project and &lt;strong>build your banks&lt;/strong>.&lt;/li>
&lt;/ol>
&lt;p>Once you’ve done that, switch back to Unity to implement the sound event.&lt;/p>
&lt;blockquote>
&lt;p>Action Events are perfect for short, non-looping sounds that play once and then stop. They are typically used for sound effects triggered by player actions or environmental events.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;img src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-8/using-playoneshot/image.png">&lt;/p>
&lt;hr>
&lt;h2 id="using-triggers-in-unity">Using Triggers in Unity&lt;/h2>
&lt;p>A &lt;strong>Trigger&lt;/strong> in Unity is an invisible area that detects when objects (like your player) enter or leave it. Triggers are built using colliders with the &lt;strong>Is Trigger&lt;/strong> box checked. When something passes through, Unity can perform specific actions—such as playing a sound.&lt;/p>
&lt;p>For our weapon pickup example:&lt;/p>
&lt;ul>
&lt;li>The &lt;code>StaffTakeTrigger&lt;/code> object in your scene will act as the trigger.&lt;/li>
&lt;li>It already has a &lt;strong>Sphere Collider&lt;/strong> attached with &lt;strong>Is Trigger&lt;/strong> enabled.&lt;/li>
&lt;li>When the player enters this area, Unity can detect the interaction and execute our FMOD command.&lt;/li>
&lt;/ul>
&lt;p>We’ll connect this trigger to our FMOD event using a custom script.&lt;/p>
&lt;hr>
&lt;h2 id="writing-the-trigger-script">Writing the Trigger Script&lt;/h2>
&lt;p>Go to your &lt;code>FMOD Scripts&lt;/code> folder and create a new C# script called &lt;code>F_StaffCollectTrigger&lt;/code> attach it to the &lt;code>StaffTakeTrigger&lt;/code> object.&lt;/p>
&lt;p>Delete the default &lt;code>Start&lt;/code> and &lt;code>Update&lt;/code> functions, as we won’t need them for this simple interaction.&lt;/p>
&lt;p>Inside the script, we’ll use the &lt;code>OnTriggerEnter&lt;/code> function. This Unity event function runs automatically when another object enters the trigger’s collider.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> UnityEngine;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">F_StaffCollectTrigger&lt;/span> : MonoBehaviour
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> OnTriggerEnter()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FMODUnity.RuntimeManager.PlayOneShot(&lt;span style="color:#e6db74">&amp;#34;event:/SFX/Interactables/StaffCollect&amp;#34;&lt;/span>, transform.position);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;code>onTriggerExit&lt;/code> is another useful function that runs when an object leaves the trigger area. You can use it for sounds that should play when the player exits, like a door closing. &lt;code>onTriggerStay&lt;/code> runs continuously while an object remains within the trigger.&lt;/p>
&lt;/blockquote>
&lt;p>This is all it takes to trigger the &lt;em>Staff Collect&lt;/em> sound effect when the player enters the area.&lt;/p>
&lt;p>However, we can improve this setup by specifying &lt;em>where&lt;/em> in 3D space the sound should be played.&lt;/p>
&lt;hr>
&lt;h2 id="understanding-vectors-in-unity">Understanding Vectors in Unity&lt;/h2>
&lt;p>To position sounds accurately, we need to work with &lt;strong>Vectors&lt;/strong>—data types that store coordinates and direction.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Vector2&lt;/strong> holds &lt;em>x&lt;/em> and &lt;em>y&lt;/em> coordinates (useful for 2D games).&lt;/li>
&lt;li>&lt;strong>Vector3&lt;/strong> holds &lt;em>x&lt;/em>, &lt;em>y&lt;/em>, and &lt;em>z&lt;/em> coordinates (used in 3D games).&lt;/li>
&lt;/ul>
&lt;p>In Unity, almost every object’s position, rotation, and scale are stored as Vector3 values. To access the current position of your object, you can reference its &lt;strong>Transform&lt;/strong> component.&lt;/p>
&lt;blockquote>
&lt;p>Here, &lt;code>transform.position&lt;/code> retrieves the Vector3 position of the object this script is attached to (the trigger). This is shorthand for &lt;code>GetComponent&amp;lt;Transform&amp;gt;().position&lt;/code>.&lt;/p>
&lt;/blockquote>
&lt;p>If you want to play the sound at a different location but related to the trigger, you can create a new Vector3:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>Vector3 pos = transform.position + Vector3.up * &lt;span style="color:#ae81ff">1.5f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>FMODUnity.RuntimeManager.PlayOneShot(&lt;span style="color:#e6db74">&amp;#34;event:/SFX/Interactables/StaffCollect&amp;#34;&lt;/span>, pos);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This example plays the sound 1.5 units above the trigger’s position.&lt;/p>
&lt;hr>
&lt;h2 id="layers-and-collision-setup">Layers and Collision Setup&lt;/h2>
&lt;p>For the trigger to work correctly, Unity needs to detect when the player enters the trigger area. This requires proper setup of &lt;strong>Layers&lt;/strong> and &lt;strong>Colliders&lt;/strong>.&lt;/p>
&lt;ol>
&lt;li>The object acting as the trigger must have a collider with &lt;strong>Is Trigger&lt;/strong> checked.&lt;/li>
&lt;li>The player object must either:
&lt;ul>
&lt;li>Have a &lt;strong>Rigidbody&lt;/strong> component, or&lt;/li>
&lt;li>Be on a layer compatible with the trigger’s layer.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>You can manage these interactions in Unity’s &lt;strong>Layer Collision Matrix&lt;/strong> under &lt;em>Edit → Project Settings → Physics&lt;/em>.
Ensure that the &lt;strong>Player&lt;/strong> layer can interact with the &lt;strong>Environment&lt;/strong> layer (or whichever layer your trigger belongs to).&lt;/p>
&lt;blockquote>
&lt;p>Tags and Layers help organize objects in your scene and control how they interact. Tags are used for identifying objects, while Layers are used for collision detection and rendering.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;img src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-8/using-playoneshot/physics-layers.png">&lt;/p>
&lt;p>Here you can see the Layer Collision Matrix in Unity, which defines how different layers interact with each other. See that the &lt;strong>Player&lt;/strong> layer can collide with the &lt;strong>Environment&lt;/strong> layer, allowing our trigger to detect when the player enters.&lt;/p>
&lt;hr>
&lt;h2 id="using-playoneshotattached">Using PlayOneShotAttached&lt;/h2>
&lt;p>While &lt;code>PlayOneShot&lt;/code> requires a position vector, FMOD also offers a variant called &lt;code>PlayOneShotAttached&lt;/code>. This version automatically plays an event at the location of a specific game object.&lt;/p>
&lt;p>Here’s how to use it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> OnTriggerEnter()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FMODUnity.RuntimeManager.PlayOneShotAttached(&lt;span style="color:#e6db74">&amp;#34;event:/SFX/Interactables/StaffCollect&amp;#34;&lt;/span>, gameObject);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>gameObject&lt;/code> reference points to the object this script is attached to—in this case, &lt;code>StaffTakeTrigger&lt;/code>.
This makes it an elegant alternative when you want to link the sound directly to an object’s position.&lt;/p>
&lt;p>If you want to attach the sound to another object in the scene, you can define a &lt;strong>public GameObject variable&lt;/strong> in your script and assign it through the Unity Inspector:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> GameObject Obj;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> OnTriggerEnter()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FMODUnity.RuntimeManager.PlayOneShotAttached(&lt;span style="color:#e6db74">&amp;#34;event:/SFX/Interactables/StaffCollect&amp;#34;&lt;/span>, Obj);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This approach provides flexibility for triggering sounds from different points in your game world.&lt;/p>
&lt;hr>
&lt;h2 id="limitations-and-next-steps">Limitations and Next Steps&lt;/h2>
&lt;p>While &lt;code>PlayOneShot&lt;/code> and &lt;code>PlayOneShotAttached&lt;/code> are great for quick, lightweight sound triggers, they come with a limitation:
you can’t manipulate FMOD parameters after triggering them.&lt;/p>
&lt;p>For cases where you need to control parameters dynamically (for instance, changing pitch or volume in response to player movement), you’ll need to use full event instances instead.&lt;/p>
&lt;p>We’ll explore this in the next module: &lt;strong>Interacting with FMOD Parameters&lt;/strong>—where you’ll learn how to adjust event behavior in real time for adaptive sound design.&lt;/p>
&lt;hr>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>The &lt;code>PlayOneShot&lt;/code> and &lt;code>PlayOneShotAttached&lt;/code> commands streamline the process of triggering short, non-looping sounds in Unity. Combined with Unity’s trigger system and a solid understanding of Vectors and Layers, they allow you to quickly prototype interactive sound behaviors.&lt;/p></content></item><item><title>Attaching Audio to Animations with FMOD and Unity</title><link>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/lectures/week-9/attaching-audio-to-animations/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/lectures/week-9/attaching-audio-to-animations/</guid><description>Introduction This post walks through a practical pipeline for triggering FMOD sound events from Unity Animation Events so your character’s footsteps, landings, and swishes line up precisely with what’s on screen. We’ll build a reusable C# function that can be called from any Animation Event and, later, accept the FMOD event path as a parameter—setting us up to drive parameters (like surface material) in a follow-up lesson.
What You’ll Build An FMOD event for a footstep, with a Material parameter that selects between multiple surface types.</description><content>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>This post walks through a practical pipeline for triggering FMOD sound events from Unity Animation Events so your character’s footsteps, landings, and swishes line up precisely with what’s on screen. We’ll build a reusable C# function that can be called from any Animation Event and, later, accept the FMOD event path as a parameter—setting us up to drive parameters (like surface material) in a follow-up lesson.&lt;/p>
&lt;hr>
&lt;h2 id="what-youll-build">What You’ll Build&lt;/h2>
&lt;ul>
&lt;li>An FMOD event for a footstep, with a &lt;code>Material&lt;/code> parameter that selects between multiple surface types.&lt;/li>
&lt;li>A Unity C# function that:
&lt;ul>
&lt;li>Creates and attaches an FMOD event instance to the player at runtime,&lt;/li>
&lt;li>Starts the event,&lt;/li>
&lt;li>Releases the instance,&lt;/li>
&lt;li>Accepts a string parameter for the FMOD event path (so you can reuse it across many animations).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Animation Events in Unity that call your C# function exactly when each foot hits the ground.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="fmod-setup-a-single-footstep-event-many-materials">FMOD Setup: A Single Footstep Event, Many Materials&lt;/h2>
&lt;p>Create a 3D event named &lt;code>Ellen_Run&lt;/code> in the &lt;code>SFX/Ellen&lt;/code> folder that plays one footstep per trigger.&lt;/p>
&lt;p>Add a user parameter named &lt;code>Material&lt;/code> and set it to Hold (so the footstep that already triggered won’t switch mid-play if the parameter changes). Add four values representing different surface types:&lt;/p>
&lt;ol>
&lt;li>Earth&lt;/li>
&lt;li>Stone&lt;/li>
&lt;li>Puddle&lt;/li>
&lt;li>Grass&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="alt text" src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-9/attaching-audio-to-animations/material-sheet.png">&lt;/p>
&lt;p>Set the parameter to Hold during playback to allow the current footstep to complete even if the player moves to a different surface.&lt;/p>
&lt;p>&lt;img alt="alt text" src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-9/attaching-audio-to-animations/set-to-hold.png">&lt;/p>
&lt;p>Add a Multi Instrument (right-click, then &lt;code>Add Multi Instrument&lt;/code>) for each material value and add the appropriate footstep samples for that surface. Enable Shuffle in the Multi Instrument settings to randomize footstep playback.&lt;/p>
&lt;p>&lt;img alt="alt text" src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-9/attaching-audio-to-animations/mutli-instrument.png">&lt;/p>
&lt;p>Now, when you select different values for the &lt;code>Material&lt;/code> parameter, you’ll hear footsteps on different surfaces.&lt;/p>
&lt;p>Save your project and build your banks before switching back to Unity.&lt;/p>
&lt;hr>
&lt;h2 id="unity-project-setup">Unity Project Setup&lt;/h2>
&lt;p>Create a script named &lt;code>F_Player&lt;/code> and add it to your player (Ellen) GameObject. This script will own all player-related SFX behaviors.&lt;/p>
&lt;h3 id="the-reusable-one-shot-function">The Reusable One-Shot Function&lt;/h3>
&lt;p>We’ll implement a “play one-shot with control” pattern: create an instance, attach it to the player, start it, and release it—while still allowing parameter control if we need it later.&lt;/p>
&lt;p>Here’s a simple implementation using a new &lt;code>PlayRunEvent&lt;/code> function. We use a public function so that Unity Animation Events can call it directly. For now we hardcode the event path; in the next section we’ll update the function to accept a string parameter so different animations can play different events.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> PlayRunEvent()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EventInstance run = RuntimeManager.CreateInstance(&lt;span style="color:#e6db74">&amp;#34;event:/SFX/Ellen/Ellen_Run&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuntimeManager.AttachInstanceToGameObject(run, gameObject, GetComponent&amp;lt;Rigidbody&amp;gt;());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.start();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.release();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Why this approach instead of &lt;code>RuntimeManager.PlayOneShot(...)&lt;/code>?&lt;/p>
&lt;ul>
&lt;li>It gives you full control over parameters (e.g., &lt;code>Material&lt;/code>) per trigger.&lt;/li>
&lt;li>It’s easy to reuse: different animations can pass different event paths into the same function.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="hooking-up-unity-animation-events">Hooking Up Unity Animation Events&lt;/h2>
&lt;p>Animation Events in Unity let an animation call your function at a specific frame. For footsteps, add an event exactly when each foot contacts the ground.&lt;/p>
&lt;ol>
&lt;li>Locate Ellen’s run animations in the Project window:
&lt;ul>
&lt;li>&lt;code>3DGameKit/Art/Animations/AnimationClips/Characters/Ellen/&lt;/code>&lt;/li>
&lt;li>Expand the &lt;code>Ellen Run Forward&lt;/code> FBX to see its clips (forward, diagonal, etc.).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Preview the clip in the Inspector. If you see “No model is available for preview,” drag the Ellen model into the preview window from &lt;code>Art/Models/Characters/Ellen&lt;/code>.&lt;/li>
&lt;li>Find the &lt;code>Ellen_Run_Forward&lt;/code> clip and click it to open the Animation tab. Click Edit to unlock it for modification.
&lt;ol>
&lt;li>&lt;img alt="alt text" src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-9/attaching-audio-to-animations/ellen-animation.png">&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Open the Events tab on the animation.
&lt;ol>
&lt;li>&lt;img alt="alt text" src="https://dsu-digital-sound-design.github.io/f-25-dad-424-sound-design-for-games/f-25-dad-424-sound-design-for-games/lectures/week-9/attaching-audio-to-animations/events.png">&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Scrub the timeline to the exact frame the foot contacts the ground.&lt;/li>
&lt;li>Click Add Event. In the Function field, enter &lt;code>PlayRunEvent&lt;/code>.&lt;/li>
&lt;li>Add a second event for the other foot.&lt;/li>
&lt;li>Click Apply to save the clip modifications.&lt;/li>
&lt;li>Play the scene and run forward; you should hear a footstep on each contact.&lt;/li>
&lt;/ol>
&lt;p>Important: The script containing the target function must live on the same GameObject that plays the animation via an Animator. Otherwise, the Animation Event won’t be able to find your function.&lt;/p>
&lt;hr>
&lt;h2 id="add-the-event-path-parameter">Add the Event Path Parameter&lt;/h2>
&lt;p>Now that the events are triggering, let’s pass the FMOD event path as a string parameter so we can reuse this function for other animations (like landing or attacking) later.&lt;/p>
&lt;p>Edit your &lt;code>F_Player&lt;/code> script to update &lt;code>PlayRunEvent&lt;/code> to accept a string parameter for the event path, as shown below.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> PlayRunEvent(&lt;span style="color:#66d9ef">string&lt;/span> eventPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EventInstance run = RuntimeManager.CreateInstance(eventPath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuntimeManager.AttachInstanceToGameObject(run, gameObject, GetComponent&amp;lt;Rigidbody&amp;gt;());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.start();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.release();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In your Animation Events, update each event to pass the event path string:
&lt;code>event:/SFX/Ellen/Ellen_Run&lt;/code> (or whatever your event path is).&lt;/p>
&lt;hr>
&lt;h2 id="understanding-c-parameters">Understanding C# Parameters&lt;/h2>
&lt;p>We defined &lt;code>PlayRunEvent(string eventPath)&lt;/code> so each animation can pass its own FMOD event path. This is a value parameter: the Animation Event provides a string, Unity passes it into your function at call time, and you use that to create the event instance.&lt;/p>
&lt;p>This pattern scales nicely:&lt;/p>
&lt;ul>
&lt;li>Walk clip → &lt;code>event:/SFX/Ellen/Ellen_Walk&lt;/code>&lt;/li>
&lt;li>Land clip → &lt;code>event:/SFX/Ellen/Ellen_Land&lt;/code>&lt;/li>
&lt;li>Attack clip → &lt;code>event:/SFX/Ellen/Ellen_Slash&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>You keep one function and let animations decide which event to play.&lt;/p>
&lt;hr>
&lt;h2 id="common-pitfalls">Common Pitfalls&lt;/h2>
&lt;ul>
&lt;li>The function name in Animation Events must match exactly, including case.&lt;/li>
&lt;li>The script with &lt;code>PlayRunEvent&lt;/code> must be on the animated GameObject (the same one with the Animator).&lt;/li>
&lt;li>Don’t forget to click Apply after editing Animation Events; missed applies are a classic time sink.&lt;/li>
&lt;li>Always call &lt;code>release()&lt;/code> after &lt;code>start()&lt;/code>; this defers cleanup until playback ends.&lt;/li>
&lt;li>If your footstep feels off by a few frames, adjust the event position slightly in the animation timeline—what looks like “contact” in the mesh may need a tiny lead or lag to feel right in-game.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="helpful-links">Helpful Links&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.unity3d.com/Manual/AnimationEvents.html">Unity Animation Events Documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://fmod.com/resources/documentation-unity?version=2.0&amp;page=welcome.html">FMOD Unity Integration Documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://fmod.com/resources/documentation-api?version=2.0&amp;page=core-api-studio-eventinstance.html">FMOD EventInstance API Reference&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.unity3d.com/ScriptReference/Rigidbody.html">Unity Scripting API: Rigidbody&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="more-complete-version-with-error-checking-and-parameterization">More complete version with error checking and parameterization:&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> UnityEngine;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> FMODUnity; &lt;span style="color:#75715e">// FMOD Unity integration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">using&lt;/span> FMOD.Studio; &lt;span style="color:#75715e">// FMOD Studio API&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// by including these namespaces, we can use FMOD types directly&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">F_Player&lt;/span> : MonoBehaviour
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Optional: cache components you’ll likely need&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">private&lt;/span> Rigidbody _rb;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> Awake()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Called by Unity Animation Events. Pass an FMOD event path, e.g. &amp;#34;event:/SFX/Ellen/Ellen_Run&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> PlayRunEvent(&lt;span style="color:#66d9ef">string&lt;/span> eventPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#66d9ef">string&lt;/span>.IsNullOrEmpty(eventPath))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Debug.LogWarning(&lt;span style="color:#e6db74">&amp;#34;PlayRunEvent called without a valid FMOD event path.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EventInstance run = RuntimeManager.CreateInstance(eventPath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Attach to this GameObject so the sound follows the player and 3D attenuation works as expected&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RuntimeManager.AttachInstanceToGameObject(run, gameObject, _rb);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.start();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> run.release(); &lt;span style="color:#75715e">// Safe: schedules the instance for cleanup after playback finishes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content></item></channel></rss>