Using Assets (.glb, textures, audio)
Ship models, textures and sounds inside your plugin. The host serves them versioned and immutable-cached; the SDK loads them with one hook.
Layout
my-plugin/
vibelands-plugin.json
client/index.jsx
assets/ ← anything in here ships with the plugin
chest.glb
sparkle.png
chime.oggThe hosted sandbox builder bundles assets/ into the signed plugin artifact (budget: 25 MB total) and the host serves it at /plugins/<id>/<version>/assets/... for hosted releases. Versioned URLs mean hard immutable caching with zero stale-asset bugs after updates.
Loading
import {
usePluginGltf, // .glb / .gltf - suspends while loading
usePluginTexture, // images - suspends
usePluginAudioBuffer, // decoded via the HOST audio context - null until ready
usePluginAssetUrl, // escape hatch: just the resolved URL string
} from '@vibelands/plugin-sdk/client';
export function ChestModel({ position }) {
const gltf = usePluginGltf('chest.glb');
return <primitive object={gltf.scene.clone()} position={position} />;
}Loaders cache per URL - and the URL embeds your version, so caches roll over cleanly on release.
Audio: always ride the master bus
usePluginAudioBuffer decodes with the host's AudioContext so playback can route through the master gain - that's what makes your sounds correctly muffle underwater and inside interiors. Requires the audio permission.
import { usePluginAudio, usePluginAudioBuffer } from '@vibelands/plugin-sdk/client';
export function useChime() {
const audio = usePluginAudio(); // needs "permissions": ["audio"]
const buffer = usePluginAudioBuffer('chime.ogg');
return () => {
const ctx = audio.getAudioContext();
if (!ctx || !buffer || !audio.isAudioEnabled()) return;
const source = ctx.createBufferSource();
source.buffer = buffer;
source.connect(audio.getMasterGain()); // ← the important line
source.start();
};
}WARNING
Never new Audio(url) or build your own AudioContext - that audio escapes interior/underwater filtering and the user's volume settings.
Practical limits
| Aspect | Rule |
|---|---|
Total assets/ size | ≤ 25 MB per plugin (check and the hosted build enforce it) |
| Formats | .glb (preferred over .gltf+bin), compressed textures, .ogg audio |
| Draco/KTX2 | not provided by the host - ship uncompressed glb or embed your own decoder |
| Paths | relative inside assets/, no .. traversal (the host 404s it anyway) |
| Disposal | loader caches live for the session; prefer .clone() when mounting the same GLTF many times |
Tier A + assets example: a picnic table at spawn
import { definePluginClient, usePluginGltf, useTerrainSampler } from '@vibelands/plugin-sdk/client';
function PicnicLayer() {
const gltf = usePluginGltf('picnic-table.glb');
const getGroundHeight = useTerrainSampler();
const x = 8, z = -6;
return <primitive object={gltf.scene} position={[x, getGroundHeight(x, z), z]} name="picnic-table" />;
}
export default definePluginClient({ WorldLayer: PicnicLayer });