Appearance
kids.kapish.audio
unity
Technical reference for the kids.kapish.audio Unity package -- audio management for Carrot Unity projects.
Package Identity
- Package name:
kids.kapish.audio - Display name: Carrot.Audio
- Version: 0.1.0
- Minimum Unity: 6000.0
- License: MIT
- Dependencies:
kids.kapish0.1.0,kids.kapish.addressables0.1.0
Assembly Structure
Carrot.Audio (Runtime)
- Path:
Runtime/Carrot.Audio.asmdef - Root namespace:
Carrot.Audio - References:
Carrot,Carrot.Precompiled,Carrot.Addressables,Unity.Addressables,Unity.ResourceManager - Engine references: Yes
- Platforms: All
The main runtime assembly. Contains AudioManager, AudioEvent, AudioHandle, AudioGroup, AudioGroupSettings, AudioSourcePool, and MusicPlayer.
Carrot.Audio.Editor (Editor)
- Path:
Editor/Carrot.Audio.Editor.asmdef - Root namespace:
Carrot.Audio.Editor - References:
Carrot,Carrot.Audio,Carrot.Editor - Platforms: Editor only
Placeholder for custom inspectors and editor tooling. Currently contains no scripts.
Architecture
Singleton Manager
AudioManager extends MonoSingleton<AudioManager> from kids.kapish. It owns the source pool, group settings array, and the MusicPlayer instance. All playback flows through the manager.
The manager is lazily initialised on first use (EnsureInitialized). Group settings are dynamically sized to match the AudioGroup enum, preserving any existing inspector values when the enum grows.
Playback Flow
- Caller invokes
AudioManager.Instance.Play(audioEvent)or the positional overload - Manager checks
AudioEvent.CanPlay()(cooldown gate) and marks the event as played - For direct clips:
GetDirectClip()selects a clip (random or sequential), a pooledAudioSourceis acquired, configured (volume, pitch, loop, spatial blend), and started - For Addressable clips: a loading placeholder is registered in
activeSounds, thenAssetLoader.LoadAsync<AudioClip>runs. On completion, a source is acquired and playback begins. If the sound is stopped before loading finishes, the load result is discarded - An
AudioHandleis returned to the caller with a unique ID
Active Sound Tracking
All playing sounds are tracked in Dictionary<uint, ActiveSound>. The Update loop:
- Ticks fade progress for any sounds in
FadeState.FadingOut - Marks completed fades and naturally-finished non-looping sounds for cleanup
- Returns completed sources to the pool
- Ticks the
MusicPlayer
Fade System
AudioHandle.FadeOut(timeMs) sets the FadeState on the ActiveSound struct. Each frame, the volume is lerped from its base level to zero. When the fade completes, the sound is stopped and its source returned to the pool. Fade timing is in milliseconds for consistency with the MusicPlayer.
AudioEvent Design
AudioEvent is a CarrotObject (ScriptableObject with grouped inspector support) configured entirely in the inspector:
- Clip selection: Supports both direct
AudioClip[]references andstring[]Addressable keys. Direct clips are checked first, then Addressables. - Randomisation: When
randomiseis true, clips are selected randomly. When false, they cycle sequentially. - Variance:
volumeVarianceandpitchVarianceadd random offsets each play. Volume is clamped to 0-1, pitch to 0.1-3.0. - Cooldown: Minimum time between plays (in seconds). Uses
Time.timefor tracking. - Spatial blend:
Optional<float>override. If unset, the manager defaults to 1.0 for positional plays and 0.0 for non-positional.
Source Pooling
AudioSourcePool wraps ComponentPool<AudioSource> from kids.kapish. On construction, it creates a template GameObject with a pre-configured AudioSource (playOnAwake = false). The pool instantiates clones from this template.
On return, sources are stopped and reset (clip cleared, loop/volume/pitch reset to defaults) before being recycled.
The pool grows on demand. The initial size is configurable on the AudioManager inspector (default 16).
Music System
MusicPlayer is a separate class (not a MonoBehaviour) owned by AudioManager. It uses two dedicated AudioSource components (not from the pool) for crossfading:
current-- the active music sourceoutgoing-- the previous track, fading out
Crossfade flow:
PlayAsyncis called with a newAudioEventorAssetAddress- Any pending Addressable load is cancelled
- The current source becomes
outgoing - A new source is created via
AudioManager.AcquireMusicSource() - The new track starts at volume 0 (if fading) and ramps up over
fadeMs - The outgoing source fades to 0 and is destroyed when complete
Music sources are added directly to the manager's GameObject and destroyed when no longer needed (not pooled, since there are at most two at any time).
The Tick(deltaTime) method is called from AudioManager.Update() to drive crossfade progress.
Group Mixing
AudioGroupSettings holds per-group Volume (0-1) and Muted state. EffectiveVolume returns 0 when muted, otherwise the raw volume.
The final volume applied to any source is: event.GetVolume() * groupSettings.EffectiveVolume * masterVolume.
Group settings are stored as an array indexed by (int)AudioGroup. The array is auto-resized in EnsureGroupSettings() whenever the enum grows, preserving existing values.
Handle System
AudioHandle is a readonly struct containing only a uint Id and a reference to the AudioManager. Methods delegate to the manager. Default handles (IsValid == false) are safe to call -- all methods no-op via null checks.
Handle IDs are monotonically increasing (++nextHandleId). There is no reuse or wrap-around handling, which is fine for the ~4 billion plays available from a uint.
Key Design Decisions
Pooled sources for SFX, dedicated sources for music. SFX sources churn rapidly and benefit from pooling. Music uses at most two sources and needs to persist across fades, so dedicated components are simpler and more robust.
Addressable loading is transparent.
AudioEventtreats direct and Addressable clips uniformly. The manager handles the async load internally and presents the sameAudioHandleAPI to callers.Value-type handles.
AudioHandleis a readonly struct to avoid allocations on everyPlaycall. Default values are safe (all methods no-op).Fade timing in milliseconds. Matches common audio middleware conventions and avoids confusion with per-frame deltaTime.
ScriptableObject events.
AudioEventassets decouple sound design from gameplay code. Designers configure clips, variance, and groups in the inspector; code just callsPlay.MusicPlayer as a plain class. Avoids the overhead and lifecycle complexity of another MonoBehaviour. The manager ticks it explicitly, keeping the update order deterministic.
Usage Guide
Practical guide to the kids.kapish.audio package -- audio management for Carrot Unity projects.
Installation
Add kids.kapish.audio as a dependency in your package's package.json or install it via the Unity Package Manager from the Carrot registry.
json
{
"dependencies": {
"kids.kapish.audio": "0.1.0"
}
}Reference the Carrot.Audio assembly in your .asmdef:
json
{
"references": ["Carrot.Audio"]
}Setup
The AudioManager is a singleton. Either add it to a scene manually or let it auto-create on first access. You can configure these settings in the inspector:
- Master Volume -- global volume multiplier
- Group Settings -- per-group volume and mute (auto-sized to match the
AudioGroupenum) - Source Pool Size -- initial number of pooled
AudioSourcecomponents (default 16)
Creating Audio Events
Create an AudioEvent asset via Create > Carrot > Audio > Audio Event in the Project window.
Inspector fields:
| Field | Description |
|---|---|
| Group | Mixing group (SFX, Music, Ambient, UI, Voice) |
| Direct Clips | Array of AudioClip references for immediate playback |
| Addressable Keys | Array of Addressable address strings for runtime loading |
| Volume / Volume Variance | Base volume with optional random offset per play |
| Pitch / Pitch Variance | Base pitch with optional random offset per play |
| Cooldown | Minimum seconds between plays (0 = no limit) |
| Randomise | Pick clips randomly (true) or cycle sequentially (false) |
| Loop | Whether the sound loops |
| Spatial Blend | Optional override for 3D spatialization (0 = 2D, 1 = 3D) |
Playing Sounds
Basic 2D Playback
csharp
using Carrot.Audio;
using UnityEngine;
public class UIButtonSound : MonoBehaviour
{
[SerializeField] private AudioEvent clickSound;
public void OnClick()
{
AudioManager.Instance.Play(clickSound);
}
}3D Positional Playback
csharp
using Carrot.Audio;
using UnityEngine;
public class Explosion : MonoBehaviour
{
[SerializeField] private AudioEvent explosionSound;
void Explode()
{
AudioManager.Instance.Play(explosionSound, transform.position);
}
}Using the AudioHandle
Every Play call returns an AudioHandle you can use to control the sound later.
csharp
using Carrot.Audio;
using UnityEngine;
public class AlarmSystem : MonoBehaviour
{
[SerializeField] private AudioEvent alarmLoop;
private AudioHandle alarmHandle;
public void TriggerAlarm()
{
alarmHandle = AudioManager.Instance.Play(alarmLoop);
}
public void StopAlarm()
{
// Immediate stop
alarmHandle.Stop();
}
public void FadeAlarm()
{
// Fade to silence over 2 seconds
alarmHandle.FadeOut(2000f);
}
void Update()
{
if (alarmHandle.IsPlaying)
{
// Alarm is still running
}
}
}Default (uninitialized) handles are safe -- Stop(), FadeOut(), and IsPlaying all no-op gracefully.
Music
Access the music player through AudioManager.Instance.Music.
Play a Track from an AudioEvent
csharp
using Carrot.Audio;
using UnityEngine;
public class LevelMusic : MonoBehaviour
{
[SerializeField] private AudioEvent levelTheme;
async void Start()
{
// Crossfade to the level theme over 1.5 seconds
await AudioManager.Instance.Music.PlayAsync(levelTheme, fadeMs: 1500f);
}
}Play from an Addressable Address
csharp
using Carrot.Addressables;
using Carrot.Audio;
async void PlayBossMusic()
{
var address = new AssetAddress("Audio/Music/BossTheme");
await AudioManager.Instance.Music.PlayAsync(address, fadeMs: 2000f);
}Pause, Resume, and Stop
csharp
var music = AudioManager.Instance.Music;
// Pause during menus
music.Pause();
// Resume gameplay
music.Resume();
// Stop with fade
music.Stop(fadeOutMs: 1000f);
// Stop immediately
music.Stop(fadeOutMs: 0f);Group Mixing
Control volume and mute state per group at runtime -- ideal for settings screens.
csharp
using Carrot.Audio;
void ApplyAudioSettings(float sfxVolume, float musicVolume, bool muteVoice)
{
var mgr = AudioManager.Instance;
mgr.SetGroupVolume(AudioGroup.SFX, sfxVolume);
mgr.SetGroupVolume(AudioGroup.Music, musicVolume);
mgr.SetGroupMuted(AudioGroup.Voice, muteVoice);
// Master volume affects everything
mgr.MasterVolume = 0.8f;
}Reading current values:
csharp
float sfxVol = AudioManager.Instance.GetGroupVolume(AudioGroup.SFX);
float effectiveVol = AudioManager.Instance.GetEffectiveVolume(AudioGroup.SFX);
// effectiveVol = sfxVol * masterVolume (0 if group is muted)Addressable Clips
AudioEvents support both direct clip references and Addressable string keys. You can mix both on the same event -- direct clips are checked first, falling back to Addressables if none are assigned.
This is useful for keeping initial scene load times low: assign frequently-used clips directly, and let rarer clips load on demand via Addressables.
AudioEvent "FootstepDirt"
Direct Clips: [footstep_dirt_01, footstep_dirt_02, footstep_dirt_03]
Addressable Keys: [] <-- all direct, instant play
AudioEvent "VoiceLine_Boss"
Direct Clips: []
Addressable Keys: ["VO/Boss/Intro_01"] <-- loaded on demandWhen an Addressable clip is loading, the sound is tracked as "loading" in the manager. If you stop the handle before loading completes, the load is discarded cleanly.
Cooldowns and Variance
Cooldowns
Set Cooldown on an AudioEvent to prevent the same sound from firing too rapidly. Useful for rapid-fire weapons, footsteps, and UI feedback.
csharp
// Even if called every frame, the event will only play once per cooldown period.
AudioManager.Instance.Play(footstepEvent);Pitch and Volume Variance
Add subtle variation to repeated sounds to avoid the "machine gun" effect:
- Volume Variance 0.1 -- volume will randomly vary by +/-0.1 from the base
- Pitch Variance 0.15 -- pitch will randomly vary by +/-0.15 from the base
Combined with randomised clip selection, this gives natural-sounding repetition with zero code.
Spatial Blend
By default, Play(event) is 2D (spatial blend 0) and Play(event, position) is 3D (spatial blend 1). To override this, set the Spatial Blend optional field on the AudioEvent:
- Set to
0to force 2D even when played with a position - Set to
0.5for a half-spatial mix - Leave unset for the automatic 2D/3D behaviour
Sequential vs Random Clips
When Randomise is on (default), clips are picked randomly each play. When off, clips cycle through the array in order, wrapping back to the start. Sequential mode is useful for:
- Dialogue lines that should play in order
- Musical phrases or stingers
- Footstep patterns that alternate left/right