DEV505 / CODE RECIPE
Send a command to a running graph.
Use Horn, Work, or Strike to trigger another path inside a play that is already alive.
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 Engine from 04-Engine to the manager's Direct events list; these are the supplied .soundgraph assets under Assets/DEV505/SoundManager/Samples.
Save the complete code below as EngineSoundControls.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.
No Inspector references are needed. The engine starts automatically; the component menu exposes its horn, pause, resume, and stop methods.
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”Enter Play Mode and choose Sound horn while the engine runs. You should hear a horn over the existing engine sound. Stop the engine, wait for it to end, and try the horn again: a finished play has no running graph to receive the signal. Start the engine to try again.
Send only when the action happens
Section titled “Send only when the action happens”The complete component retains the accepted Engine handle, checks it is alive, and sends the exact authored signal name:
SoundBus.SendSignal(engine, "Horn");The graph’s On Signal node named Horn responds by starting its horn voice. The engine loops continue. Calling Play with Sounds.Engine for every horn press would create or replace entire engine plays instead.
Use the graph’s exact signal spelling. For the Engine graph, "Shift" is another authored signal. A signal does not imply a universal engine command: a different graph responds only to the signals it defines.
Signals are different from notifications
Section titled “Signals are different from notifications”A signal travels from your code into a graph: “sound the horn.” A notification travels from the runtime back to your code: “this play finished or was cancelled.” The component subscribes once, matches its own handle, clears it on a terminal notification, and disposes the subscription on disable.
Workshop uses the same mechanism for Work, Pause, and Strike. Set Tool first, then send Work. Its graph signal named Pause stops tool voices; it is not the runtime’s pause function. Use the complete Workshop recipe for that combination and overlapping hammer impacts.
Complete component
Section titled “Complete component”using System;using DEV505.SoundManager;using UnityEngine;
public sealed class EngineSoundControls : MonoBehaviour{ [SerializeField, Range(0f, 1f)] private float rpm = 0f; private SoundHandle engine; private IDisposable subscription;
private void OnEnable() { subscription = SoundBus.Notifications.Subscribe(OnSoundChanged); }
private void Start() => StartEngine();
[ContextMenu("Start engine")] public void StartEngine() { if (!Application.isPlaying || !isActiveAndEnabled || SoundBus.IsAlive(engine)) return;
var service = SoundServices.Current; if (service == null) { Debug.LogWarning("Add an active Sound Manager to the scene.", this); return; }
var context = SoundPlayContext.Following(transform, service.OwnerOf(gameObject)) .With(SoundParameters.Engine.Rpm, rpm) .With(SoundParameters.Engine.Load, 0.2f); var result = SoundBus.Play(Sounds.Engine, context); if (result.Accepted) engine = result.Handle; else Debug.LogWarning($"Could not start Engine: {result.Status}", this); }
private void Update() { if (SoundBus.IsAlive(engine)) SoundBus.SetFloat(engine, SoundParameters.Engine.Rpm, rpm); }
[ContextMenu("Sound horn")] public void SoundHorn() { if (SoundBus.IsAlive(engine)) SoundBus.SendSignal(engine, "Horn"); }
[ContextMenu("Pause engine")] public void PauseEngine() => SoundBus.SetPaused(engine, true);
[ContextMenu("Resume engine")] public void ResumeEngine() => SoundBus.SetPaused(engine, false);
[ContextMenu("Stop engine")] public void StopEngine() { if (SoundBus.IsAlive(engine)) SoundBus.Stop(engine); // Keep the handle until the ending notification so it can be identified. }
private void OnSoundChanged(SoundNotification message) { if (!message.Handle.Equals(engine) || !message.IsTerminal) return; Debug.Log($"Engine ended: {message.Kind} ({message.Reason})", this); engine = default; }
private void OnDisable() { subscription?.Dispose(); subscription = null; StopEngine(); engine = 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.