Skip to content

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.ogg

The 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

jsx
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.

jsx
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

AspectRule
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/KTX2not provided by the host - ship uncompressed glb or embed your own decoder
Pathsrelative inside assets/, no .. traversal (the host 404s it anyway)
Disposalloader caches live for the session; prefer .clone() when mounting the same GLTF many times

Tier A + assets example: a picnic table at spawn

jsx
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 });

VibeLands Creator - Plugin SDK apiVersion 2