An example of splitting audio content across several content directories, using the content directory API released in Unity 6.6. Currently the project builds with 6.6, it will be upgraded to 6.7 after that is released.
It uses a "Core" content directory that simulates the non-DLC content that would always ship with the player. Additional audio clips are then built into separate content directories (aka "Sound Packs"). Root assets — ScriptableObjects nominated as the entry points of a build — organize this content for both building and loading.
This project can be tested in Editor play mode or built to a player (using the Build Profile window).
When testing in Editor play mode, open Bootstrap.unity.
Two sounds play automatically when the example starts. After that the keyboard drives everything:
| Key | Action |
|---|---|
1 - 9 |
Play a clip from the Core content directory |
A - E |
Play a clip from SoundPack1 |
F - J |
Play a clip from SoundPack2 |
Del |
Stop playback |
F1 |
Show / hide the on-screen key list |
Esc |
Quit (in the Editor, exit play mode) |
Each clip is named after the key that plays it, so a.mp3 plays when you press the A key. The keys
that do anything therefore depend on which content directories have been built and registered — F1
lists the ones that are currently available, grouped by the content directory that provided them.
Select the "Content Directory Profile" assets in the Assets/Editor/ContentBuilds folder and click
"Build Content Directory" in the Inspector to start each build.
You must build the "Core" content directory before the project will run at all, because the startup
scene lives inside it. The Sound Packs are optional; build them and the A - J keys start working.
Content directory builds are written to Build/ContentDirectoryBuilds, which keeps generated content
out of the Assets folder and out of source control. A BuildPlayerProcessor copies them into
StreamingAssets during the player build.
There is also a custom build script with menu options under "Example". These exist mainly for CLI builds, which are convenient for agentic access to the project. Working directly in the Editor needs no custom build script.
Content directory and player builds both use the active platform, as selected in the Build Profile window. The profile assets do not pin a platform, so switching the active platform and rebuilding is all that is required.
At build time, root assets decide what goes into each content directory, and the player build picks up whatever content directories have been built:
flowchart LR
subgraph roots [Root assets]
CAC[CoreAudioClips]
ARC[AudioResourcesCore]
AR1[AudioResourcesSoundPack1]
AR2[AudioResourcesSoundPack2]
end
subgraph profiles [Content Directory Profiles]
PC[Core]
P1[SoundPack1]
P2[SoundPack2]
end
subgraph out [Build/ContentDirectoryBuilds]
BC[Core]
B1[SoundPack1]
B2[SoundPack2]
end
CAC --> PC
ARC --> PC
AR1 --> P1
AR2 --> P2
PC -- BuildContentDirectory --> BC
P1 -- BuildContentDirectory --> B1
P2 -- BuildContentDirectory --> B2
BC & B1 & B2 -- ContentDirectoryDeployment --> SA[Player Build]
Boot[Bootstrap.unity] -- Build Profile --> SA
StartScene.unity and the audio clips end up in the Core and Sound Pack
builds because the root assets reference them.
At runtime, the bootstrap scene registers the content directories and then hands over to the startup scene, which reads its clips from whatever is registered. Shutdown runs in the reverse order:
sequenceDiagram
participant B as Bootstrap scene<br/>(RegisterAndLaunch)
participant C as ContentLoadManager
participant S as StartScene<br/>(AudioManager)
B->>C: RegisterContentDirectory for Core, SoundPack1, SoundPack2
B->>C: GetRootAssets of CoreAudioClips
B->>S: LoadSceneAsync(startupScene)
S->>C: GetRootAssets of AudioResources, per content directory
Note over S: Key press: Loadable.Load() and play
Note over B,S: Quit
B->>S: UnloadSceneAsync
S->>C: Release every loaded clip
B->>C: UnregisterContentDirectory for each handle
Bootstrap.unity is the only scene built into the player. It hosts RegisterAndLaunch, which
registers the Content Directories and loads the startup scene out of the Core directory.
StartScene.unity lives inside the Core content directory and hosts the audio components and the
on-screen key list.
Example audio clips are in the Assets/Samples folder, with one folder per content directory that
consumes them:
Core,SoundPack1andSoundPack2hold the clips named after keyboard keys.Startupholdsclip01andclip02. These are reached through named fields onCoreAudioClipsrather than by key, so they are the one case where the clip name is not a keyboard key.Extraholds spare clipsktop. Nothing references them; they are there for the "add another Sound Pack" exercise below.
The RootAssets folder contains the ScriptableObjects that reference those clips:
CoreAudioClips.assetreferences clips through named fields, one per intended use, such asstartupSound. A single asset of this type is expected.AudioResourcesCore.assetis a more general table of clips, each tracked by name.AudioResourcesSoundPack1.assetandAudioResourcesSoundPack2.assetuse the same ScriptableObject type and reference the clips for each Sound Pack.
Each AudioResources asset names one folder under Assets/Samples and keeps its table filled from the
audio clips in it, so the table never has to be maintained by hand. Use the "Track a Directory of Audio
Clips" button in the Inspector to choose the folder; after that, adding, removing, renaming or moving a
clip in the folder updates the asset on its own.
The Editor folder contains content excluded from the player build:
ContentBuildsholds the content directory Profile assets, one per content directory, each referencing the root assets to include and the output path to write. The Sound Pack profiles set theUseArchiveoption, so each pack is written as a single.archivefile, while Core is left as loose files to show both layouts.ContentDirectoryProfile.csandContentDirectoryProfileEditor.csdefine the profile ScriptableObject itself, and the Inspector that edits it and launches its build.AudioResourcesEditor.csadds the "Track a Directory of Audio Clips" button to theAudioResourcesInspector.AudioResourcesWatcher.csis anAssetPostprocessorthat refills anAudioResourcesasset whenever the folder it tracks changes.ContentDirectoryDeployment.cscopies the built content directories into the player build, writes the listing file the player reads, and preloads the files for a Web build.BuildAll.csprovides the "Example" menu items used for CLI builds.
The Scripts folder contains the MonoBehaviours and ScriptableObjects:
RegisterAndLaunch.csruns from the bootstrap scene and owns the content directories for the whole session: it registers them, loads the startup scene, and unregisters them on shutdown.CoreAudioClips.csis a root asset that assigns specific sounds to specific moments.PlayFromCoreAudioClips.csplays the startup sounds through theCoreAudioClipsnamed fields.AudioResources.csis a root asset holding a general library of clips that can be played by name. One or more can be built into any content directory. The folder it tracks is an Editor only field, since a player needs the clips but not the folder they were gathered from.AudioManager.csassembles the clips from every registered content directory into one list of key bindings, and plays them on the matching key press.ContentDirectoryManager.csfinds and registers content directories, and unregisters them again.AudioKeyLegend.csdraws the on-screen key list.
- Use of the regular player build UI, with minimal content in the player itself.
- Use of content directories to build content that is not part of the player build.
- Use of content directory Profile assets to define content-only builds, without a custom build script.
- Use of
Loadable<T>to reference content for a build. ALoadable<AudioClip>field, rather than a plainAudioClipfield, defers loading the audio until it is needed instead of pulling every sample in at startup. - Use of
LoadableSceneIdto reference a scene that lives in a content directory, and load it by id. - Use of
ContentLoadManager.GetRootAssets<T>()to find content, working the same way in the player and in Editor play mode. - Use of a serialized
Dictionaryfield, soAudioResourcesmaps clip names to clips with no custom serialization or Inspector code. - Use of an
AssetPostprocessorto keep a root asset in step with a folder of assets, so a list of content is maintained by the Editor rather than by hand or by a pre-build step. - Use of
BuildPlayerProcessor.PrepareForBuild()andBuildPlayerContext.AddAdditionalPathToStreamingAssets()to pull content directory builds into the player, without the cost of putting built content inAssets/StreamingAssets. - Discovery of content directories based on build folder convention, so that new Sound Packs can be added without changing code.
- Releasing
Loadablecontent when the MonoBehaviour that loaded it is destroyed. - Ordered shutdown: the startup scene is unloaded before its content directory is unregistered, since a directory cannot be torn down while content loaded from it is still in use.
The following are some suggested exercises that could be implemented to extend the project, and learn more about the Unity 6.6 build and load system:
- Follow the instructions above and build for several platforms.
- Add a key to the
AudioManagerthat plays all available clips in sequence, with a short delay between each. - Extend the
AudioManagerto print additional statistics, for example the size of the clips that are currently loaded. - Add another Sound Pack using the clips in
Assets/Samples/Extra. This should not require changing any code — the clips are already namedk,l,mand so on, so they can be played from the keyboard. Adding clips to a folder that an existingAudioResourcesasset already tracks requires no Editor work at all. - Split the Core content directory in two, and confirm the key list still reports each clip against the directory that provided it.
- Add a keyboard option to reload the Sound Pack content directories at runtime. Add or remove
SoundPack directories in the player's StreamingAssets folder to test this. Note: this exercise is
not suitable for platforms where the StreamingAssets folder is read-only, or is located inside a
container file (
.apk,.jar,.data, and so on). - Port the example to use content directories through Addressables.
- Adjust the code to find Sound Packs in other locations on the local file system.
- Add code to discover and download soundpacks from a web server to a local file system location, then load them in the player. Although Unity does not have full support for remote distribution of content directories in Unity 6.6, the simple structure of this project lends itself to a "write your own" solution.