Skip to content

Loading Assets

Your custom components and behaviors can load external assets like images, audio files, or 3D models. Mattercraft provides two approaches depending on whether the asset is fixed or can change at runtime.

Use a constructor property with the ProjectFile type when the asset is set once and doesn’t change during the experience. The Mattercraft editor will show a file picker that filters to matching files in your project.

import { ContextManager, ProjectFile, zComponent, zLoad } from '@zcomponent/core';
import { Group } from '@zcomponent/three/lib/components/Group';
import * as THREE from 'three';
interface ConstructorProps {
/**
* The image to display
*/
source: ProjectFile<'*.+(jpg|jpeg|png|webp)'>;
}
@zComponent({ icon: 'texture' })
export class TexturedSphere extends Group {
private _material: THREE.MeshStandardMaterial;
constructor(contextManager: ContextManager, private constructorProps: ConstructorProps) {
super(contextManager, constructorProps);
this._material = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(),
this._material
);
this.element.add(mesh);
}
@zLoad()
private async _load() {
const loader = new THREE.TextureLoader();
const texture = await loader.loadAsync(this.constructorProps.source);
texture.colorSpace = THREE.SRGBColorSpace;
this._material.map = texture;
}
dispose() {
this._material.map?.dispose();
this._material.dispose();
return super.dispose();
}
}

The ProjectFile<'pattern'> type accepts a glob pattern to filter which files appear in the picker:

Pattern Matches
*.+(jpg|jpeg|png) Image files
*.+(mp3|wav) Audio files
*.glb 3D model files

The @zLoad() decorator registers the method as a loadable process - the loading screen will wait for it to complete before the experience starts.

Use a runtime property with @zUI({ values }) when the asset can be changed during the experience. This is useful for swappable textures or configurable media.

import { ContextManager, zComponent, zObserve, zUI } from '@zcomponent/core';
import { Group } from '@zcomponent/three/lib/components/Group';
import * as THREE from 'three';
@zComponent({ icon: 'texture' })
export class SwappableTexture extends Group {
private _material = new THREE.MeshStandardMaterial();
constructor(contextManager: ContextManager, constructorProps: {}) {
super(contextManager, constructorProps);
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(),
this._material
);
this.element.add(mesh);
}
@zUI({ values: 'files *.+(jpg|jpeg|png)' })
@zObserve((url, instance: SwappableTexture) => {
if (url) {
const loader = new THREE.TextureLoader();
loader.load(url, texture => {
texture.colorSpace = THREE.SRGBColorSpace;
instance._material.map = texture;
instance._material.needsUpdate = true;
});
}
})
public textureUrl?: string;
}

Runtime file properties load asynchronously after the experience has started, so they won’t delay the loading screen.

For assets that ship with your component (not user-configurable), use the standard import.meta.url pattern:

const textureUrl = new URL('./textures/default.png', import.meta.url).href;
@zLoad()
private async _load() {
const loader = new THREE.TextureLoader();
const texture = await loader.loadAsync(textureUrl);
this._material.map = texture;
}

This bundles the asset with your script, making it available without any user configuration.

Approach Use When
Constructor prop with ProjectFile Asset is required and set once (e.g., a component’s primary texture)
Runtime prop with @zUI({ values }) Asset is optional or can be swapped during the experience
Bundled with import.meta.url Asset ships with your component and isn’t user-configurable

For user-facing components, constructor props are usually the best choice - they’re loaded before the experience starts and integrate with the editor’s file picker.