Skip to main content

Asset Build

A published game normally ships the Content/ folder as raw files and decodes them at load time: PNGs through Texture2D.FromStream, Aseprite files through the engine's own parser, audio through NVorbis and NLayer. The asset build compiles that folder with the MonoGame Content Builder (MGCB) into .xnb files, so a build loads pre-decoded textures and pre-flattened Aseprite frames instead. It is off by default and changes nothing in the editor, which keeps loading raw files.

Turning it on​

Build > Asset Build in the menu bar holds the switch and the tool:

ItemWhat it does
Compile assets on buildStores AssetBuild.Enabled in ProjectSettings.json. When on, every game build runs the asset build before copying content.
Build assets nowRuns the asset build alone into <project>/bin/AssetBuild/<platform> (the standalone tool's output, never used by a game build) and shows a report window with counts, timings and errors. The folder is emptied before every run.
Clean asset buildDeletes that output and the intermediate folder.
SettingsPlatform, compression, texture options, include and exclude globs, rules, and the extension scaffold.
New pipeline extensionOpens the settings window at the scaffold section (see Extending the asset build).
Show last reportReopens the report of the last run, including one started by a game build.
Open output folderOpens the last output in the file browser.

The same switch is exposed to the gateway as assetbuild.settings and the tool as assetbuild.run (platform, clean, copyRaw, output inside the project); assetbuild.status and assetbuild.clean round it out, and build.game compileAssets= overrides the stored setting for one build.

Incremental builds​

MGCB keeps compiled outputs under obj/AssetBuild/<platform>/compiled/<tool-version> and dependency records beside them in obj/AssetBuild/<platform>/obj. Game builds and Build assets now share this cache. Recreating a publish or standalone output folder only copies the current compiled assets from the cache; it does not force another compile. MGCB rebuilds changed sources, dependencies, or processor settings, and missing outputs. The first build after enabling this cache compiles everything once.

The report distinguishes newly compiled files from up to date files. Clean asset build deletes both the standalone output and the persistent cache, so the next run recompiles everything. Removed or renamed assets are excluded from the next output and index.

What gets compiled​

SourceCompiled asRuntime typeNotes
.png .jpg .bmp .tgaTextureImporter + TextureProcessorTexture2DPremultiplyAlpha follows the setting; ColorKeyEnabled off; mipmaps off; TextureFormat from the setting (Color or Compressed).
.aseprite .aseAsepriteImporter + AsepriteProcessor (Voltage.Pipeline)AsepriteFileFrames, layers, tags, slices and per-layer pixel data are flattened at build time, so the runtime skips cel decompression and compositing.
.wavWavImporter + SoundEffectProcessorSoundEffectOnly when CompileAudio is on; PCM .xnb files are larger than the source.
.ogg .mp3copied rawSoundEffectDecoded at load time as today; CompileAudio converts them through SoundEffectProcessor when on.
.fnt + textureBitmapFontImporter + BitmapFontProcessor (Voltage.Pipeline)BitmapFontUses the engine's existing BitmapFontReader.
.vtileset .vasset .vtimeline .vprefab .vscene .json .tmx .tsx .metacopied rawas todayJSON and Tiled formats stay as files; the AOT-safe readers need no change.
.fx .mgfxountouchedEffectEffects keep the separate mgfxc path from the Effects menu.
everything elsecopied raw

Files matched by an exclude glob, and files under Content/Voltage (engine content), are never compiled.

How a build finds compiled assets​

The asset build writes Content/content.index next to the compiled files: one line per compiled asset, source path<TAB>asset name, where the source path is the project-relative path the scene data and scripts use (Content/Characters/Hero.aseprite) and the asset name is the MGCB name relative to the content root, without extension (Characters/Hero). When two sources would share a name (Hero.png and Hero.aseprite), the later one gets its extension appended (Characters/Hero_aseprite).

VoltageContentManager reads the index once at startup when it exists. Every Load* method, LoadByType<T> behind AssetReference, and therefore every SpriteRenderer, SpriteAnimator, TilemapRenderer, audio component and legacy script call such as Content.LoadTexture("Content/UI/Cursor.png") or AsepriteUtils.LoadAsepriteAnimation(entity, "Content/Characters/Hero.aseprite", "Idle") first asks the index for a compiled name and loads the .xnb through the base ContentManager when one exists. Anything without an entry loads the raw file exactly as before, so a project can compile a subset, or nothing, and still run. The editor never has an index, so it never changes behaviour.

A compiled source is never shipped: the build output holds either the .xnb or the raw file, and files without a compiled counterpart are always copied. Game builds start from an empty output folder every time, and a standalone run empties bin/AssetBuild/<platform> first and removes any .xnb the current run did not produce, so renamed or deleted sources leave nothing behind.

Extending the asset build​

Rules decide what happens to an extension before the built-in table does, so a project can route its own file types through custom importers, or change how a built-in type is handled (route .png through another processor, or copy or skip a type). A rule has a name, extensions, an action (compile, copy or skip), the importer and processor class names, optional Key=Value processor parameters, and optionally the project-relative DLL that holds the classes; rules with no DLL use MonoGame's built-in importers. Rules live under AssetBuild.Rules in ProjectSettings.json, in the settings window, and on the gateway as assetbuild.rules, assetbuild.rule.set and assetbuild.rule.remove. Editor plugins register rules in code with AssetBuildRules.Register; a project rule for the same extension wins.

Build > Asset Build > New pipeline extension (or assetbuild.scaffold name=Level extension=.lvl) writes a working starting point:

  • Pipeline/Level/Level.csproj with LevelImporter, LevelProcessor and LevelWriter, a .NET 8 class library referencing MonoGame.Framework.Content.Pipeline. The game project excludes Pipeline/** from its own compile (new projects carry the exclusion; the scaffold adds it to older ones).
  • Scripts/Content/LevelAsset.cs with the runtime LevelAsset type, its LevelReader, and a module initializer that registers a raw-file loader through VoltageContentManager.RegisterLoader, so [AssetType(typeof(LevelAsset))] AssetReference fields load in the editor and in raw builds too.
  • The rule Level for .lvl pointing at Pipeline/Level/bin/Release/net8.0/Level.dll.

When a rule's DLL sits in a folder with exactly one .csproj, the asset build runs dotnet build -c Release on it whenever a source is newer than the DLL, before generating the response file. The scaffolded project references Voltage.Pipeline through the VoltagePipelineDir MSBuild property, which the editor passes on that command line, so the csproj carries no machine-specific path; building it by hand needs -p:VoltagePipelineDir=<editor folder>/Pipeline/. Rules are validated wherever they come from: the DLL must sit inside the project, importer and processor must be class names, parameters must be Key=Value, and none of them may contain ; or line breaks; a rule that fails a check fails its files with the reason. Extension projects must stay on .NET 8, which is what mgcb loads. Only the runtime file compiles with the game scripts; the pipeline project is never loaded by the editor or the game.

Tooling​

The build uses the dotnet-mgcb tool at the same version as the engine's MonoGame package, installed into a tool manifest inside the project (<project>/.config/dotnet-tools.json) with dotnet tool restore, which the editor runs the first time and whenever the version changes. Custom importers live in Voltage.Pipeline.dll, which ships beside the editor and is passed to MGCB with /reference. The generated response file is written to <project>/obj/AssetBuild/<platform>.rsp and can be inspected or run by hand with dotnet mgcb /@:<file>; it deliberately does not use the .mgcb extension, which the game's own MonoGame builder task would pick up during publish.

Requirements: the .NET SDK on the path (already needed for game builds). No Visual Studio components are needed.

Diagnostics​

The report window and assetbuild.run list every file with its outcome: compiled, copied, skipped by a glob, or failed with MGCB's message; a file that MGCB accepted but produced no .xnb for is reported as failed too. assetbuild.status reports the tool version, whether the manifest is restored, the last output and its index size. log.tail carries the same lines with the [AssetBuild] prefix.