Table of Contents

Class ContentDirectories

Namespace
GrindFest.Content
Assembly
GrindFest.dll

Where the built content directories live, and the one place that registers them.

Layout, one directory per content build, beside the content package it belongs to:

<StreamingAssets>/Content/base/content-directory/          the base game
<persistentDataPath>/Mods/<mod>/Content/content-directory/  one per mod

Why beside the package: the two halves of a scope belong together. The package carries the data (index, assets.json, defs) and the content directory carries the art the keys resolve to, so a scope moves, ships and gets deleted as one thing.

Why not the Addressables layout: a content directory is build output, and build output inside Assets/ is imported as assets. Under StreamingAssets it is copied verbatim into the player, which is what a content build wants, and on Web the same path is reached through the preloaded VFS — hence ExistsAsync(string, CancellationToken), which is the project's abstraction for "a folder on desktop, a URL in a browser".

Registration order is the mod-override mechanism. ContentLoadManager searches registered directories in reverse registration order, so registering the base game first and mods after makes a mod's version of a key win for free — no id remapping, no InternalIdTransformFunc, which is what ModAssetCatalogs needed with Addressables. <xref href="GrindFest.Content.ContentPaths.ListModFolders" data-throw-if-not-resolved="false"></xref> returns mods in dependency order, so a mod that depends on another is registered after it and overrides it, as it should.

public static class ContentDirectories
Inheritance
object
ContentDirectories

Fields

FolderName

The build's output folder name inside a content package.

ModScopePrefix

The content scope a mod's roots carry: mod:<mod folder name>.

Declared on the runtime side because both halves need it and neither owns the other's: the editor names a mod's scope with it, and the runtime derives the mod's key namespace (mod/<name>/…) from the same folder name. It is deliberately the same convention the Addressables group used, so migrating a mod changes how its assets are declared and nothing about what they are called.

PathInStreamingAssets

Where the base game's content directory sits inside StreamingAssets, and therefore inside the player.

It is declared here, on the runtime side, because the build processor that places it there is editor-only: a constant two files would otherwise each declare, and a mismatch between them is a content directory that is shipped to the wrong path and silently missing at runtime.

Properties

BaseDirectory

Methods

ModDirectory(string)

A mod's content directory: the sibling of its index.json.

RegisterAllAsync(CancellationToken)

Registers every content directory that exists: base first, then mods in dependency order so that a later mod overrides the ones it depends on. Returns how many registered, and logs the ones that exist but could not be registered (a name collision or a directory built for another build target) because those are silent content holes otherwise.

Zero is a legitimate answer, and it is the migration's switch: with nothing registered, AssetLibrary falls through to Addressables exactly as it did before the migration, which is what keeps the game playable while scopes are converted one at a time.