Version: Unity 6.6 (6000.6)
Language : English
Include scenes in a content build
Avoiding asset duplication

Load content directories

Each call to BuildPipeline.BuildContentDirectory populates the specified output directory with the built content from one or more root assets.

At runtime, a script must call ContentLoadManager.RegisterContentDirectory once per content directory to make its content available:

ContentDirectoryHandle handle = ContentLoadManager.RegisterContentDirectory(localPath);

This loads the build manifest for the content directory to populate in-memory data structures. Root assets from the registered content directory become available through the ContentLoadManager APIs.

After registering a content directory, you can call ContentLoadManager.GetRootAssets to discover what root assets are available inside it.

GetRootAssets<T> returns the root assets of type T from every registered content directory, not only from the most recently registered one. This is how code in a Player build reaches content in a content directory, and how content in one content directory build reaches content in another. To query a single content directory instead, pass the ContentDirectoryHandle that RegisterContentDirectory returned.

When attempting to load an asset or scene, Unity searches through the loaded content directories in reverse order of their registration, meaning the last registered is the first searched, as in a stack.

Load content directories built outside the Assets folder

If you build content directories into Assets/StreamingAssets, Unity imports every file that the content build produces. For a large content build, this wastes time and disk space each time Unity imports the project. To avoid this, build the content directories to a folder outside the Assets folder and copy them into the Player build instead. The following example builds into Builds/ContentDirectories.

The example has two parts:

  • A runtime helper class that registers content directories by name, in both Play mode and a Player build.
  • An Editor build callback that copies the content directories into the Player build.

The following sections describe the runtime class first, then the Editor build callback that enables it in a Player. The Web platform needs extra handling in both parts. For more information, refer to Preload content directories for the Web platform.

Register content directories by name

The following class registers content directories by name and tracks their handles for cleanup. In Play mode it reads from the build output location. In a Player it reads from the StreamingAssets folder, where the build callback copied them. On the Web platform, preloaded files are in the Emscripten virtual file system at /vfs_streamingassets.

// Registers content directories by name and unregisters them on shutdown.
public class ContentDirectoryManager
{
    // Build output location for the content directories, relative to the
    // project folder. It's outside the Assets folder, so Unity doesn't
    // import the built content.
    public const string ContentBuildsPath = "Builds/ContentDirectories";

    readonly List<ContentDirectoryHandle> m_Handles =
        new List<ContentDirectoryHandle>();

    // During Play mode the content directories are read from the build
    // output location. In a Player they're read from the StreamingAssets
    // folder, where the Player build copied them.
    static string contentRootPath
    {
        get
        {
UNITY_EDITOR
            return ContentBuildsPath;
f UNITY_WEBGL
            // Preloaded StreamingAssets files are in the Emscripten virtual
            // file system at this fixed path.
            return "/vfs_streamingassets";
e
            return Application.streamingAssetsPath;
if
        }
    }

    public void Register(string directoryName)
    {
        string path = contentRootPath + "/" + directoryName;
        m_Handles.Add(ContentLoadManager.RegisterContentDirectory(path));
    }

    public void UnregisterAll()
    {
        for (int i = m_Handles.Count - 1; i >= 0; i--)
        {
            ContentLoadManager.UnregisterContentDirectory(m_Handles[i]);
        }

        m_Handles.Clear();
    }
}

Register each content directory by name from a bootstrap scene:

ContentDirectoryManager contentDirectories = new ContentDirectoryManager();
contentDirectories.Register("MyContentDirectory");

Call UnregisterAll on shutdown, after each asset is released and each scene from the content directories is unloaded.

Copy the content directories into the Player build

BuildPlayerProcessor.PrepareForBuild runs before a Player build. The following build callback uses it to add the content directories to the build with BuildPlayerContext.AddAdditionalPathToStreamingAssets, which copies files into the StreamingAssets folder of the Player without importing them into the project. For a Web build, it also writes the preload manifest.

// Copies the built content directories into the Player build.
class ContentDirectoryDeployment : BuildPlayerProcessor
{
    // A Web build preloads the files listed by every manifest in this
    // folder into its virtual file system.
    const string k_WebPreloadFolder =
        "Library/PlayerDataCache/WebGLPreloadedStreamingAssets";

