Bitmoji
The bitmoji capability brings the player's own Bitmoji into your game, as a rigged 3D model or as a head-and-shoulders image. It only describes the player who's playing, and no avatar ID ever reaches your page.
Plan a fallback
Not every player has a Bitmoji, and any Bitmoji request can fail. Keep a default character in your game's zip, and show it when:
snapchat.isSupported('bitmoji')isfalse.getAvatarInfo()reportshasAvatar: false.- A request rejects with
ERR_INTERNAL. - The game runs on the desktop stub, whose 3D avatar is an empty placeholder.
snapchat.isStubistruethere.
Check for an avatar before you request one, because the media calls reject with ERR_INTERNAL for a player without a Bitmoji:
const { hasAvatar } = await snapchat.bitmoji.getAvatarInfo();
if (!hasAvatar) useDefaultCharacter();
Media URLs
get3DAvatar() and get2DAvatar() resolve with a url. Treat it as short-lived and opaque:
- Load it as soon as you get it. It expires after about five minutes.
- Call the API again for each load rather than keeping the URL.
- Don't parse it. It has no file extension, so tell your loader the asset type instead.
3D character
get3DAvatar() returns the player's Bitmoji as a rigged GLB with a standard skeleton and no animations. Author your animation clips on that rig, bundle them in your zip, and play them on the avatar. Build your default character on the same rig, so the same clips play on both.
// The default character, bundled in the zip.
let url = './assets/avatar_default.glb';
if (snapchat.isSupported('bitmoji') && !snapchat.isStub) {
try {
const { hasAvatar } = await snapchat.bitmoji.getAvatarInfo();
if (hasAvatar) url = (await snapchat.bitmoji.get3DAvatar()).url;
} catch (e) {
console.warn('Using the default avatar:', e.message);
}
}
// Hand url to your engine's GLB loader straight away.
2D image
get2DAvatar() returns a head-and-shoulders render of the avatar. size is 'small', 'medium', or 'large', and defaults to 'medium':
const { url } = await snapchat.bitmoji.get2DAvatar({ size: 'large' });
portraitImg.src = url;
Variants
getVariants() returns the IDs of a set of variants that Snapchat curates, the same set its own selfie picker offers. The IDs have no names, so to let the player choose one, render each variant at the small size, about six at a time, and use the images as the labels:
const { variants } = await snapchat.bitmoji.getVariants();
for (let i = 0; i < variants.length; i += 6) {
const batch = variants.slice(i, i + 6);
const images = await Promise.all(
batch.map((variant) =>
snapchat.bitmoji.get2DAvatar({ variant, size: 'small' })
)
);
images.forEach(({ url }, j) => addVariantButton(batch[j], url));
}
You can also pick variant IDs while you build the game and keep them as data. Either way, variants can retire. Requesting a retired one rejects with ERR_INVALID_PARAMS, so fall back to the default image:
let image;
try {
image = await snapchat.bitmoji.get2DAvatar({
variant: savedVariant,
size: 'large',
});
} catch (e) {
if (e.message !== snapchat.errors.INVALID_PARAMS) throw e;
// The variant has retired: show the default image instead.
image = await snapchat.bitmoji.get2DAvatar({ size: 'large' });
}
avatarImg.src = image.url;
With PlayCanvas
These recipes use the PlayCanvas Engine. app is your pc.Application (this.app inside a script), and snapchat.init() has already run. With ES modules, the same names come from playcanvas instead of the pc global.
Two helpers keep loading and freeing side by side. Neither needs a file extension in the URL, because the asset type is enough:
// Registers and loads an asset; resolves when it's ready, and rejects (unregistering it) on error.
function loadAsset(name, type, url) {
const asset = new pc.Asset(name, type, { url });
return new Promise((resolve, reject) => {
asset.ready(() => resolve(asset));
asset.once('error', (err) => {
app.assets.remove(asset);
reject(err);
});
app.assets.add(asset);
app.assets.load(asset);
});
}
// Frees an asset from loadAsset. Destroy the entities that use it first.
function unloadAsset(asset) {
asset.unload(); // for a GLB, this also frees its meshes, materials, and textures
app.assets.remove(asset);
}
Bitmoji as a 3D character
Load the player's avatar, or the default one, and play a clip from your zip on it through the anim component:
async function loadAvatar() {
if (snapchat.isSupported('bitmoji') && !snapchat.isStub) {
try {
const { hasAvatar } = await snapchat.bitmoji.getAvatarInfo();
if (hasAvatar) {
const { url } = await snapchat.bitmoji.get3DAvatar();
return await loadAsset('avatar', 'container', url);
}
} catch (e) {
console.warn('Using the default avatar:', e.message);
}
}
return loadAsset('avatar', 'container', 'assets/avatar_default.glb');
}
const [avatarAsset, danceAsset] = await Promise.all([
loadAvatar(),
loadAsset('dance', 'container', 'assets/dance.glb'),
]);
const avatar = avatarAsset.resource.instantiateRenderEntity();
avatar.addComponent('anim');
avatar.anim.assignAnimation(
'Dance',
danceAsset.resource.animations[0].resource
);
app.root.addChild(avatar);
When the character leaves the game, or before you load a different avatar, destroy the entity first, so nothing still renders the freed meshes. Keep danceAsset loaded for the next avatar, and free it once the game no longer needs the clip:
avatar.destroy();
unloadAsset(avatarAsset);
Bitmoji as a UI image
Show the image on an element, here portrait, an entity with an image element component. Keep your own placeholder texture, placeholderTexture, for players without a Bitmoji:
async function loadAvatarImage() {
if (!snapchat.isSupported('bitmoji')) return null;
try {
const { url } = await snapchat.bitmoji.get2DAvatar({ size: 'large' }); // rejects without a Bitmoji
return await loadAsset('avatar-image', 'texture', url);
} catch (e) {
console.warn('No Bitmoji image:', e.message);
return null;
}
}
const imageAsset = await loadAvatarImage();
if (imageAsset) portrait.element.texture = imageAsset.resource;
To free it, put the placeholder back first:
portrait.element.texture = placeholderTexture;
unloadAsset(imageAsset);
BitmojiAPI in the API reference lists every parameter and return value.