| Header | Link |
|---|---|
| Purpose | Purpose |
| Use Cases | Use Cases |
| Practical Example | Practical Example |
| Core Idea | Core Idea |
| Authoring Layout | Authoring Layout |
| Path Rules | Path Rules |
| Build + Export | Build + Export |
| Runtime Mount | Runtime Mount |
| Auto Scan + Rescan | Auto-Scan--Rescan |
DLC lets you ship extra content, such as scenes, scripts, materials, and meshes,
after launch without rebuilding the base game. Each pack is authored inside the
project, exported to a single .dlc file, and mounted at startup under its own
dlc://<name>/ path space. Base content stays in res://; DLC content lives
beside it and can reference across boundaries, so add-ons integrate with the
shipped game instead of replacing it.
- Cosmetic pack shipped after launch: a
dlc://skins/pack of materials and meshes that the base game references fromres://. - Expansion campaign: new levels and scripts under
dlc://episode2/scenes/, loaded when the player owns the pack. - Free content drop: a small
.dlcdropped into the install'sdlcfolder and mounted automatically on the next launch. - Self-contained add-on: DLC-authored content uses
dlc://self/...so it keeps working regardless of the pack's final name. - Cross-pack content: one pack referencing another with
dlc://OtherName/....
Base game code owns entitlement checks and fallback behavior. A pack owns its
scenes, scripts, and assets under one mount. Use dlc://self/ for internal pack
refs so renaming the installed pack does not break it; use a named mount only
for an intentional cross-pack dependency.
DLC stays runtime-loaded, so every entry path may be absent. Treat missing packs
as a product state, not a panic. Static res:// content remains the better fit
for content required by every install.
DLC mounts automatically at startup, so game code just references dlc:// paths
like any other resource:
lifecycle!({});
methods!({
fn on_shop_open(&self, ctx: &mut ScriptContext<'_, API>) {
// Load a scene shipped in the "cosmetics" DLC, if it is installed.
match scene_load!(ctx.run, "dlc://cosmetics/scenes/shop.scn") {
Ok(_node) => { /* shop opened */ }
Err(_err) => { /* pack not installed; show base UI */ }
}
}
});If a referenced path is missing at runtime (the pack is not installed), the load fails with a normal resource/script load error, which you handle like any other missing asset.
- DLC is always runtime-loaded.
- Base game content stays in
res://. - DLC content lives under
dlc://<name>/. - DLC is authored in project source, exported to
.dlc, and mounted at startup.
Inside the project root:
project/
res/
dlcs/
NAME/
scenes/
scripts/
materials/
meshes/
...
CLI helper:
perro new_dlc --name NAMEReserved names:
selfis reserved fordlc://self/...path resolution.- You cannot create, build, or mount a DLC named
self(case-insensitive).
new_dlc creates:
dlcs/NAME/scenes/main.scndlcs/NAME/scripts/script.rsdlcs/NAME/materials/dlcs/NAME/meshes/
Create files inside a DLC with --dlc:
perro new_script --name Foo --dlc NAME --res /scripts
perro new_scene --name Intro --dlc NAME --res /scenes
perro new_animation --name Idle --dlc NAME --res /animations- Base content:
ResPath::new("res://...") - DLC content:
ResPath::new("dlc://NAME/...") - User data:
ResPath::new("user://...") - Inside DLC-authored content,
ResPath::new("dlc://self/...")resolves to the current DLC mount.
See ResPath.
Reference behavior:
- Base -> DLC: allowed (
rescan referencedlc://NAME/...). - DLC -> base: allowed (
dlc://NAME/...can referenceres://...). - DLC -> same DLC: allowed (
dlc://self/...ordlc://NAME/...). - DLC -> other DLC: allowed (
dlc://OtherName/...).
If a referenced path is missing at runtime, lookup/load fails with a normal resource/script load error.
Build one DLC:
perro dlc --name NAMEPipeline:
- Reads
project/dlcs/NAME/. - Generates the DLC scripts crate:
.perro/dlc/NAME/scripts/
- Generates the DLC pack crate:
.perro/dlc/NAME/pack/
- Builds both runtime-loadable modules.
- Packs the manifest, modules, and DLC resources into:
.output/dlc/NAME.dlc
- Compresses the final
.dlcwhen it reduces file size. - Removes the temporary
.dlc.stagingfolder after a successful pack.
Important split:
.perro/scripts/=> base game scripts only..perro/dlc/NAME/scripts/=> DLCNAMEscripts only..perro/dlc/NAME/pack/=> DLCNAMEpack lookup module only.
On startup, the runtime mounts DLC automatically.
Dev source mount:
- Scans
project/dlcs/*. - Mounts each as
dlc://NAME/.... - Uses generated DLC scripts from
.perro/dlc/NAME/scripts/.
Release installed mount:
- Scans the install directory:
LocalAppData/<ProjectName>/dlc/*.dlc
- Loads the manifest, scripts module, and pack module from each
.dlc. - Decompresses compressed
.dlcpacks in memory during mount. - Mounts each as
dlc://NAME/....
The user:// data path uses:
LocalAppData/<ProjectName>/data
So the DLC install directory is a sibling path:
LocalAppData/<ProjectName>/dlc
- Startup auto-scan is built in.
- A manual runtime rescan helper API is not exposed yet.
- A future helper (for example
scan_dlc()) can be added to trigger a remount without restart.