    const string k_WebPreloadManifest =
        k_WebPreloadFolder + "/content-directories.manifest";

    public override void PrepareForBuild(
        BuildPlayerContext buildPlayerContext)
    {
        string buildsPath = ContentDirectoryManager.ContentBuildsPath;

        // Skip if no content directories exist. This allows other builds
        // in the same project to proceed without content directories.
        if (!Directory.Exists(buildsPath))
            return;

        string[] directories = Directory.GetDirectories(buildsPath);
        if (directories.Length == 0)
            return;

        // Unity copies the contents of buildsPath into the root of the
        // StreamingAssets folder, so each content directory keeps its own
        // folder name.
        buildPlayerContext.AddAdditionalPathToStreamingAssets(buildsPath);

        // Preserve the types used by the content so that managed code
        // stripping does not remove them.
        foreach (string directory in directories)
        {
            if (BuildHistory.TryGetBuildSummaryForOutputPath(
                directory, out BuildReportSummary summary))
            {
                if (BuildHistory.TryGetBuildReportDirectory(
                    summary.BuildSessionGUID, out string reportDirectory))
                {
                    buildPlayerContext.AddPreviousBuildReportDirectory(
                        reportDirectory);
                }
            }
        }

        if (buildPlayerContext.BuildPlayerOptions.target ==
            BuildTarget.WebGL)
        {
            WriteWebPreloadManifest(buildsPath, directories);
        }
        else if (File.Exists(k_WebPreloadManifest))
        {
            // Remove the manifest from an earlier Web build, so that it
            // can't preload files in a later one.
            File.Delete(k_WebPreloadManifest);
        }
    }

    // The Web platform serves the StreamingAssets folder over HTTP, which
    // a browser can't read synchronously. RegisterContentDirectory needs
    // synchronous access, so list each file for the build to preload.
    static void WriteWebPreloadManifest(string buildsPath,
        string[] directories)
    {
        Directory.CreateDirectory(k_WebPreloadFolder);

        using (StreamWriter writer =
            new StreamWriter(k_WebPreloadManifest, false))
        {
            foreach (string directory in directories)
            {
                string[] files = Directory.GetFiles(
                    directory, "*", SearchOption.AllDirectories);

                string directoryName = Path.GetFileName(directory);
                foreach (string file in files)
                {
                    string relativePath = directoryName + "/" +
                        Path.GetRelativePath(directory, file);
                    writer.WriteLine(relativePath.Replace('\\', '/'));
                }
            }
        }
    }
}

Because AddAdditionalPathToStreamingAssets copies the contents of the folder you pass it, each content directory keeps its own folder name at the root of StreamingAssets. Build the content directories before you build the Player, so that the callback finds them.

The example also looks up each content directory’s build history folder using BuildHistory.TryGetBuildSummaryForOutputPath and passes it to AddPreviousBuildReportDirectory, which preserves the types used by the content during managed code stripping. For more information, refer to How code stripping affects content.

Preload content directories for the Web platform

RegisterContentDirectory requires synchronous file access, and a browser can’t read from the network synchronously. A Web build instead preloads specific files from the StreamingAssets folder into its virtual file system, which makes them available synchronously at runtime.

To select the files to preload, write a manifest file with the .manifest extension to Library/PlayerDataCache/WebGLPreloadedStreamingAssets, with one path per line relative to the StreamingAssets folder. The build reads every manifest in that folder, so several packages can each contribute one. The WriteWebPreloadManifest method in the previous example lists every file in every content directory.

Write the manifest from PrepareForBuild, as the previous example does, so that the Player build finds it. A build callback that runs after the build, such as IPostprocessBuildWithReport, is too late, because a Web build decides whether to pack each file into its virtual file system while it assembles the build.

Important: Library/PlayerDataCache is an internal Editor build cache, and this manifest location isn’t a supported API. Also delete the manifest when you no longer need the files preloaded, because a stale manifest keeps files in the virtual file system.

Unload a content directory

To clean up resources, call ContentLoadManager.UnregisterContentDirectory once per content directory after Loadable.Release has been called on each loaded asset, and after each scene has been unloaded (for example, via SceneManager.UnloadSceneAsync). Unity logs an error if UnregisterContentDirectory is called while any files are open.

Additional resources

Include scenes in a content build
Avoiding asset duplication