| name | gum-packaging |
| description | Bundle a Gum project into a single .gumpkg file (tar+brotli) for distribution. Trigger when shipping a built game, optimizing initial load on web/BlazorGL, or when the user mentions 'gum pack', '.gumpkg', or wants fewer loose Content files. Covers gumcli pack, runtime loading, and the loose-vs-bundle toggle for diagnostics. |
Gum Packaging (.gumpkg)
gumcli pack walks a Gum project's dependencies and writes a single tar+brotli bundle (.gumpkg) containing the elements, font cache, and external textures. The project can be either of Gum's two on-disk formats — XML (.gumx/.gusx/.gucx/.gutx/.behx) or JSON (.gumj/.gusj/.gucj/.gutj/.behj, converted from XML via gumcli convert-to-json) — pack and runtime bundle loading both resolve dependencies against whichever family the project actually is. At runtime, Gum picks loose vs. bundle from the extension of the path you pass — .gumx/.gumj load loose, .gumpkg loads the bundle. There is no sibling probing: the extension is the single source of truth (a probe would be a guaranteed-404 HTTP request on Blazor/WASM). So the path your code passes must match the artifact your build deployed.
When to use
- Shipping a build — fewer files to copy, faster startup on web targets, no static-asset manifest bloat.
- BlazorGL/WASM specifically — many small
.gusx/.gucx/.png files are slow to fetch over HTTP; one .gumpkg is one request.
- Don't pack during authoring — the Gum editor saves loose files. Keep loose during development; pack for distribution.
Pack command
Run from the project directory that contains the .gumx:
"$GUMCLI" pack Content/GumProject/GumProject.gumx
Default output is GumProject.gumpkg next to the .gumx. Override with -o:
"$GUMCLI" pack Content/GumProject/GumProject.gumx -o build/GumProject.gumpkg
gumcli is the GumCli .NET tool — dotnet tool install -g GumCli, or pin it in a local .config/dotnet-tools.json and invoke via dotnet gumcli (preferred in a build, so the tool version tracks the Gum NuGet version). See the gumcli skill.
Categories (--include)
Default is core,fontcache,external — everything. Trim if your build pipeline regenerates pieces:
core — the project + element files (.gumx+.gusx/.gucx/.gutx/.behx, or the JSON equivalents)
fontcache — generated .fnt/.png under FontCache/
external — sprite-source .pngs and custom fonts referenced by the project but living outside Core/FontCache. This includes a Font value that's a project-relative .ttf path (not just CustomFontFile) — Gum detects it from the .ttf extension alone, no registration needed. Requires Gum/KernSmith 2026.8.28.1-preview.1+ — on an older Gum, pack still writes the file into the bundle but the runtime can't read it back out, so text silently falls back to KernSmith's embedded placeholder font instead of erroring.
"$GUMCLI" pack Content/GumProject/GumProject.gumx --include core,external
Runtime loading
Pass the extension that matches the deployed artifact — .gumpkg in bundle builds, .gumx/.gumj in loose builds. The build already knows the mode, so surface it as a compile constant and switch on it:
FlatRedBallService.Default.Initialize(this, new EngineInitSettings
{
#if GUM_BUNDLE
GumProjectFile = "GumProject/GumProject.gumpkg"
#else
GumProjectFile = "GumProject/GumProject.gumx"
#endif
});
Define GUM_BUNDLE from the same MSBuild property that flips deployment (see the csproj pattern below). Do not probe File.Exists to choose at runtime — on streaming platforms (Blazor/WASM) that miss is a 404, which is exactly what the extension-as-source-of-truth design avoids.
Web must bundle. Loose .ganx animation files can't be enumerated over HTTP, so in loose mode on WASM animations silently don't load. Ship web as .gumpkg.
.NET version requirement
The bundle loader requires .NET 7+ (pure-managed brotli + tar). FRB2 targets net10.0+, so this is always satisfied.
Blazor / KNI WASM target
Loading on Blazor WebAssembly works out of the box with FRB2 — FlatRedBallService.Initialize installs a TitleContainer.OpenStream hook on Gum's FileManager so all bundle and asset reads route through the static-web-asset manifest. No game-side code change is needed.
There's nothing to do per project, but be aware of two constraints if you ever pull bundles in outside FRB2:
- The decompression must be pure-managed (Gum uses
BrotliSharpLib + SharpCompress). The BCL System.IO.Compression.BrotliStream and System.Formats.Tar both throw PlatformNotSupportedException on browser-WASM.
- TitleContainer rejects rooted paths (
/Content/...). FRB2's hook strips leading separators automatically; a custom hook must do the same.
FRB2 csproj integration pattern
Gate pack-vs-loose behind an MSBuild property so you can flip between modes for diagnostics:
<PropertyGroup>
<UseGumPackage Condition="'$(UseGumPackage)' == ''">false</UseGumPackage>
<DefineConstants Condition="'$(UseGumPackage)' == 'true'">$(DefineConstants);GUM_BUNDLE</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(UseGumPackage)' != 'true'">
<Content Include="Content\GumProject\**\*.*"
Exclude="Content\GumProject\**\*.gumpkg"
CopyToOutputDirectory="PreserveNewest" />
<None Remove="Content\GumProject\**\*.*" />
</ItemGroup>
<ItemGroup Condition="'$(UseGumPackage)' == 'true'">
<Content Include="Content\GumProject\GumProject.gumpkg"
Link="Content\GumProject\GumProject.gumpkg"
CopyToOutputDirectory="PreserveNewest" />
<None Remove="Content\GumProject\**\*.*" />
</ItemGroup>
<Target Name="PackGumProject"
BeforeTargets="AssignTargetPaths"
Condition="'$(UseGumPackage)' == 'true'"
Inputs="@(GumSourceFiles)"
Outputs="Content\GumProject\GumProject.gumpkg">
<Exec Command="dotnet tool restore" />
<Exec Command="dotnet gumcli pack Content\GumProject\GumProject.gumx" />
</Target>
Toggle from the command line:
dotnet build -p:UseGumPackage=true
dotnet build -p:UseGumPackage=false
Always .gitignore the generated .gumpkg — it's a build output, not source.
Verification
After a packed build, confirm the deployed Content/GumProject/ contains only GumProject.gumpkg (no .gumx, no Screens/, no FontCache/). If you still see loose files, the bundle code path won't run and you're not actually testing it.
To confirm which mode actually loaded at runtime, check GumService.Default.CurrentProjectResolution?.UsedBundle. The mode is driven by the extension your code passes (the GUM_BUNDLE switch), not by which files happen to be on disk — renaming deployed files won't change it.
Exit codes
| Code | Meaning |
|---|
| 0 | Bundle written |
| 1 | Dependency files missing on disk |
| 2 | Project failed to load, or invalid --include |