DEV505 / CODE RECIPE
Schedule thunder and cancel the whole storm.
Place each thunder at its strike point, let the graph delay it, and use one owner to clear pending and audible weather sounds.
Set up the scene
Section titled “Set up the scene”Start with Your first sound if you have not set up a sound scene before. Use a separate test scene with one enabled Sound Manager, generated Sound Keys, and one Audio Listener near the sound object. Add Weather and Thunder from 05-Weather to the manager's Direct events list; these are the supplied .soundgraph assets under Assets/DEV505/SoundManager/Samples.
Save the complete code below as StormSounds.cs in Assets/Scripts. Wait for Unity to compile it, create an empty GameObject, and drag the script onto it. The filename must match the class name. Test one recipe component at a time so another demo controller does not compete for the same events.
Create an empty GameObject a few units to one side of the listener and assign it to Strike Point. If left empty, thunder uses the component’s position. Rain and Wind fields set the requested weather level.
In Play Mode, wait until the Sound Manager is ready. To run a listed test action, open the component's three-dot menu in the Inspector and choose that action. These are the script's ContextMenu methods. Your own code or a Unity UI Button can call its public methods too. Inspector edits made during Play Mode are temporary.
Try the component
Section titled “Try the component”Choose Start weather. Raise Rain and Wind and hear them grow gradually. Choose Strike thunder: after 0.55 seconds, the rumble begins from Strike Point. Wait more than four seconds before another strike because the event has a cooldown.
For the cancellation test, strike again and immediately choose Clear weather, before the 0.55-second delay finishes. The background fades and that waiting rumble should never start. Disable the component to test its final cleanup.
The script does not draw lightning. The marked location inside the Accepted branch is where your own flash effect would start, giving the sound its delayed response. A refused request should not flash as though thunder had been scheduled.
A fixed position and a shared owner
Section titled “A fixed position and a shared owner”Weather starts with initial Rain and Wind of zero. Update sends the Inspector targets. Each Thunder request uses At(strikePoint.position, owner): the strike stays at that point even if the cloud moves later.
Both events carry the same owner token. Clear uses StopOwned(owner, 0.85f), which reaches the weather loop and thunder plays, including those waiting inside a graph Delay. Stopping only the Weather handle would leave Thunder independent.
The owner remains valid for another storm from this component. OnDisable stops its owned plays immediately and releases the owner record. The saved Weather event is 2D; Thunder is 3D with a 4–20 unit distance range. Positioning the weather bed itself does not add direction to it.
Keep the delay in one place
Section titled “Keep the delay in one place”This example sends the thunder request at the moment of the strike. The graph supplies the delay. Adding another C# timer before that request would add a second wait. A separate delayed callback would also need its own cancellation; the graph-owned delay already follows the play’s lifetime.
Complete component
Section titled “Complete component”using DEV505.SoundManager;using UnityEngine;
public sealed class StormSounds : MonoBehaviour{ [SerializeField, Range(0, 1)] private float rain = 0.35f; [SerializeField, Range(0, 1)] private float wind = 0.18f; [SerializeField] private Transform strikePoint = null; private ISoundService service; private SoundOwnerToken owner; private SoundHandle weather; private SoundHandle thunder;
[ContextMenu("Start weather")] public void StartWeather() { if (!Application.isPlaying || !isActiveAndEnabled || weather.IsAlive()) return; service = SoundServices.Current; if (service == null || !service.Ready) { Debug.LogWarning("Wait for an enabled Sound Manager to become ready.", this); return; } owner = service.OwnerOf(this); var context = SoundPlayContext.At(transform.position, owner) .With(SoundParameters.Weather.Rain, 0f) .With(SoundParameters.Weather.Wind, 0f); var result = SoundBus.Play(Sounds.Weather, context); if (result.Accepted) weather = result.Handle; else Debug.LogWarning($"Weather could not start: {result.Status}", this); }
private void Update() { if (!weather.IsAlive()) return; SoundBus.SetFloat(weather, SoundParameters.Weather.Rain, rain); SoundBus.SetFloat(weather, SoundParameters.Weather.Wind, wind); }
[ContextMenu("Strike thunder")] public void Strike() { if (!Application.isPlaying || !isActiveAndEnabled || !weather.IsAlive()) return; Vector3 position = strikePoint ? strikePoint.position : transform.position; var result = SoundBus.Play(Sounds.Thunder, SoundPlayContext.At(position, owner)); if (result.Accepted) { thunder = result.Handle; // Flash your lightning here; the graph waits 0.55 seconds before thunder. } else Debug.LogWarning($"Thunder could not play: {result.Status}", this); }
[ContextMenu("Clear weather")] public void Clear() { if (ReferenceEquals(service, SoundServices.Current) && owner.IsValid) service.StopOwned(owner, 0.85f); // Also cancels thunder still waiting in Delay. weather = default; thunder = default; }
private void OnDisable() { if (ReferenceEquals(service, SoundServices.Current) && owner.IsValid) { service.StopOwned(owner, 0); service.ReleaseOwner(owner); } service = null; owner = default; weather = default; thunder = default; }}Keep exploring
Section titled “Keep exploring”Read the demo walkthrough for the graph, parameters, controls, and Unity screenshots. The related API reference explains the calls used here. Return to all twelve recipes to choose another interaction.
This page imports the checked C# file directly. It was compiled against the documented Unity revision; its runtime checks and source hash are recorded in the documentation repository’s validation files.