The heal event got rebuilt four times. Single-target pickup, then a party heal, then a heal scaled by missing health, then a heal that had to be refused because the ability was still on cooldown. Each addition came back to audio as a new event and a new bank export.
None of that was necessary. One FMOD Studio event can carry all four cases if the parameter sheet is agreed before anyone opens the timeline. Parameters are cheap to add early and expensive once a programmer has wired a call site against a specific event path.
Sheet first, event second. The rest of this is what belongs in the sheet.
The Sheet Is the Contract, the Event Is an Implementation
A workable sheet has five columns: name, type, range or labels, default, and who writes it. If a row cannot be filled in, the parameter is not ready to build. The programmer never needs to read the event; they need the sheet.
FMOD gives each event continuous and discrete parameters, and the choice is not cosmetic. Continuous interpolates, so every intermediate value has to mean something. Discrete snaps between labelled values, which is what you want when halfway is nonsense. Three targets and four are different situations. Two and a half targets is not a situation.
| Name | Type | Range or labels | Default | Written by |
|---|---|---|---|---|
| intensity | Continuous | 0.0 to 1.0 | 0.5 | Ability resolution |
| target_count | Discrete | 1, 2, 3, 4 | 1 | Targeting system |
| cooldown_state | Discrete | ready, cooling | ready | Cooldown timer |
The fifth column saves arguments. If combat and ability systems both write intensity, they will disagree inside a week and nobody can say which value produced the quiet heal in the bug report.
Intensity Drives Layers and a Filter, Not a Master Fader
Wiring intensity straight to event gain is the usual mistake. Small heals go quiet, large heals go loud, and the cue either vanishes under gunfire or clips the bus. Players read that as distance or as a mixing error, not as importance.
Keep the attack transient near constant and move the body under it. Three destinations cover most heals: the gain of a sustained shimmer layer, the cutoff of a low-pass on the tail, and the send to a short plate. At 0.2 the shimmer sits 15 dB down and the filter closes near 1.2 kHz. At 1.0 the shimmer is at unity, the filter is open and the send is up 4 dB.
Curves and seek speed
A linear curve in linear gain rarely sounds linear. A shape that moves fast between 0.1 and 0.4 and then flattens gives small heals usable resolution and keeps big ones off the ceiling.
Seek speed is the part people forget. If intensity jumps from 0.2 to 0.9 on the frame the ability lands, the filter snaps audibly on the tail. A seek of 150 to 200 ms on the filter and 60 to 100 ms on gain smooths it without making the heal late. Past roughly 250 ms the sound arrives after the health bar.
Target Count Is Discrete, and the Sound Caps It
target_count is discrete: 1, 2, 3, 4, capped at four by the sound rather than the game. A party heal on twenty players is not twenty copies. The ear reads density, not instance count, and twenty stacked voices mostly produce a transient pile-up and a pumping compressor.
Discrete stops let each value carry its own automation, which matters because spread is not linear in count. One target is centred and dry. Two sit at plus and minus 20 percent width with a 40 ms offset. Four spread wider, 40 to 70 ms between arrivals, pitch offsets from 0 to 40 cents. The first voice plays unmodified and on time because it carries the identity.
Stagger is not a delay
Four identical copies offset by 50 ms sound like a slapback. Change the layer subset per voice: the second and fourth can drop the sub, the third can use the brighter variant. Pan alternately rather than sweeping, and keep the last arrival inside 200 ms of the first or the cue bleeds into the next beat.
When a party heal and a single-target heal must sound related, that relationship is a layer swap in one event, not a copied timeline. See party versus single heals.
Cooldown Is a State, Not a Permission
A denied heal says the input arrived and was refused, and it usually has to land in well under 100 ms or the player presses again. I build it as a separate event: it fires from the input handler rather than from ability resolution, it is often quieter and more centred, and it must play when the heal event itself must not start.
A discrete cooldown_state parameter still earns a place on the heal event when the ability has a ready chime in the same family. The labels ready and cooling let that chime share the bank, the bus and the effects chain with the heal.
A parameter reports state; it does not grant permission. Do not start the heal with the parameter set to denied, because a running event that produces no audible layer still holds a voice, an instance slot in the profiler and a place in the snapshot blend. Start the refusal instead. Its timing is covered in the sound of an ability on cooldown.
Snapshots, Defaults, and What Preview Hides
Snapshots shape the mix an event lands in, which is a different job from shaping the event. For a heal system two states earn their place: low health and pause.
For low health, duck music 2 or 3 dB, pull ambience down slightly and open a little space between 2 and 5 kHz. Fade in over 150 to 250 ms so the duck does not read as its own event, and out over 400 to 600 ms. Keep the heal out of the snapshot body: it applies to the whole bus, so two heal sources get shaped together.
Snapshot intensity and event intensity can also compound. If both duck the same bus, the second heal in a chain sounds quieter than the first for no reason the player can see.
Defaults, ordering and stacking
If code never sets a parameter, the event plays the value saved in the bank, which can differ between the editor session and the last export. Declare a default and log a warning when it was unset at start.
A parameter set after start is applied at the next mix, so the first 20 to 50 ms play the old value. Set intensity and target count before starting the instance, and use the ignore-seek-speed flag when a value has to land rather than glide.
Built-in parameters come from the runtime. Distance, speed and listener direction follow the listener transform, and the 3D preview only approximates a strafing camera or an emitter on a moving socket. The editor also plays one instance; an encounter may start six heals inside 300 ms, where the voice count, the tail and the snapshot blend behave differently.
Global parameters are the other runtime difference: one value across thirty events saves calls but obscures who wrote it. Getting the call site right is most of the integration work, which is why FMOD in Unity is worth reading before the first event is wired.
What the Sheet Hands to a Programmer
The document has to survive being read by someone who has never opened FMOD.
- snake_case names, no spaces, no units in the name. intensity plus a documented range beats intensity_0_1.
- One range scale per project. Mixing 0 to 1 with 0 to 100 in one bank yields a hundredfold error that shows up as a silent layer, not a crash.
- A default for every parameter, and the documented behaviour when nothing is set.
- The bank and event path beside each parameter, because a programmer searches for the string.
- A version line, updated in the same commit as the bank export.
Cache the parameter id in hot paths and reset parameters on a recycled instance. A pooled emitter that keeps target_count from its last play is easy to ship and hard to reproduce.
None of this is specific to heals. The same reasoning runs through designing for middleware parameters, and a heal is a good place to practise because the state space fits in your head.
The check: play 0.2 and 0.9 intensity, one target and four, then the refusal beside them. If the four cases sound related and none of them needed a new timeline, the sheet is done. If one did, the gap was in the sheet, not in the event.