Assets: Runtime Resources Loader
A runtime resource gallery. The project bundles three images and two JSON
files under Assets/resources, then loads them from script with
APJS.Resources. Tapping the preview cycles the active texture, while the
HUD lists the resource paths discovered by getAllPaths() and shows the
loadAll("gallery/aurora") bundle count for resources that share the same
base path.

What you'll build
- A 2D gallery card with one large image, three swatches, a status label, and a live resource-path readout.
- Runtime assets under
Assets/resources/gallery/andAssets/resources/data/, including.pngtextures and.userjsonmetadata. - A
RuntimeResourcesLoaderscript that usesAPJS.Resources.getAllPaths(),exist(),load(), andloadAll(). - A
JsonAssetmetadata read throughjsonAsset.json, used to name each card and describe what happened. - A
RecordStartreset so recordings always begin from the first card.
Open the demo
↓ runtime-resources-loader.zip
Unzip and open in Effect House (5.9.0+). The opening scene contains:
- Camera - default 3D perspective camera, untouched.
- 2D Camera - auto-created when the first 2D object was added.
- TitleText - "Runtime Resources" at the top of the UI.
- HeroImage - the large Screen Image updated by
Resources.load(). - SwatchAurora / SwatchCitrus / SwatchOcean - three small Screen Images updated with the loaded textures.
- StatusText - the current card title, resource path, and JSON note.
- PathListText - the paths returned by
Resources.getAllPaths(), plus theloadAll("gallery/aurora")result count. - GameController - empty SceneObject hosting
RuntimeResourcesLoader.
The project includes these bundled files:
Assets/resources/gallery/aurora.png
Assets/resources/gallery/aurora.userjson
Assets/resources/gallery/citrus.png
Assets/resources/gallery/ocean.png
Assets/resources/data/resource_cards.userjson
aurora.png and aurora.userjson intentionally share the same base path.
That makes APJS.Resources.loadAll("gallery/aurora") return both matching
assets, ordered by resource-type priority.
Read the script
RuntimeResourcesLoader.ts
@component()
export class RuntimeResourcesLoader extends APJS.BasicScriptComponent {
@serializeProperty heroImage!: APJS.SceneObject;
@serializeProperty swatches: APJS.SceneObject[] = [];
@serializeProperty statusText!: APJS.SceneObject;
@serializeProperty pathListText!: APJS.SceneObject;
private readonly imagePaths: string[] = [
"gallery/aurora.png",
"gallery/citrus.png",
"gallery/ocean.png",
];
private hero!: APJS.Image;
private status!: APJS.Text;
private paths!: APJS.Text;
private swatchImages: APJS.Image[] = [];
private textures: APJS.Texture[] = [];
private titles: string[] = ["Aurora Panel", "Citrus Panel", "Ocean Panel"];
private notes: string[] = ["", "", ""];
private activeIndex: number = 0;
private inited: boolean = false;
private touchCallback!: (event: APJS.IEvent) => void;
private recordStartCallback!: (event: APJS.IEvent) => void;
onUpdate(_dt: number): void {
if (this.inited) return;
if (!this.heroImage || !this.statusText || !this.pathListText || this.swatches.length < 3) return;
this.hero = this.heroImage.getComponent("Image") as APJS.Image;
this.status = this.statusText.getComponent("Text") as APJS.Text;
this.paths = this.pathListText.getComponent("Text") as APJS.Text;
this.swatchImages = [];
for (const obj of this.swatches) {
const image = obj.getComponent("Image") as APJS.Image;
if (image) this.swatchImages.push(image);
}
const cards = this.loadCards();
for (let i = 0; i < cards.length && i < this.titles.length; i++) {
this.titles[i] = cards[i].title || this.titles[i];
this.notes[i] = cards[i].note || "";
}
this.textures = [];
for (const resourcePath of this.imagePaths) {
if (!APJS.Resources.exist(resourcePath)) {
console.error("[RuntimeResourcesLoader] missing resource " + resourcePath);
continue;
}
const texture = APJS.Resources.load(resourcePath) as APJS.Texture;
if (texture) this.textures.push(texture);
}
const allPaths = APJS.Resources.getAllPaths();
const auroraBundle = APJS.Resources.loadAll("gallery/aurora");
this.paths.text =
"Bundled paths:\n" +
allPaths.slice(0, 6).join("\n") +
"\nloadAll('gallery/aurora') -> " + auroraBundle.length + " assets";
this.touchCallback = (event: APJS.IEvent) => {
const touch = event.args[0] as APJS.TouchData;
if (touch.phase !== APJS.TouchPhase.Began) return;
this.showCard((this.activeIndex + 1) % this.textures.length);
};
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.Touch, this.touchCallback, this);
this.recordStartCallback = (_event: APJS.IEvent) => {
this.showCard(0);
};
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.RecordStart, this.recordStartCallback, this);
this.showCard(0);
this.inited = true;
console.log("[RuntimeResourcesLoader] ready, paths=" + allPaths.length + ", textures=" + this.textures.length);
}
onDestroy(): void {
const emitter = APJS.EventManager.getGlobalEmitter();
if (this.touchCallback) emitter.off(APJS.EventType.Touch, this.touchCallback, this);
if (this.recordStartCallback) emitter.off(APJS.EventType.RecordStart, this.recordStartCallback, this);
}
private loadCards(): any[] {
const metadataPath = "data/resource_cards.userjson";
if (!APJS.Resources.exist(metadataPath)) {
return [];
}
const jsonAsset = APJS.Resources.load(metadataPath) as APJS.JsonAsset;
const data = jsonAsset ? jsonAsset.json : undefined;
if (!data || !Array.isArray(data.cards)) {
return [];
}
return data.cards;
}
private showCard(index: number): void {
if (this.textures.length === 0) {
this.status.text = "No runtime resources were loaded";
return;
}
this.activeIndex = index;
const texture = this.textures[index];
this.hero.texture = texture;
this.hero.stretchMode = APJS.StretchMode.Fill;
for (let i = 0; i < this.swatchImages.length; i++) {
const image = this.swatchImages[i];
if (this.textures[i]) image.texture = this.textures[i];
image.stretchMode = APJS.StretchMode.Fill;
image.opacity = i === index ? 1 : 0.42;
}
this.status.text =
this.titles[index] +
" - " +
this.imagePaths[index] +
"\n" +
(this.notes[index] || "Loaded with APJS.Resources.load().") +
"\nTap the preview to cycle.";
}
}
API notes
APJS.Resources.getAllPaths()returns the original resource paths bundled with the effect. Use it for debugging and for validating that the package contains the files you expect.APJS.Resources.exist(path)checks a path relative toAssets/resources. It accepts either a full file path or a suffix-less base path.APJS.Resources.load(path)loads the first matching resource. In this tutorial it loadsTextureobjects for the three.pngfiles and aJsonAssetfordata/resource_cards.userjson.APJS.Resources.loadAll(path)returns every matching resource. When you omit the suffix, resources that share the same base path are returned together. Here,gallery/auroramatches bothaurora.pngandaurora.userjson.JsonAsset.jsonparses a.userjsonfile into a JS object or array. Treat the returned value as a read-only runtime copy; editing it does not write back to the project asset.
Try next
- Add a fourth image under
Assets/resources/gallery/, import it, and add a matching entry todata/resource_cards.userjson. - Replace the tap-anywhere cycling with three tappable swatches by
hit-testing each swatch Image with
APJS.TouchUtils.isScreenPointOnImage. - Use a suffix-less call like
APJS.Resources.load("gallery/aurora")and compare it withloadAll("gallery/aurora")in the script logs.