DEV505 / CODE RECIPE
Start, loop, and stop.
A complete Campfire component with key-based playback, one retained handle, and lifecycle cleanup.
Prerequisites
Section titled “Prerequisites”Complete Your first sound first. You need one enabled Sound Manager, the Campfire graph in its Direct events list, generated Sound Keys, and an Audio Listener near the object playing the sound.
Save this as CampfireSound.cs under Assets/Scripts. Create an empty GameObject named Fire sound near the listener and drag the script onto it. No fields need an Inspector reference. Remove the first tutorial’s FirstSound component if you are reusing that object, so two components do not start the same fire.
This component waits for you to call Ignite(); it does not play automatically. If your scene has a Unity UI Button, add an entry to the Button’s On Click list, drag Fire sound into the object field, and choose CampfireSound → Ignite(). Wire a second button to Extinguish(). Your own interaction script can call the same methods. For a test that needs no button setup, use the complete first-sound tutorial instead.
When you trigger Ignite, you should hear ignition followed by crackling. Extinguish fades it and plays an ending cue. This example creates no fire model or particles. Use the supplied demo’s existing controller when trying its full visual scene.
Complete component
Section titled “Complete component”using DEV505.SoundManager;using UnityEngine;
// Register the included Campfire event on an active SoundManager first.public sealed class CampfireSound : MonoBehaviour{ private SoundHandle fire;
public bool IsPlaying => SoundBus.IsAlive(fire);
public void Ignite() { if (!isActiveAndEnabled || SoundBus.IsAlive(fire)) return;
var result = SoundBus.Play(Sounds.Campfire, transform); if (!result.Accepted) { Debug.LogWarning($"Campfire did not start: {result.Status}.", this); return; }
fire = result.Handle; }
public void Extinguish() { if (SoundBus.IsAlive(fire)) SoundBus.Stop(fire, fade: 0.15f);
fire = default; }
private void OnDisable() => Extinguish();}Why these calls belong here
Section titled “Why these calls belong here”Ignite() checks whether its previous play is still alive before starting another, so repeated button presses do not stack loops. The handle identifies that play. Its IsValid property alone does not say whether the sound has finished; SoundBus.IsAlive does. The active/enabled check prevents a disabled component from starting a new loop.
Play(Sounds.Campfire, transform) follows this transform and assigns its GameObject as owner. The success path retains the returned handle; a rejected request reports its actual status.
Extinguish() requests a stop, then clears the component’s handle. The graph can still play its release tail after the component has cleared that field. This means the component’s IsPlaying reports its retained active play, not whether the last extinguish tail is still audible.
OnDisable() runs the same cleanup when the component is disabled. Keep the manager alive if you want to hear the release: shutting the manager down ends its playback service.
The fade has two sources
Section titled “The fade has two sources”The call passes a fallback fade of 0.15f seconds. In the supplied graph, On Stop explicitly reaches the ignition and crackle nodes’ Stop ports, so those voices use their node fade-outs: 0.3 s and 0.6 s. Remaining pre-existing voices would use the requested fallback. The extinguish cue is a new release voice and is allowed to finish.
Think of the API fade as the fallback, and the graph’s explicit Stop connections as more specific instructions. Read the graph walkthrough alongside this example.
Change the fire’s intensity
Section titled “Change the fire’s intensity”This optional excerpt goes in a method of the component above. It changes the target volume parameter of the running crackle; it does not change the particles.
SoundBus.SetFloat(fire, SoundParameters.Campfire.Intensity, 0.35f);The graph declares Intensity from 0 to 1, defaults to 0.65, and smooths rises and falls at 0.5 units per second. To choose an initial intensity, put the value in a SoundPlayContext before the play starts. The Play reference shows how to keep the same explicit owner when constructing a context.
Validation
Section titled “Validation”The checked example is stored once and imported directly into this page, so copied code and the validated source cannot drift apart. M1 validation compiles this component against the live Unity project and exercises acceptance, duplicate-start protection, stop/release completion, and disable cleanup. See the repository’s validation report for the exact revision and results.