# Introduction to Creator Tools

This page is the starting point for creators interested in learning about building on VIVERSE.

***

## The Best on the Web

{% columns %}
{% column valign="middle" %}
VIVERSE is home to the **best content on the internet** that can be shared with audiences around the world through the **web browser**. Whether you are building engaging 3D experiences, web applications, or videos, your work can easily be hosted on and shared through VIVERSE. Our goal is to help creators distribute and monetize their work through VIVERSE's easy hosting and content distribution.
{% endcolumn %}

{% column valign="middle" %}

<p align="center"><img src="/files/5jALE1r1RLTpmbVzczg8" alt="" data-size="original"></p>
{% endcolumn %}
{% endcolumns %}

## Publishing to VIVERSE

Creators can host web-compatible content of all kinds on VIVERSE using [Studio](/publishing-with-your-viverse-account), the one-stop-shop for managing content on VIVERSE. Through Studio, creators can upload new experiences, apps, and videos, and monitor the performance of content already on VIVERSE. In addition to Studio, we also offer a [Command Line Interface](/standalone-app-publishing/publishing-to-viverse-with-the-cli) for creators uploading 3D experiences and applications to publish from the terminal (and even [integrate publishing to VIVERSE directly into their tools](https://news.viverse.com/post/wonderland-engine-viverse-integration)).

{% hint style="info" %}
Learn [how to publish your project](/how-to-publish) on VIVERSE!
{% endhint %}

**Engaging 3D experiences are the bread and butter of VIVERSE.** As part of [HTC](https://www.htc.com/us/), the VIVERSE team has a rich legacy of empowering creators building in 3D and XR. As such, we have implemented several tools to help creators building 3D experiences.

<table><thead><tr><th>Platform</th><th width="386">Description</th><th>Documentation</th></tr></thead><tbody><tr><td>Standalone App Publishing</td><td>Developers may publish to VIVERSE using any platform that can build for WebGL/HTML5. Publish from UnityWebGL, Godot, ThreeJS, Babylon, AFRAME, and more!</td><td><a href="/pages/JqHJ5DwOTqTfXk3ARPYE">See here</a></td></tr><tr><td>PlayCanvas Toolkit</td><td>PlayCanvas is an open source game engine with a web-based editor. We have created a toolkit for the PlayCanvas editor that allows creators to publish to VIVERSE with automatic support for multiplayer VIVERSE avatars.</td><td><a href="/pages/sZ5ZvAKPVvx4tjl9VjXB">See here</a></td></tr><tr><td>Create from Templates</td><td>Create from our library of world templates and add 3D elements and media directly in VIVERSE. World decoration is great for meetup hosts and beginners looking to customize their first world from a wide range of template environments.</td><td><a href="/pages/rEKzZAx6ei3CYAu3ykGN">See here</a></td></tr></tbody></table>

## Sharing Content

{% columns %}
{% column %}
VIVERSE makes it easy for creators to share  work with audiences both on and outside of VIVERSE. After uploading their creations to VIVERSE, creators may select from multiple visibility levels, participate in content curation and discovery, and embed their VIVERSE-hosted content on any webpage of their choosing.

{% hint style="info" %}
[Check out our documentation](/sharing-content) on how to share your work on VIVERSE!
{% endhint %}
{% endcolumn %}

{% column valign="middle" %}

<p align="center"><img src="/files/AJ9CEGDURDGD877ZBTM1" alt="" data-size="original"></p>
{% endcolumn %}
{% endcolumns %}

## Monetization

At VIVERSE, our goal is not only to help creators share their work, but also to **help creators earn from their creations**! We offer a variety of tools for creators to earn on VIVERSE, including engagement-based compensation, channel subscriptions, paid titles, and in-app purchases. Additionally, we frequently host competitions and programs where creators can participate and receive rewards and funding for building with VIVERSE.

{% hint style="info" %}
Learn more about [how you can earn on VIVERSE](/monetization)!
{% endhint %}

## Optimization

The web browser is the most accessible platform for content sharing across the internet. Billions of devices, from mobile phones, to desktop computers, and VR headsets, have access to the browser! However, with so many potential users and devices, it is important to ensure that your content performs well for all of your intended users. Even if you do not have experience building for the web browser, we are here to help creators bring their work to the web for the first time!

{% hint style="info" %}
Learn more about [optimizing your work for the web](/optimization#why-build-for-the-web)!
{% endhint %}

## The Creator Community & Getting Support

VIVERSE is proud to support the work many of the most innovative artists, developers, educators, and entrepreneurs across the world. Our [Community Discord Server](https://discord.gg/viversecreators) is the epicenter of our creator community and the best way to get connected with other innovative builders.

In the Discord Server, the community and VIVERSE team would love to hear your questions, bug reports, and ideas for making a more accessible platform. Simply create a new post in the #get-help channel when you land.

For issues related to your VIVERSE account, we recommend reaching out to the company-wide support channel: <https://support.viverse.com/hc/en-us>


# How to Publish

Step-by-step guide for publishing content to VIVERSE Studio. Covers all creation paths: New Experience (Game Engine, Web Builder, Open Brush), New Game, and New Video.

***

## Introduction

You can upload to VIVERSE from all web-compatible frameworks, including 3D experiences, web applications, and videos! No matter what you are building with, you can upload and manage your builds easily using [VIVERSE Studio](/publishing-with-your-viverse-account) or our command-line interface (CLI). Once uploaded, VIVERSE content is associated with your VIVERSE account and profile located at <https://worlds.viverse.com/profile>. This page guides you through the uploading process using both methods!

{% hint style="warning" %}
While 3D experiences and applications can be uploaded using either VIVERSE Studio or the CLI, **currently videos can only be uploaded via Studio, not the CLI.** Learn more about [specifications for applications and 3D experiences](/standalone-app-publishing/intro-to-standalone-app-publishing), including supported file types, requirements, and integrations with our SDKs.
{% endhint %}

The VIVERSE Studio offers a user-friendly interface that makes it easy for creators of all skill levels to upload and manage content on VIVERSE. You can learn the process for uploading [APPLICATIONS](#creating-applications) or [VIDEOS](#create-a-video) below.

### Prerequisites

* VIVERSE account
* Signed in at [https://worlds.viverse.com/](https://www.viverse.com/)

***

## Using Templates To Create Experiences or Creating An Open Brush Project

{% columns %}
{% column %}
Create an experience using a template or create an Open Brush project using the following menus. Select **Create** from the VIVERSE Studio navigation bar > **New Experience.** To use a template, Select Option B. **Web Builder.** For Open Brush projects, select **View Docs** and follow the Open Brush publishing instructions. Documentation for Creating From Templates can be found here: <https://docs.viverse.com/publishing-from-templates/creating-from-templates>
{% endcolumn %}

{% column %}

<figure><img src="/files/N4hPZgw0o0Cy6trrUeYl" alt="" width="194"><figcaption></figcaption></figure>

<figure><img src="/files/i8uUD0qlkcnKOBcMRXEM" alt="" width="375"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

## Create An Experience

{% columns %}
{% column %}

#### Games can be created using the following menus:

A. Select **Create** from the VIVERSE Studio navigation bar > **New Game**&#x20;

or

B. Select **Create** from the VIVERSE Studio navigation bar > **New Experience** > **Game Engine**.
{% endcolumn %}

{% column %}

<figure><img src="/files/74KhCKSpPGFq8llrcdmt" alt="" width="194"><figcaption></figcaption></figure>

<figure><img src="/files/9kGx1emeYP9UHTjh8ByO" alt="" width="375"><figcaption></figcaption></figure>

{% endcolumn %}
{% endcolumns %}

### Option 1: Create An Experience - Uploading the zip file

{% columns %}
{% column %}
{% stepper %}
{% step %}
Upload Your Experience Build

* In the **Upload game file** dialog, use **Option A**.
* Drag and drop a ZIP file into the upload area, or select **Select file** and choose a ZIP file.

{% endstep %}

{% step %}

#### Enter Basic Details

On **Content settings → General**, configure **Basic info**:

* **A:** Verify your uploaded game file, or use **Re-upload** if needed.
* Enter a **Name**.
* **B:** Enter a **Description** (optional).

{% endstep %}

{% step %}

#### Configure Discoverability

On **Content settings → General**, configure **Thumbnail** and **Discoverability**:

* **C:** Upload a **Thumbnail**.
* **D:** Select **Content type**.
* **E:** Choose **Categories**.
* **F:** Select additional **Genres** (optional).
* **G:** Add **Tags**.

{% endstep %}

{% step %}

#### Set Language, Audience, and Permissions

On **Content settings → General**:

* **H:** Set **Content language**.
* **I:** Configure **Audience & Age restriction**.
* **J:** Review **Content headers & permissions**.

  **Content Behavior Permissions**

  * `allow-forms` — Submit forms
  * `allow-modals` — Open modal dialogs
  * `allow-popups` — Open popup windows
  * `allow-top-navigation` — Navigate top window
  * `allow-pointer-lock` — Use pointer lock
  * `allow-presentation` — Start presentations
  * `allow-downloads` — Download files
  * `allow-orientation-lock` — Allow orientation lock
  * `allow-popups-to-escape-sandbox` — Allow popups to escape sandbox
  * `allow-top-navigation-by-user-activation` — Allow top navigation by user activation

  **Device and Sensor Access Permissions**

  * `accelerometer` — Allows access to accelerometer sensor data
  * `camera` — Allows access to device camera
  * `gyroscope` — Allows access to gyroscope sensor data
  * `magnetometer` — Allows access to magnetometer sensor data
  * `midi` — Allows access to MIDI devices
  * `window-management` — Allows multi-window management
  * `xr-spatial-tracking` — Allows access to VR/AR features
    {% endstep %}

{% step %}

#### Configure Media

On **Content settings → Media**:

* **K:** Configure **Device** support.
* **L:** Upload a **Trailer** (optional).
* **M:** Upload **Gallery** images (optional).

{% endstep %}

{% step %}

#### Configure Display Settings

On **Content settings → Display settings**:

* **N:** Configure **Branding overlay appearance**.
* **O:** Configure the **Fullscreen feature**.
* **P:** Add **Share your code** (optional).

{% endstep %}

{% step %}

#### Configure SDK and Access

On **Content settings → Access & Security**:

* **Q:** Configure **SDK settings**.
* **R:** Configure **Entry** access (optional).
* **S:** Add **Co-owners** if needed.

{% endstep %}

{% step %}

#### Submit for Review

* Select **Submit for Review**.
* Confirm the content permissions.
* Select **Submit**. Moderation usually takes less than 48 hours. If you need support with moderation, please reach out to us through [this form](https://support.viverse.com/hc/en-us/requests/new).

{% endstep %}
{% endstepper %}
{% endcolumn %}

{% column %}

<figure><img src="/files/allGta2vjiTs9Cz2LbA7" alt="" width="219"><figcaption></figcaption></figure>

<figure><img src="/files/I9ShhUgQdg3phZPol927" alt="" width="349"><figcaption></figcaption></figure>

<figure><img src="/files/7RVJilO17cBWLoCzPk9G" alt="" width="348"><figcaption></figcaption></figure>

<figure><img src="/files/OUl7DHEQ1QJAHWZ6Vv7K" alt="" width="368"><figcaption></figcaption></figure>

<figure><img src="/files/Vg2wbZLEQEr63Okk1VOT" alt="" width="348"><figcaption></figcaption></figure>

<figure><img src="/files/CbX5YQuilk9rLzjAWqwD" alt="" width="348"><figcaption></figcaption></figure>

<figure><img src="/files/Mh6Jkyf2RmAYAA22rXxf" alt="" width="368"><figcaption></figcaption></figure>

<figure><img src="/files/9wuJI8bOEZvv7cjkT8SD" alt="" width="353"><figcaption></figcaption></figure>

<figure><img src="/files/ck4dufvLmj9YcKmXMmTs" alt="" width="173"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

### Option 2: Create An Experience - Create an App ID

{% columns %}
{% column %}
{% stepper %}
{% step %}

#### Select Viverse SDK Integration

* Select Option B. **Create App ID**. The **App ID** is an important piece of information for anyone [integrating VIVERSE SDKs](/developer-tools) into their project.
  {% endstep %}

{% step %}

#### Create SDK Content

* Enter an Experience Name.
* Accept the Platform Agreement.
* Select **Create Content**.

{% endstep %}

{% step %}

#### Copy the Generated App ID

* Copy the generated App ID.
* Select **Done**.

{% endstep %}
{% endstepper %}
{% endcolumn %}

{% column %}

<figure><img src="/files/lLcedSHa55rONm8ZmAiv" alt="" width="219"><figcaption></figcaption></figure>

<figure><img src="/files/duMEaxwQh21N3fxyCD7B" alt="" width="207"><figcaption></figcaption></figure>

<figure><img src="/files/xnCSTHcplOodN7isgkgx" alt="" width="208"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

### Option 3: New Game - Use CLI Upload Tools

{% columns %}
{% column %}
{% stepper %}
{% step %}

#### View CLI Documentation

* Select **Manual**.
* Follow the [CLI upload documentation](#publishing-with-the-cli).

{% endstep %}
{% endstepper %}
{% endcolumn %}

{% column %}

<figure><img src="/files/GTgiHfRufRXduL7mccDs" alt="" width="219"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

## Create A Video

{% hint style="info" %}
Learn more about [specifications for video content](#specifications-for-video-content), including upload limits, output specs, and our transcoding matrix.
{% endhint %}

{% columns %}
{% column %}
{% stepper %}
{% step %}

#### Start a New Video

Select **Create** from the VIVERSE Studio navigation bar > **New Video**

{% endstep %}

{% step %}

#### Upload and Publish a Video

Upload a video file.

{% endstep %}

{% step %}

#### Add Info

A. Video File Name

B. Enter a Name and Description.

C. Upload a Thumbnail.

D. Configure Category, Genres, and Tags.

{% endstep %}

{% step %}

#### Add Access & Security

E. Select the Content Language.

F. Configure Audience & Age Restriction.

G. Configure Access Settings.

H. Add Passcode

I. Add Collaborators
{% endstep %}
{% endstepper %}
{% endcolumn %}

{% column %}

<figure><img src="/files/CzsSggAQCtrSeJG11FjM" alt="" width="194"><figcaption></figcaption></figure>

<figure><img src="/files/ca6MApGuq7Ep8Tjy9s67" alt="" width="217"><figcaption></figcaption></figure>

<figure><img src="/files/cfuTiea9CI9CPAZAUoTC" alt="" width="280"><figcaption></figcaption></figure>

<figure><img src="/files/tOm0onBqQ4mb9pj1Uyh2" alt="" width="280"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

## Publishing with the CLI

The [VIVERSE CLI](https://www.npmjs.com/package/@viverse/cli) can be used to publish any web-compatible WebGL / HTML5 build to the VIVERSE platform after an authentication process.

#### Installation

Using npm:

```
npm install -g @viverse/cli
```

> **Note:** This CLI requires Node.js version 22.15.0 or higher.

#### Authentication

Login to VIVERSE platform:

```
viverse-cli auth login
```

<figure><img src="/files/pLTPRlkFyVVuecyfsqSI" alt=""><figcaption></figcaption></figure>

Or directly pass in login credentials for CI/CD integration:

```
viverse-cli auth login -e <email> -p <password>
```

In such CI/CD environments, it's recommended to use environment variables:

```
viverse-cli auth login -e $VVS_EMAIL -p $VVS_PASSWORD
```

After login, check your authentication status:

```
viverse-cli auth status
```

<figure><img src="/files/NtafDYuCvms4ukSwCzcf" alt=""><figcaption></figcaption></figure>

And to logout:

```
viverse-cli auth logout
```

After [install and authentication](#installation), the VIVERSE CLI can be used to publish any working WebGL build to the VIVERSE platform after an authentication process. When publishing, you'll either access your existing projects, or create a new one.

#### Creating Applications

You can use the VIVERSE CLI to create a new application directly:

```
viverse-cli app create
```

<figure><img src="/files/Opa2ZN3rHRIGQixE23NY" alt=""><figcaption></figcaption></figure>

Or specify a name:

```
viverse-cli app create --name <application-name>
```

Alternately, you can use the [VIVERSE Studio](https://studio.viverse.com/) workflow to create an application ID:

<figure><img src="/files/26UZ9ceRhuN3js9sXPEe" alt=""><figcaption><p>From the Upload section, click the light blue "Create New World" button in the top right to open this modal.</p></figcaption></figure>

<figure><img src="/files/i8aIEj5sJIpwWEkVZKug" alt=""><figcaption><p>Once created, your App ID will display on the world page.</p></figcaption></figure>

#### Listing Applications

Once authenticaed, you can view your account's available application list:

```
viverse-cli app list
```

The output will be displayed in a table format with the following columns:

* ID: Application identifier
* STATE: Application state
* TITLE: Application name
* URL: Application preview URL

#### Publishing

Publishing content requires two inputs:

1. **App ID** — the target application to publish to (**required**)
2. **Content path** — the directory containing your content (optional if you're already in that directory)

**Option 1: Specify content path**

```
viverse-cli app publish <path> --app-id <your-app-id>
```

**Option 2: From within content directory**

```
viverse-cli app publish --app-id <your-app-id>
```

> **Note:** The App ID is required. You can use the `viverse-cli app list` command to query your existing application IDs, or view the IDs of newly created applications after using `app create`.

> **Important:** After uploading content successfully, you'll need to visit the Creator Studio website to complete the review and publishing process.

> **Warning:** The `<path>` parameter MUST point to your **build output folder** and NOT your source code folder. Publishing source code folders (containing `src/`, `node_modules/`, or development files like `.tsx`, `.jsx`, `.vue`, `.unity`, etc.) will result in non-functional content and deployment failures.

***

## Specifications for Video Content

#### Input video specifications

<table><thead><tr><th width="264.40625">Feature</th><th>Supported Formats and Types</th></tr></thead><tbody><tr><td>File Format</td><td><ul><li>Audio Video Interleave (.avi)</li><li>MPEG-1 (.mpg)</li><li>MPEG-4 (.mp4, .m4v)</li><li>MPEG transport stream (.ts)</li><li>MPEG-2 Transport Stream (.ts, .m2ts)</li><li>QuickTime Movie (.mov)</li><li>Matroska (.mkv)</li><li>Windows Media Video (.wmv)</li><li>Blu-ray Disc Audio-Video (.m2ts)</li></ul></td></tr><tr><td>File Size</td><td><ul><li>Max: 30 GB</li></ul></td></tr><tr><td>Video Codec</td><td><ul><li>MPEG-1/2/4</li><li>AVC (H.264)</li><li>HEVC (H.265)</li><li>Apple ProRes</li><li>AV1</li></ul></td></tr><tr><td>Video Resolution</td><td><ul><li>Max: 4096x2304</li><li>Min: 128x128</li></ul></td></tr><tr><td>Video Duration</td><td><ul><li>Max: 120 min</li></ul></td></tr><tr><td>Video Bitrate</td><td><ul><li>Max: 60 Mbps (H.264)</li><li>Max: 45 Mbps (H.265)</li><li>Min: 60 Kbps</li></ul></td></tr><tr><td>Audio Codec</td><td><ul><li>AAC</li><li>AC-3</li><li>MPEG Audio</li><li>MP3</li></ul></td></tr><tr><td>Audio Bitrate</td><td><ul><li>Max: 576 Kbps</li><li>Min: 24 Kbps</li></ul></td></tr></tbody></table>

#### Output video specifications

<table><thead><tr><th width="118.80078125">Resolution</th><th width="144.62109375">Codec / Bitrate</th><th width="169.1484375">Audio Specs</th><th>Description</th></tr></thead><tbody><tr><td><code>3840x2160</code></td><td>H.265 (HEVC)<br>15 - 30 Mbps</td><td>AAC-LC<br>384 kbps / 48kHz</td><td>High efficiency for VR/Desktop clarity</td></tr><tr><td><code>2560x1440</code></td><td>H.265 (HEVC)<br>8 - 14 Mbps</td><td>AAC-LC<br>384 kbps / 48kHz</td><td>The sweet spot for quality / speed</td></tr><tr><td><code>1920x1080</code></td><td>H.264 (AVC)<br>4 - 6 Mbps</td><td>AAC-LC<br>192 kbps / 48kHz</td><td>Fallback for compatibility</td></tr><tr><td><code>1280x720</code></td><td>H.264 (AVC)<br>2 - 3 Mbps</td><td>AAC-LC<br>192 kbps / 48kHz</td><td>Mobile optimization</td></tr><tr><td><code>720x480</code></td><td>H.264 (AVC)<br>1 - 1.5 Mbps</td><td>AAC-LC<br>128 kbps / 48kHz</td><td>Data saver mode</td></tr><tr><td><code>640x360</code></td><td>H.264 (AVC)<br>0.8 - 1.0 Mbps</td><td>AAC-LC<br>128 kbps / 48kHz</td><td>Legacy mobile support</td></tr><tr><td><code>400x240</code></td><td>H.264 (AVC)<br>0.4 - 0.6 Mbps</td><td>AAC-LC<br>128 kbps / 48kHz</td><td>Restricted bandwidth</td></tr></tbody></table>

#### Transcoding matrix

<table><thead><tr><th width="147.10546875">If You Upload (Input)</th><th width="340.53515625">Your Viewers Get These Resolutions and Frame Rates</th><th width="160.1171875">Max Audio Quality</th><th width="97.3828125" align="center">Quality Badge</th></tr></thead><tbody><tr><td>4K @ 60fps<br><code>3840x2160</code></td><td><strong>2160p60</strong>, 1440p60, 1080p60, 720p60, <em>480p30</em>, <em>360p30, 240p30</em></td><td><strong>384 kbps</strong> (Hi-Fi)</td><td align="center"><code>4K</code></td></tr><tr><td>4K @ 30fps<br><code>3840x2160</code></td><td><strong>2160p30</strong>, 1440p30, 1080p30, 720p30, 480p30, 360p30, 240p30</td><td><strong>384 kbps</strong> (Hi-Fi)</td><td align="center"><code>4K</code></td></tr><tr><td>2K @ 60fps<br><code>2560x1440</code></td><td><strong>1440p60</strong>, 1080p60, 720p60, <em>480p30</em>, <em>360p30, 240p30</em></td><td><strong>384 kbps</strong> (Hi-Fi)</td><td align="center"><code>HD</code></td></tr><tr><td>2K @ 30fps<br><code>2560x1440</code></td><td><strong>1440p30</strong>, 1080p30, 720p30, 480p30, 360p30, 240p30</td><td><strong>384 kbps</strong> (Hi-Fi)</td><td align="center"><code>HD</code></td></tr><tr><td>1080p @ 60fps<br><code>1920x1080</code></td><td><strong>1080p60</strong>, 720p60, <em>480p30</em>, <em>360p30, 240p30</em></td><td><strong>192 kbps</strong> (Stereo)</td><td align="center"><code>HD</code></td></tr><tr><td>1080p @ 30fps<br><code>1920x1080</code></td><td><strong>1080p30</strong>, 720p30, 480p30, 360p30, 240p30</td><td><strong>192 kbps</strong> (Stereo)</td><td align="center"><code>HD</code></td></tr><tr><td>720p @ 60fps<br><code>1280x720</code></td><td><strong>720p60</strong>, <em>480p30</em>, <em>360p30, 240p30</em></td><td><strong>128 kbps</strong> (Standard)</td><td align="center"><code>HD</code></td></tr><tr><td>720p @ 30fps<br><code>1280x720</code></td><td><strong>720p30</strong>, 480p30, 360p30, 240p30</td><td><strong>128 kbps</strong> (Standard)</td><td align="center"><code>HD</code></td></tr><tr><td>480p (Any FPS)<br><code>720x480</code></td><td><strong>480p30</strong>, 360p30, 240p30</td><td><strong>128 kbps</strong> (Standard)</td><td align="center"><code>SD</code></td></tr></tbody></table>


# VIVERSE Studio

Overview of the VIVERSE Studio creator dashboard. Covers channel analytics on the Dashboard, Partner Program information, and content management for experiences, games, and videos.

***

## Introduction

The [VIVERSE studio](https://studio.viverse.com/) is the one-stop-shop for managing all content associated with your VIVERSE account and profile. Studio includes analytics about the performance of your content and tools to manage both video and application content.

### Prerequisites

* VIVERSE account
* Signed in at <https://worlds.viverse.com/>

***

## Dashboard

{% columns %}
{% column %}
The **Dashboard** provides a summary of channel activity for the selected world and date range.
{% endcolumn %}

{% column %}

<figure><img src="/files/EekKrKJ59IhIuY1fvBZE" alt="" width="375"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

### Dashboard Sections

| Label | Section                      | Description                                                                                                                                                 |
| ----- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A     | Turn views into revenue card | Promotional card for the Partner Program. This area encourages creators to learn about monetization options in VIVERSE.                                     |
| B     | New Visitors metric box      | Shows the number of first-time visitors within the selected world filter and date range. The percentage indicates change compared with the previous period. |
| C     | Unique Visitors metric box   | Shows how many unique visitors reached the selected scope during the selected date range.                                                                   |
| D     | Views metric box             | Shows the total number of views for the selected scope and date range. When available, it also shows views over 30 seconds as a secondary metric.           |
| E     | Likes metric box             | Shows the total number of likes received during the selected scope and date range.                                                                          |
| F     | World filter dropdown        | Filters dashboard data by a specific world or by **All Worlds**. Use this when you only want to review analytics for one published world.                   |
| G     | Date range dropdown          | Filters dashboard data by date range. Use this to compare recent performance, campaign windows, or activity over a specific time period.                    |
| H     | Visitors chart section       | Trend chart showing visitor activity over time for the selected world filter and date range.                                                                |
| I     | Views chart section          | Trend chart showing view activity over time for the selected world filter and date range.                                                                   |

***

## Partner Program

{% columns %}
{% column %}
The **Partner Program** page introduces monetization options available in VIVERSE.
{% endcolumn %}

{% column %}

<figure><img src="/files/zUfDpItrZ5avhECTPY4h" alt="" width="375"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

### Partner Program Sections

| Label | Section                 | Description                                                                                                                                                    |
| ----- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A     | Learn more button       | Opens more information about the Partner Program. Select this button to learn how monetization works and what requirements may apply.                          |
| B     | Partner Program section | Main overview area for the Partner Program. This section explains that creators may be able to monetize video content and reach a broader audience in VIVERSE. |

***

## Content Management

{% columns %}
{% column %}
The **Content** page is where you manage uploaded videos, games, and experiences. The page includes tabs for switching between content categories, a search button, a filter button, list columns, and action buttons.
{% endcolumn %}

{% column %}

<figure><img src="/files/d6fAAg0Stgr8pHLa3YB3" alt="" width="375"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

### Content Page Section

| Label | Section             | Description                                                                                                                  |
| ----- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| A     | All tab             | Shows all content across videos, experiences, and games.                                                                     |
| B     | Experiences tab     | Filters the list to show only experience content.                                                                            |
| C     | Games tab           | Filters the list to show only game content.                                                                                  |
| D     | Videos tab          | Filters the list to show only video content.                                                                                 |
| E     | Search button       | Opens search so you can find content by title or keyword.                                                                    |
| F     | Filter button       | Opens filters to refine the content list.                                                                                    |
| G     | Thumbnail column    | Displays the preview image or video thumbnail for each content item.                                                         |
| H     | Title column        | Shows the title of each content item.                                                                                        |
| I     | Content type column | Indicates whether each item is a video, game, or experience.                                                                 |
| J     | Visibility column   | Shows the visibility status, such as private or unlisted.                                                                    |
| K     | Last updated column | Shows the most recent status and date for each content item, such as published, draft, uploaded, submitted, or under review. |
| L     | Views column        | Shows the number of views for published content.                                                                             |
| M     | Edit button         | Opens a published content item so you can make changes.                                                                      |
| N     | View button         | Opens the published content item for viewing.                                                                                |
| O     | Edit draft button   | Opens draft content so you can continue editing.                                                                             |
| P     | Preview button      | Opens a preview of draft or submitted content.                                                                               |

***

### Experiences Tab

Use this tab when you want to manage spatial environments, art, apps, or other experience-based content.

{% columns %}
{% column %}
The **Experiences** tab is active when the tab label is highlighted. This view shows experience content only.
{% endcolumn %}

{% column %}

<figure><img src="/files/Sx4ktGpnYnnE1fcp8xzz" alt="" width="375"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

| Label | Section                  | Description                                                    |
| ----- | ------------------------ | -------------------------------------------------------------- |
| A     | Experiences tab          | Indicates the Experiences filter is active.                    |
| B     | Experiences content list | Shows all experience content available in your Studio account. |

***

### Games Tab

Use this tab when you want to manage game projects separately from experiences and videos.

{% columns %}
{% column %}
The **Games** tab is active when the tab label is highlighted. This view shows game content only.
{% endcolumn %}

{% column %}

<figure><img src="/files/bUUoIEFmeeCIAzMqwNi7" alt="" width="375"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

| Label | Section            | Description                                              |
| ----- | ------------------ | -------------------------------------------------------- |
| A     | Games tab          | Indicates the Games filter is active.                    |
| B     | Games content list | Shows all game content available in your Studio account. |

***

### Videos Tab

Use this tab when you want to manage video clips, trailers, and captures.

{% columns %}
{% column %}
The **Videos** tab is active when the tab label is highlighted. This view shows video content only.
{% endcolumn %}

{% column %}

<figure><img src="/files/FsApSKMoBVSW2nZGzlGB" alt="" width="375"><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

| Label | Section             | Description                                               |
| ----- | ------------------- | --------------------------------------------------------- |
| A     | Videos tab          | Indicates the Videos filter is active.                    |
| B     | Videos content list | Shows all video content available in your Studio account. |

***

## Review & Content Moderation

After uploading, your content will be viewable in preview mode only. To submit your world for curation on our webpages, please select "Submit for Review" and select whether you would like iframe support for your experience.

{% hint style="info" %}
Since VIVERSE content is iframed by default on our landing pages, several web features require requesting permissions, such as `xr-spatial-tracking`, `camera`, or `allow-pointer-lock`. These can be applied to preview links in the "iframe Support for Preview" menu, and must be set when you "Submit for Review," as well.
{% endhint %}

All content must comply with the standards outlined in the VIVERSE Platform Terms of Use: <https://www.viverse.com/terms-of-use> While not an exhaustive list, we will not tolerate content which...

* Depicts graphic or disturbing nudity, gore, sexual acts, violence, whether legal or illegal
* Incites violence/hatred against or exposes personal information of individuals or groups
* Includes malware, inappropriately collects user data, or inappropriately solicits money/information from users
* Includes unauthorized connections with technologies that go against the VIVERSE terms of use


# Sharing Content

This page shared details about how VIVERSE creators can share their work and get their work discovered on VIVERSE.

***

## Creator Profile

VIVERSE makes it easy for creators to share their work with audiences on and outside of VIVERSE. All content is associated with the creator profile, which can be customized to showcase additional media and details. The VIVERSE team manually curates creator profiles who actively publish and share their work, so be sure to fully customize your creator profile with details about your creative practice!

<figure><img src="/files/zY4xpVsKuY1MlqCNClS8" alt=""><figcaption></figcaption></figure>

Your VIVERSE Creator profile has two visibility modes, public and private. In addition to your banner image, description, media, and content, private mode allows creators to view the content that your have purchased, the creators you follow, and other liked content.&#x20;

<figure><img src="/files/rX9G8XTnvem6XkmXuQpD" alt=""><figcaption></figcaption></figure>

## Discovery on VIVERSE

After your content has been approved and set to **Public,** it will automatically be discoverable based on its assigned Genres and Description details. The VIVERSE recommendation system also suggests content related to players' interests and based on a content's popularity (both likes and recent viewership).&#x20;

<figure><img src="/files/sjLfn6Q3cV5HpDaI0FcF" alt=""><figcaption></figcaption></figure>

In addition to algorithmically recommending content, the VIVERSE team manually curates and features content on our webpages which can help your work get seen by more players. **The best way to get curated is to make the VIVERSE team aware of your work.** We recommend sharing your project on social media and tagging @VIVERSEofficial, and to join our [discord server](https://discord.gg/viversecreators) to show off your work in the #share channel.

## Embedding Content

{% columns %}
{% column width="58.333333333333336%" %}
The VIVERSE team supports creators who want to share their work across the internet, including embedding on other high-traffic webpages and curation surfaces. Underneath your content, the share button presents options for sharing via social media and through iframe embedding.

Iframes are a powerful and easy way to allow users experience content hosted on VIVERSE on other webpages. When you select **Embed,** you will receive an iframe html element that can be easily added to your webpage.&#x20;

In some cases, you may need the VIVERSE team to manually approve the webpage you wish to embed on. To process this manual request, please [email the VIVERSE team](mailto:michael_morran@htc.com?cc=brooke_chen@htc.com\&subject=Request%20for%20iframe%20embedding\&body=Hello%20VIVERSE%20Team%2C%0A%0AI%20would%20like%20to%20request%20that%20%7BVIVERSE%20url%7D%20be%20approved%20for%20embedding%20on%20%7Bwebpage%20url%7D.%20This%20webpage%20is%20%7Bdescription%20of%20webpage%20you%20wish%20to%20embed%20on%7D.) with your content's URL, the URL of the webpage you want to embed your content on, and a description of the webpage.
{% endcolumn %}

{% column width="41.666666666666664%" %}
![](/files/xufqMDGHnNEIlysYNSBb)

{% endcolumn %}
{% endcolumns %}

<figure><img src="/files/lCClTfNZ2MCQEpyxr17Q" alt=""><figcaption></figcaption></figure>


# Intro to Standalone App Publishing

This page overviews the requirements, tools, and process for building applications on VIVERSE.

***

## Publishing Apps to VIVERSE

3D experiences and web applications are the foundation of VIVERSE's creator community. We invest heavily in the development of amazing interactive content and are always expanding our support for new tools and frameworks where the best experiences are being crafted for the web.

While VIVERSE supports custom publishing pipelines for a few platforms, any platform can publish to VIVERSE using [Studio](/how-to-publish) by **uploading a .zip file with an index.html file at the top level of its file tree**.&#x20;

## Supported Tools & Frameworks

<table><thead><tr><th width="185.38433837890625">Platform</th><th>Description</th></tr></thead><tbody><tr><td>HTML5 &#x26; WebGL</td><td>Creators can generally publish any project compatible with HTML5. See <a data-mention href="/pages/lFedMcq1wh7DyvT8rmya">/pages/lFedMcq1wh7DyvT8rmya</a>for details on requesting advanced features, such as user microphone permissions.</td></tr><tr><td>WebGL &#x26; WebGPU</td><td>Creators can generally publish any WebGL project on VIVERSE. Certain features for WebGPU, such as support for multi-threading, may need to be approved manually. See <a data-mention href="#bringing-your-own-game-servers-and-analytics">#bringing-your-own-game-servers-and-analytics</a>for how to submit requests for these permissions.</td></tr><tr><td>PlayCanvas</td><td>VIVERSE supports publishing from PlayCanvas through <a href="/pages/lFedMcq1wh7DyvT8rmya">VIVERSE Studio</a> and with the <a href="/pages/sZ5ZvAKPVvx4tjl9VjXB">VIVERSE Toolkit</a>, which adds multiplayer avatars, UI, and a cross-device character controller by default.</td></tr><tr><td>ThreeJS</td><td>VIVERSE supports most ThreeJS projects, as well as the many tools and frameworks that have been built on top of the library, such as React-Three-Fibre (R3F)nd AFRAME.<br><br>VIVERSE sponsors the development of an open source ThreeJS and R3F-compatible avatar system called <a href="https://pmndrs.github.io/viverse/getting-started/index">pmndrs/viverse</a>. </td></tr><tr><td>Unity WebGL</td><td>Creators can publish projects built with Unity's WebGL export pipeline, and we recommend they upgrade to the latest version of Unity to benefit from the recent upgrades for web optimization made in Unity 6. We have written multiple examples in <a data-mention href="/spaces/StEZJb1cl50eSxquMjc5/pages/tdfGRwsJsqI8VNQoT3AS">/spaces/StEZJb1cl50eSxquMjc5/pages/tdfGRwsJsqI8VNQoT3AS</a> for how to utilize our SDKs in your Unity Projects.</td></tr><tr><td>Open Brush</td><td>In December 2025, VIVERSE worked with the Icosa Foundation, to build a pipeline for publishing from Open Brush to VIVERSE with multiplayer avatars.</td></tr><tr><td>Wonderland Engine</td><td>In November 2025, VIVERSE worked with the Wonderland Engine team to develop a <a href="https://github.com/WonderlandEngine/viverse-example/tree/main">plug-in for Wonderland Engine</a>, including examples of a cross-device character controller and one-click publishing to VIVERSE.</td></tr><tr><td>Godot HTML5</td><td>VIVERSE supports publishing from Godot using their HTML5 publishing pipeline.</td></tr><tr><td>BabylonJS</td><td>VIVERSE supports publishing Babylon projects.</td></tr><tr><td>Unreal Engine</td><td>Unfortunately, there is no 1st-party pipeline for publishing from Unreal Engine to web. VIVERSE is open to discussing support for any 3rd party publishing/streaming pipelines from Unreal Engine to the web.</td></tr></tbody></table>

*Don't see your platform or want to request new features? Join our* [*Discord Server*](https://discord.gg/viversecreators) *and let us know if you would like more support.*

## Developer Tools

Along with our platform integrations that allow you to publish to VIVERSE, we also have a number of standalone developer tools that can be utilized in experiences hosted on *and* off of VIVERSE. These tools help creators get the most out of their 3D experience and make the difficult parts of online development easier.

> ***NOTE:** VIVERSE SDKs cannot be used with projects published via the* [*PlayCanvas Create SDK extension*](https://docs.viverse.com/playcanvas-sdk/playcanvas-extension-setup)*, which do not have App IDs.*

<table><thead><tr><th>Tool</th><th width="386">Description</th><th>Documentation</th></tr></thead><tbody><tr><td>Polygon Streaming</td><td>Upload and embed high quality 3D assets in web-based experiences. Polygon Streaming's innovative technology makes highly-detailed, large assets accessible to billions of devices through the web browser!</td><td><a href="/spaces/Y3WbwobASVOf4RiqnLEI">See here</a></td></tr><tr><td>Avatar &#x26; Account SDK</td><td>Utilize our avatar and virtual identity system to bring multiplayer to your 3D experience.</td><td><a href="/spaces/StEZJb1cl50eSxquMjc5/pages/ll6eQM9DqjGA2KjEj40s">See here</a></td></tr><tr><td>Leaderboard SDK</td><td>Add a leaderboard to single and multiplayer gaming experiences. Our leaderboard system allows you to keep track of player performance between sessions.</td><td><a href="/spaces/StEZJb1cl50eSxquMjc5/pages/i7IneT5Y8vdV9FZb07ve">See here</a></td></tr><tr><td>Matchmaking &#x26; Networking SDK</td><td>Add persistent data to single and multiplayer gaming experiences.</td><td><a href="/spaces/StEZJb1cl50eSxquMjc5/pages/TFbUC1Qdy1qIMF2JcWJT">See here</a></td></tr><tr><td>Storage SDK</td><td>Persist data between sessions for users associated with their VIVERSE account.</td><td><a href="/spaces/StEZJb1cl50eSxquMjc5/pages/edVDqDdQnGN04YsXLX8n">See here</a></td></tr></tbody></table>

## Bringing Your Own Game Servers & Analytics

While VIVERSE provides free services and SDKs for creators to integrate into their projects, developers may also provision and connect with their own servers/analytics on VIVERSE. This process does require manual approval by the VIVERSE team, but we are able to quickly approve these requests. Please [email the VIVERSE team](mailto:michael_morran@htc.com?cc=brooke_chen@htc.com\&subject=Request%20for%20external%20API%20approval\&body=Hello%20VIVERSE%20Team%2C%0A%0AI%20would%20like%20to%20request%20that%20%7BVIVERSE%20url%7D%20be%20allowed%20to%20connect%20to%20%7Bexternal%20URL%7D.%20This%20is%20the%20URL%20for%20%7Bdescription%20of%20the%20external%20service%20you%20would%20like%20to%20connect%20to%7D.) with your request and a description of the external server you would like to connect to.


# Unity WebGL

Code examples, compatibility guides, and technical documentation for Unity WebGL builds targeting VIVERSE. Includes render pipelines, WebGL templates, loading screens, and deployment examples.

***

## Publishing Tutorial

Anyone can publish their WebGL-compatible Unity project to VIVERSE in a few simple steps. In this guide, we'll walk through the process of creating an new Unity project, making sure it is compatible with WebGL, and publishing to VIVERSE using the [VIVERSE CLI](https://www.npmjs.com/package/@viverse/cli).

{% hint style="info" %}
For this guide, we are using the VIVERSE CLI, but it is also possible to compress and [upload your build file directly to the VIVERSE Studio](/publishing-with-your-viverse-account#upload).
{% endhint %}

While VIVERSE is a great place for multiplayer games with networked avatars — and we have a number of services that can help you implement these features — it is not required to implement networked avatars to publish to VIVERSE.

### Prerequisites

* Unity Hub and Unity installed on your device.
* [Node](https://nodejs.org/en) and [npm](https://www.npmjs.com/package/@viverse/cli) installed on your device. Please use at least Node v22 - **Only required if using the CLI, not VIVERSE Studio**

{% hint style="warning" %}
In this tutorial, we will be using Unity v6.1, however any WebGL-compatible version of Unity should be supported.
{% endhint %}

### Configure Your Unity Project

{% stepper %}
{% step %}

#### Create a Unity Project

<figure><img src="/files/Tuevx3jwYm8yoYJFPJz9" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Install the Unity Plugin

Navigate to Window > Package Manager > Unity Registry and search for "WebGL Publisher". Add the module to your project.

<p align="center"> <img src="/files/ANCJgo1FOdoQNSXMlkz2" alt=""></p>
{% endstep %}

{% step %}

#### (Optional) Enable Decompression Fallback

Compression is supported on VIVERSE, however you can enable fallback if you are encountering errors or would like to have it included. Navigate to Edit > Project Settings > Player > Web Settings > Publishing Settings and check "Decompression Fallback".

<figure><img src="/files/jlqIjq4Qy0I51X24jbAj" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Build and Publish to VIVERSE

{% stepper %}
{% step %}

#### Build Your Project

Navigate to Publish and select "WebGL Publish". In the pop-up, click "Build and Publish", selecting the desired folder for your build. When doing this for the first time, Unity will automatically publish to their web-servers for testing. For future builds, you can disable this behavior to just the builds without publishing.

<figure><img src="/files/FNEyUflsVau6qTHfODFf" alt="" width="317"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Install the VIVERSE CLI

In a terminal session, run `npm install -g @viverse/cli` to install the CLI globally. Make sure you are using at least node v22.

<figure><img src="/files/2Hu1addxggD6KWjQ3d8v" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Login to VIVERSE

In the terminal session, run `viverse-cli auth login` and enter your VIVERSE account email and password.

<figure><img src="/files/Fb1s20hxrkyQPhMm9lNc" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create VIVERSE App

In the terminal session, run `viverse-cli app create` . Once complete, copy the app ID to be used when publishing.

<figure><img src="/files/oXO7Z4NCBGbgij8FUdx9" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Publish to VIVERSE

In the terminal session, run `viverse-cli app publish {path/to/unity/webgl/build} --app-id {your app id from step 4}` referencing folder containing the index.html of your Unity build.

<figure><img src="/files/rQXNmkpecN02Whi4B6MF" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Test & Configure World Settings

Navigate to the preview url created for the world. You can also access the world and its settings in [studio.viverse.com/content](https://studio.viverse.com/content).&#x20;

<figure><img src="/files/C5J54mwg2aif5WhajmGQ" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/4QYS318YNTuCCuPByY7D" alt="" width="316"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Submit for Curation and Discovery

By default, worlds uploaded will only be accessible via preview urls. For placement and curation on our webpages, meaning your experience will be easier to share, please [submit for review](/publishing-with-your-viverse-account#upload).
{% endstep %}
{% endstepper %}

## Unity & VIVERSE Compatibility Overview

VIVERSE supports Unity WebGL builds with specific requirements and recommendations for optimal performance and compatibility. This section covers essential settings and considerations for targeting VIVERSE.

### Supported Unity Versions

VIVERSE supports the following Unity versions:

* **Unity 2021 LTS** (2021.3.x) - Recommended for stability
* **Unity 2022 LTS** (2022.3.x) - Recommended for newer features
* **Unity 2023.x** - Supported, but verify compatibility with VIVERSE SDK

**Note:** Always use LTS (Long Term Support) versions for production projects targeting VIVERSE to ensure long-term compatibility and support.

### Required Unity Modules

Install the following modules via Unity Hub:

1. **WebGL Build Support** - Essential for building WebGL projects
2. **WebGL Development Build Support** - Recommended for debugging
3. **IL2CPP** - Required for WebGL builds (automatically included with WebGL module)

Verify installation: `Edit → Preferences → External Tools` or check installed modules in Unity Hub.

### Supported Render Pipelines

**Important:** Unity WebGL does **NOT** support HDRP (High Definition Render Pipeline).

**Built-in Render Pipeline:**

* Fully supported for WebGL/VIVERSE
* Best compatibility across all browsers
* Recommended for projects targeting maximum compatibility
* No additional packages required

**Universal Render Pipeline (URP):**

* Fully supported for WebGL/VIVERSE
* **Recommended** for new projects targeting WebGL
* Better performance and modern features optimized for web platforms
* Requires URP package (installed via Package Manager)
* Optimized for low-end and web platforms

**High Definition Render Pipeline (HDRP):**

* **NOT supported** on WebGL/VIVERSE
* HDRP is designed for high-end GPUs and requires features unavailable in WebGL:
  * Real-time ray tracing
  * Advanced volumetrics
  * Compute shaders with full GPU access
  * Large render targets
  * Advanced shader models
* WebGL runs in a browser sandbox with restricted GPU access and memory
* **You must convert HDRP projects to URP for WebGL builds**

### Choosing a Render Pipeline

**For New Projects Targeting WebGL/VIVERSE:**

* **URP** is **strongly recommended** for modern features and optimal WebGL performance
* Install via `Window → Package Manager → Unity Registry → Universal RP`

**For Existing Projects:**

* **Built-in:** Works without changes, but URP offers better WebGL performance
* **URP Migration:** Use Unity's Render Pipeline Converter tool (`Edit → Render Pipeline → Universal Render Pipeline → Convert Project to URP`)
* **HDRP Projects:** **Must convert to URP** for WebGL builds (HDRP is not supported)

**Converting HDRP to URP for WebGL:**

1. Install URP package via Package Manager
2. Use Unity's Render Pipeline Converter:
   * `Edit → Render Pipeline → Universal Render Pipeline → Convert Project to URP`
3. Review and update materials (HDRP materials need URP equivalents)
4. Test shaders and lighting (some HDRP features may not have direct URP equivalents)
5. Optimize for WebGL performance (WebGL is sensitive to draw calls, textures, and shader complexity)

**Considerations:**

* URP provides better WebGL performance than Built-in
* URP has smaller build sizes
* URP supports modern shader features optimized for web
* Built-in has maximum compatibility but lower performance
* **HDRP cannot be used for WebGL builds**

### Render Pipeline Settings

**Built-in Render Pipeline:**

* No special configuration needed
* Settings in `Edit → Project Settings → Graphics`
* Configure Quality Settings as needed

**Universal Render Pipeline (URP):**

* Create URP Asset: `Assets → Create → Rendering → URP Asset`
* Assign in `Edit → Project Settings → Graphics → Scriptable Render Pipeline Settings`
* Configure URP Asset settings:
  * **Render Scale:** 1.0 (or lower for performance)
  * **HDR:** Disabled (WebGL doesn't support HDR)
  * **MSAA:** 2x or 4x (balance quality vs performance)
  * **Shadow Distance:** Adjust based on your scene needs

### Shader Compatibility

**Built-in Shaders:**

* All built-in shaders work with Built-in Render Pipeline
* Standard, Unlit, UI shaders fully supported

**URP Shaders:**

* Use URP-compatible shaders (Lit, Unlit, etc.)
* Built-in shaders won't work with URP
* Convert shaders using Shader Graph or manually rewrite
* **HDRP shaders are NOT compatible** - must convert to URP shaders for WebGL

**Custom Shaders:**

* Ensure shaders are compatible with your chosen render pipeline
* **HDRP shaders cannot be used in WebGL builds** - convert to URP equivalents
* Test shaders in WebGL build (some features may not work)
* WebGL 2.0 supports most modern shader features, but not HDRP-specific features
* WebGL is sensitive to shader complexity - optimize for performance

## Unity Formatting and Custom Loading Screens

An example of a fullscreen WebGL template can be found [here](https://github.com/VIVERSE-DOCS/viverse-docs/blob/main/samples/Unity/WebGL_FullScreen_Template.zip). The instructions for creating a WebGL fullscreen template are below.

### Understanding WebGL Templates

WebGL templates control the HTML/CSS/JavaScript wrapper around your Unity WebGL build. They determine:

* How the canvas is displayed (fullscreen, windowed, etc.)
* Loading screen appearance and behavior
* Browser UI elements visibility
* Mobile responsiveness
* Fullscreen functionality

**Default Templates:**

* Unity provides default templates (Minimal, Default, etc.)
* Custom templates allow full control over presentation

### Fullscreen Canvas Benefits

A fullscreen canvas template provides:

* **Immersive Experience:** No browser UI distractions
* **Maximum Screen Usage:** Canvas fills entire viewport
* **Better Performance:** No layout calculations for browser elements
* **Professional Appearance:** Clean, game-like presentation
* **VIVERSE Optimization:** Matches VIVERSE's immersive environment

**Key Features:**

* Canvas set to 100% width and height
* Body and container with `overflow: hidden`
* No scrollbars or browser chrome
* Responsive to different screen sizes
* Mobile-friendly viewport configuration

### Custom Loading Screen Overview

Custom loading screens enhance user experience by:

* **Branding:** Display your logo/branding during load
* **Progress Feedback:** Show loading progress to users
* **Professional Appearance:** Custom design matching your project
* **User Engagement:** Keep users informed during asset loading

**Components:**

* Loading bar container (centered on screen)
* Logo/branding element
* Progress bar (animates from 0% to 100%)
* Optional loading text or animations
* Styled with CSS for custom appearance

### Create the WebGL Template Folder Structure

{% stepper %}
{% step %}

#### Navigate to Assets Folder

In Unity, open your project and navigate to the `Assets` folder in the Project window.
{% endstep %}

{% step %}

#### Create WebGLTemplates Directory

1. Right-click in the `Assets` folder → Create → Folder
2. Name the folder `WebGLTemplates` (exact name required by Unity)
3. This folder will contain all custom WebGL templates
   {% endstep %}

{% step %}

#### Create Fullscreen Template Folder

1. Right-click on `WebGLTemplates` → Create → Folder
2. Name it `Fullscreen` (this will be the template name visible in Build Settings)
3. Unity will automatically detect this folder as a WebGL template
   {% endstep %}
   {% endstepper %}

### Create the Fullscreen HTML Template

{% stepper %}
{% step %}

#### Create index.html File

1. Right-click on the `Fullscreen` folder → Show in Explorer (Windows) or Reveal in Finder (Mac)
2. Create a new text file named `index.html` (not `index.html.txt`)
3. This file will serve as the main HTML template for your WebGL build
   {% endstep %}

{% step %}

#### Add Fullscreen HTML Structure

Open `index.html` and add the following fullscreen template code:

```html
<!DOCTYPE html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Unity WebGL Player | {{{ PRODUCT_NAME }}}</title>
    <style>
      html, body {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
        overflow: hidden;
      }
      #unity-container {
        position: absolute;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
      #unity-container.unity-mobile {
        position: fixed;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
      }
      .unity-mobile #unity-canvas {
        width: 100%;
        height: 100%;
        display: block;
      }
      #unity-canvas {
        background: #231F20;
      }
      #unity-loading-bar {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        display: none;
      }
      #unity-logo {
        width: 154px;
        height: 130px;
        background: url('unity-logo-dark.png') no-repeat center;
      }
      #unity-progress-bar-empty {
        width: 141px;
        height: 18px;
        margin-top: 10px;
        margin-left: 6.5px;
        background: url('progress-bar-empty-dark.png') no-repeat center;
      }
      #unity-progress-bar-full {
        width: 0%;
        height: 18px;
        margin-top: 10px;
        background: url('progress-bar-full-dark.png') no-repeat center;
      }
      #unity-footer {
        position: relative;
        display: none;
      }
      .unity-mobile #unity-footer {
        display: none;
      }
      #unity-webgl-logo {
        float: left;
        width: 204px;
        height: 38px;
        background: url('webgl-logo.png') no-repeat center;
      }
      #unity-build-title {
        float: right;
        margin-right: 10px;
        line-height: 38px;
        font-family: arial;
        font-size: 18px;
      }
      #unity-fullscreen-button {
        cursor: pointer;
        float: right;
        width: 38px;
        height: 38px;
        background: url('fullscreen-button.png') no-repeat center;
      }
      #unity-warning {
        position: absolute;
        left: 50%;
        top: 5%;
        transform: translate(-50%);
        background: white;
        padding: 10px;
        display: none;
      }
    </style>
  </head>
  <body>
    <div id="unity-container" class="unity-desktop">
      <canvas id="unity-canvas" tabindex="-1"></canvas>
      <div id="unity-loading-bar">
        <div id="unity-logo"></div>
        <div id="unity-progress-bar-empty">
          <div id="unity-progress-bar-full"></div>
        </div>
      </div>
      <div id="unity-warning"> </div>
      <div id="unity-footer">
        <div id="unity-webgl-logo"></div>
        <div id="unity-fullscreen-button"></div>
        <div id="unity-build-title">{{{ PRODUCT_NAME }}}</div>
      </div>
    </div>
    <script>
      var container = document.querySelector("#unity-container");
      var canvas = document.querySelector("#unity-canvas");
      var loadingBar = document.querySelector("#unity-loading-bar");
      var progressBarFull = document.querySelector("#unity-progress-bar-full");
      var fullscreenButton = document.querySelector("#unity-fullscreen-button");
      var warningBanner = document.querySelector("#unity-warning");

      function unityShowBanner(msg, type) {
        function updateBannerVisibility() {
          warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
        }
        var div = document.createElement('div');
        div.innerHTML = msg;
        warningBanner.appendChild(div);
        if (type == 'error') div.style = 'background: red; padding: 10px;';
        else {
          if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
          setTimeout(function() {
            warningBanner.removeChild(div);
            updateBannerVisibility();
          }, 5000);
        }
        updateBannerVisibility();
      }

      var buildUrl = "Build";
      var loaderUrl = buildUrl + "/{{{ LOADER_FILENAME }}}";
      var config = {
        dataUrl: buildUrl + "/{{{ DATA_FILENAME }}}",
        frameworkUrl: buildUrl + "/{{{ FRAMEWORK_FILENAME }}}",
        codeUrl: buildUrl + "/{{{ CODE_FILENAME }}}",
#if MEMORY_FILENAME
        memoryUrl: buildUrl + "/{{{ MEMORY_FILENAME }}}",
#endif
#if SYMBOLS_FILENAME
        symbolsUrl: buildUrl + "/{{{ SYMBOLS_FILENAME }}}",
#endif
        streamingAssetsUrl: "StreamingAssets",
        companyName: "{{{ COMPANY_NAME }}}",
        productName: "{{{ PRODUCT_NAME }}}",
        productVersion: "{{{ PRODUCT_VERSION }}}",
        showBanner: unityShowBanner,
      };

      if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
        var meta = document.createElement('meta');
        meta.name = 'viewport';
        meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
        document.getElementsByTagName('head')[0].appendChild(meta);
        container.className = "unity-mobile";
        canvas.className = "unity-mobile";
        // Use native device pixel ratio for sharp rendering on high-DPI screens.
        // Cap at 2 to avoid excessive GPU load on 3x devices.
        config.devicePixelRatio = Math.min(window.devicePixelRatio || 1, 2);
      } else {
        canvas.style.width = "100%";
        canvas.style.height = "100%";
      }

      loadingBar.style.display = "block";

      var script = document.createElement("script");
      script.src = loaderUrl;
      script.onload = () => {
        createUnityInstance(canvas, config, (progress) => {
          progressBarFull.style.width = 100 * progress + "%";
        }).then((unityInstance) => {
          loadingBar.style.display = "none";
          fullscreenButton.onclick = () => {
            unityInstance.SetFullscreen(1);
          };
        }).catch((message) => {
          alert(message);
        });
      };

      document.body.appendChild(script);
    </script>
  </body>
</html>


```

**Key Features:**

* `html`, `body`, and `#unity-container` fill the viewport with `overflow: hidden` for true fullscreen
* **Desktop:** canvas `style` width/height set to `100%` in JavaScript
* **Mobile (VIVERSE-friendly):** full-viewport fixed container, canvas `100%` via CSS, viewport meta tag, and `config.devicePixelRatio = 1` to reduce high-DPI backing-store cost
* Loading bar centered on screen during asset loading
* Footer hidden by default (can be shown if needed)
  {% endstep %}

{% step %}

#### Refresh Unity Project

1. Return to Unity Editor
2. The `index.html` file should appear in the Project window under `Assets/WebGLTemplates/Fullscreen/`
3. If not visible, right-click in Project window → Refresh, or press `Ctrl+R` (Windows) / `Cmd+R` (Mac)
   {% endstep %}
   {% endstepper %}

### Customize the Loading Screen

{% stepper %}
{% step %}

#### Understanding the Loading Screen Elements

The template includes several customizable elements:

* `#unity-loading-bar`: Container for loading screen elements
* `#unity-logo`: Logo image displayed during loading
* `#unity-progress-bar-empty`: Background of the progress bar
* `#unity-progress-bar-full`: Animated progress bar that fills from 0% to 100%

All elements are centered on screen using CSS transforms.
{% endstep %}

{% step %}

#### Replace Default Loading Images

1. In the `Fullscreen` folder, replace the default Unity loading images with your custom assets:
   * `unity-logo-dark.png` (154×130px recommended) - Your logo/branding
   * `progress-bar-empty-dark.png` (141×18px) - Progress bar background
   * `progress-bar-full-dark.png` (141×18px) - Progress bar fill image
2. Alternatively, modify the CSS in `index.html` to use different image paths or create custom loading UI with HTML/CSS instead of images.
   {% endstep %}

{% step %}

#### Custom Loading Screen with HTML/CSS

To create a fully custom loading screen without images, modify the `#unity-loading-bar` section:

```html
<div id="unity-loading-bar">
  <div id="custom-logo" style="width: 200px; height: 200px; margin: 0 auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center; color: white; font-size: 48px; font-weight: bold;">
    YOUR LOGO
  </div>
  <div id="custom-progress-container" style="width: 300px; height: 20px; margin: 20px auto; background: rgba(255,255,255,0.2); border-radius: 10px; overflow: hidden;">
    <div id="unity-progress-bar-full" style="width: 0%; height: 100%; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); transition: width 0.3s ease;"></div>
  </div>
  <div id="loading-text" style="text-align: center; color: white; font-family: Arial; font-size: 18px; margin-top: 10px;">
    Loading...
  </div>
</div>
```

Update the JavaScript progress callback to also update text:

```javascript
createUnityInstance(canvas, config, (progress) => {
  progressBarFull.style.width = 100 * progress + "%";
  var loadingText = document.querySelector("#loading-text");
  if (loadingText) {
    loadingText.textContent = "Loading... " + Math.round(progress * 100) + "%";
  }
})
```

{% endstep %}

{% step %}

#### Add Custom Loading Animations

Enhance the loading screen with CSS animations. Add to the `<style>` section:

```css
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

#unity-logo {
  animation: pulse 2s ease-in-out infinite;
}

@keyframes slideIn {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}

#unity-progress-bar-full {
  transition: width 0.3s ease;
  animation: slideIn 0.5s ease-out;
}
```

{% endstep %}
{% endstepper %}

### Configure Build Settings

{% stepper %}
{% step %}

#### Open Build Settings

1. In Unity, go to `File → Build Settings...`
2. Select `WebGL` from the Platform list
3. Click `Switch Platform` if not already on WebGL (this may take a few minutes)
   {% endstep %}

{% step %}

#### Select the Fullscreen Template

1. Click `Player Settings...` (or go to `Edit → Project Settings → Player`)
2. In the Player Settings window, expand the `Publishing Settings` section
3. Under `WebGL Template`, select `Fullscreen` from the dropdown
4. The template you created should now be available in this list
   {% endstep %}

{% step %}

#### Configure Additional WebGL Settings

For optimal VIVERSE deployment, configure these settings:

**Resolution and Presentation:**

* Default Canvas Width: 1920 (or your target resolution)
* Default Canvas Height: 1080 (or your target resolution)
* Run In Background: Enabled (recommended for VIVERSE)

**Publishing Settings:**

* Compression Format: Gzip or Brotli (for smaller file sizes)
* Data caching: Enabled (improves load times for returning users)
* Name Files As Hashes: Enabled (better caching)

**Other Settings:**

* Strip Engine Code: Enabled (reduces build size)
* Managed Stripping Level: Medium or High (further reduces size)
  {% endstep %}
  {% endstepper %}

### Build the WebGL Project

{% stepper %}
{% step %}

#### Prepare for Build

1. Ensure your scene is saved (`Ctrl+S` / `Cmd+S`)
2. In Build Settings, verify the scenes you want to include are checked
3. Click `Add Open Scenes` if your current scene isn't listed
   {% endstep %}

{% step %}

#### Build the Project

1. Click `Build` in the Build Settings window
2. Choose or create an output folder (e.g., `Builds/WebGL`)
3. Click `Select Folder`
4. Unity will compile and build your project (this may take several minutes)
5. Once complete, navigate to the build output folder
   {% endstep %}

{% step %}

#### Verify Build Output

Your build folder should contain:

* `index.html` - Your custom fullscreen template
* `Build/` folder - Contains Unity WebGL build files (.data, .framework.js, .loader.js, etc.)
* `TemplateData/` folder (if using default template) - Contains template assets
* `StreamingAssets/` folder (if you have streaming assets) - Contains additional assets

The `index.html` should be your custom fullscreen template with the loading screen.
{% endstep %}
{% endstepper %}

### Test the Fullscreen Build Locally

{% stepper %}
{% step %}

#### Set Up Local Server

WebGL builds require a web server to run (cannot open `index.html` directly due to CORS restrictions).

**Option 1: Python HTTP Server (if Python installed):**

```bash
cd path/to/your/build/folder
python -m http.server 8000
```

Then open `http://localhost:8000` in your browser.

**Option 2: Node.js HTTP Server:**

```bash
npm install -g http-server
cd path/to/your/build/folder
http-server -p 8000
```

**Option 3: Unity's Built-in Server:** After building, Unity may offer to open a local server automatically.
{% endstep %}

{% step %}

#### Test Fullscreen Functionality

1. Open the build in your browser
2. Verify the loading screen appears and progress bar animates
3. Once loaded, the canvas should fill the entire browser window
4. Test the fullscreen button (if visible) or press `F11` for browser fullscreen
5. Verify no browser UI elements are visible (address bar, scrollbars, etc.)
6. Test on different screen resolutions and aspect ratios
   {% endstep %}

{% step %}

#### Test Mobile Responsiveness

1. Open browser developer tools (`F12`)
2. Enable device emulation mode
3. Test on various mobile device presets (iPhone, Android, tablets)
4. Verify the viewport meta tag works correctly
5. Test touch interactions if your project uses them
   {% endstep %}
   {% endstepper %}

### Deploy to VIVERSE

{% stepper %}
{% step %}

#### Prepare Build for Upload

1. Navigate to your build output folder
2. Select all files and folders (`index.html`, `Build/`, `StreamingAssets/` if present)
3. Create a ZIP archive of these files
4. Name it appropriately (e.g., `MyProject_WebGL_Build.zip`)
5. Verify the ZIP contains the root `index.html` file (not nested in a subfolder)
   {% endstep %}

{% step %}

#### Upload to VIVERSE Studio

1. Log in to VIVERSE Studio (<https://worlds.viverse.com/>)
2. Navigate to `Manage Content` or your project dashboard
3. Click `Upload` or `New Content`
4. Select your ZIP file
5. Wait for upload and processing to complete
   {% endstep %}

{% step %}

#### Configure VIVERSE Settings

1. In VIVERSE Studio, configure your content settings:
   * Set appropriate title and description
   * Configure access permissions (public/private)
   * Set thumbnail/preview image
   * Configure any required VIVERSE SDK settings
2. Ensure WebGL compatibility is enabled
   {% endstep %}

{% step %}

#### Preview and Publish

1. Use VIVERSE Studio's preview feature to test your content
2. Verify fullscreen behavior works correctly in VIVERSE environment (test on **mobile** as well as desktop; layout and GPU memory behave differently on phones)
3. Test loading screen appearance and timing
4. Once satisfied, submit for approval/publishing
5. After approval, your content will be available in VIVERSE
   {% endstep %}
   {% endstepper %}

### Advanced Customization

{% stepper %}
{% step %}

#### Customize Background Color

Modify the canvas background in the CSS:

```css
#unity-canvas {
  background: #231F20; /* Change to your desired color */
  /* Or use a gradient: */
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
```

{% endstep %}

{% step %}

#### Add Splash Screen or Branding

Add custom HTML before the canvas loads:

```html
<div id="splash-screen" style="position: absolute; width: 100%; height: 100%; background: #000; display: flex; align-items: center; justify-content: center; z-index: 1000;">
  <div style="text-align: center; color: white;">
    <h1>Your Game Title</h1>
    <p>Loading...</p>
  </div>
</div>
```

Then hide it in JavaScript after Unity loads:

```javascript
.then((unityInstance) => {
  loadingBar.style.display = "none";
  var splash = document.querySelector("#splash-screen");
  if (splash) splash.style.display = "none";
  // ... rest of code
})
```

{% endstep %}

{% step %}

#### Implement Custom Error Handling

Enhance error messages for better user experience:

```javascript
.catch((message) => {
  // Hide loading screen
  loadingBar.style.display = "none";
  
  // Show custom error UI
  var errorDiv = document.createElement('div');
  errorDiv.style.cssText = 'position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.9); color: white; padding: 40px; border-radius: 10px; text-align: center; z-index: 1000;';
  errorDiv.innerHTML = '<h2>Failed to Load</h2><p>' + message + '</p><button onclick="location.reload()" style="margin-top: 20px; padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer;">Retry</button>';
  document.body.appendChild(errorDiv);
});
```

{% endstep %}

{% step %}

#### Optimize Loading Performance

1. **Enable Compression:** In Player Settings → Publishing Settings, use Gzip or Brotli compression
2. **Reduce Build Size:** Enable code stripping and remove unused assets
3. **Use Addressables:** For large projects, consider Unity Addressables for on-demand loading
4. **Optimize Assets:** Compress textures, reduce polygon counts, optimize audio files
5. **CDN Hosting:** Host your build on a CDN for faster global loading times
   {% endstep %}
   {% endstepper %}

### Troubleshooting

**Template Not Appearing in Build Settings:**

* Ensure the folder is named exactly `WebGLTemplates` (case-sensitive)
* Ensure `index.html` is directly in the template folder (e.g., `Assets/WebGLTemplates/FullScreen/index.html`)
* Refresh Unity project (`Ctrl+R` / `Cmd+R`)
* Restart Unity Editor

**Loading Screen Not Showing:**

* Check browser console for JavaScript errors (`F12`)
* Verify image paths in CSS are correct
* Ensure `loadingBar.style.display = "block"` is called before Unity loads
* Check that progress callback is properly connected

**Fullscreen Not Working:**

* **Desktop:** verify canvas has `width`/`height` `100%` (set in JS in this template) and `html`/`body`/`#unity-container` fill the viewport
* **Mobile:** ensure `#unity-container.unity-mobile` and `.unity-mobile #unity-canvas` rules are present (small canvas or black bars usually mean mobile layout CSS is missing)
* If WebGL fails or stutters on high-DPI phones, confirm `config.devicePixelRatio = 1` runs in the mobile branch
* Test in different browsers (Chrome, Firefox, Edge)
* Some browsers require user interaction before allowing fullscreen API

**Build Too Large:**

* Enable compression in Publishing Settings
* Increase Managed Stripping Level
* Remove unused assets and scripts
* Consider using Unity Addressables for large content
* Compress textures and audio files

**VIVERSE Upload Issues:**

* Ensure ZIP contains `index.html` at root level
* Verify all required files are included (Build folder, etc.)
* Check file size limits in VIVERSE Studio
* Ensure WebGL build target is correct (not IL2CPP if not supported)


# ThreeJS

This document provides a guide for creating a sample app in Three.js, building the app with Vite and deploying the app to VIVERSE.

***

### Introduction

In this getting-started guide, we will cover the basics of setting up a ThreeJS project and publishing to VIVERSE using [the VIVERSE CLI](https://www.npmjs.com/package/@viverse/cli).

{% hint style="info" %}
For this guide, we are using the VIVERSE CLI, but it is also possible to compress and [upload your build file directly to the VIVERSE Studio](/publishing-with-your-viverse-account#upload).
{% endhint %}

### Installing Node.js

{% stepper %}
{% step %}

#### Download Node.js

A. Download the latest version of **Node.js (LTS)** from http <https://nodejs.org/en>.

<figure><img src="/files/BzX0Qly7JzIVa22NYk9U" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Automatically install the necessary tools

A. Use the defaults during the installation, but place a checkmark\
in the **Automatically install the necessary tools** checkbox

<figure><img src="/files/k8eMVp2Kt6gHKg1aMxq4" alt="" width="304"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Confirm Node.js is installed with at least v22

A. Open a command prompt and type: **node**, then click Enter.

B. Confirm that Node.js is installed when the following message prints in command prompt: **Welcome to Node.js v##.##.##**.

<figure><img src="/files/iQotUay0M1mFbPdAHGdo" alt="" width="373"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Installing Three.js and making an example project

This guide is a walkthrough for creating an example Three.js project

{% stepper %}
{% step %}

#### Create project folder

A. Create a new folder that will contain the project.

<figure><img src="/files/SkkCzymaj8bsc8Sd4VYC" alt="" width="334"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create the index.html

A. Create the **index.html** page inside the project folder. This can be done by creating a text file, pasting the code and saving it as a **.HTML** page or using an IDE, such as Visual Studio Code.

**index.html**

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>My first three.js app</title>
    <style>
      body { margin: 0; }
    </style>
  </head>
  <body>
    <script type="module" src="/main.js"></script>
  </body>
</html>
```

<figure><img src="/files/mOk12QVwv1u2mIOaUEPB" alt="" width="371"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create the main.js

A. Create the **main.js** page inside the project folder. This can be done by creating a text file, pasting the code and saving it as a **.JS** file or using an IDE, such as Visual Studio Code.

**main.js**

```javascript
import * as THREE from 'three';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
document.body.appendChild( renderer.domElement );

const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );

camera.position.z = 5;

function animate() {

  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;

  renderer.render( scene, camera );

}
```

<figure><img src="/files/Hh1XQdmuZf6gAWwECHjd" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Install Three.js framework

A. Three.js needs to be installed in the project folder. Open command prompt and change directories to your Three.js project.

B. Type: **npm install --save three**.

<figure><img src="/files/qOKxLCqqJ5y2Lvxj7J3u" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Confirm Three.js framework is installed

A. Confirm **node\_modules** folder and **package.json** have been added to the directory.

<figure><img src="/files/FdeKStHCLu5PtWzCN5BU" alt="" width="375"><figcaption></figcaption></figure>

B. Confirm the **three** folder folder and **.package-lock.json** have been added to the **node\_modules** directory.

<figure><img src="/files/UDPAbw7dkdbGtrfcGDhl" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Installing Vite and using it to build your project

{% stepper %}
{% step %}

#### Install the build tool Vite

A. If choosing to use **Vite** as the build tool, it needs to be installed in the Three.js project folder also. With command prompt opened and the directory set to your Three.js project, type the command: `npm install --save-dev vite`.

<figure><img src="/files/FtBvcPg8H0doszTQkYJ4" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Confirm Vite has been installed

A. Confirm **Vite** has been installed by checking for additional folders in the **node\_modules** folder.

<figure><img src="/files/OlGg6xqLdp0X3YfDRJdP" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add the Vite build settings

A. Add the Vite build settings to **package.json** file.

**package.json**

```json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "three": "^0.175.0"
  },
  "devDependencies": {
    "vite": "^6.3.2"
  }
}

```

<figure><img src="/files/NF6MMf25E6uvyyqSSyx4" alt="" width="287"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add the vite.config.js file

A. Add the **vite.config.js** file to the root of the project.

**vite.config.js**

```javascript
import { defineConfig } from 'vite';
import path from 'path';

// https://vite.dev/config/
export default defineConfig({
  base: './', // Use relative path as base URL
});
 
```

<figure><img src="/files/841cShmMZmkOp1CxiuUh" alt="" width="369"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create a development build of the Three.js project

A. To create a development build of the Three.js project, type the following command inside command prompt under the Three.js project directory: **npx vite**.

<figure><img src="/files/WHKVzNSUGZlnmwVS0gCG" alt="" width="369"><figcaption></figcaption></figure>

B. Confirm the development build of the Three.js project was built successfully when Vite provides a **localhost URL** to test.

<figure><img src="/files/zAp7sW4Hw6Whfn98TtUu" alt="" width="320"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Test development build

A. To test a development build of the Three.js project, open the browser and navigate to the URL that was printed in the previous step. In this example, the URL is <http://localhost:5173>. Confirm the app works as expected.

<figure><img src="/files/nUWt9ONi2rd9Euq5Tal2" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create production build

A. To create a production build of the Three.js project, type the following command inside command prompt under the Three.js project directory: **npx vite build**.

<figure><img src="/files/kswTc3AvB7JJB8Qnqt0C" alt="" width="375"><figcaption></figcaption></figure>

B. Confirm the production build of the Three.js project was built successfully by confirming the **dist** folder was created and populated.

<figure><img src="/files/XgSBNe96MM4bsbkSdJW3" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Installing the VIVERSE CLI

{% stepper %}
{% step %}

#### Install the VIVERSE (CLI) command-line tool

A. Inside a command prompt, type: **npm install -g @viverse/cli**, then click Enter. Installing a package with **-g** installs the package globally. The location of globally installed packages depends on your operating system and npm configuration:

* **Windows** : In windows, packages are installed in %APPDATA%\npm\node\_modules.
* **macOS and Linux** : In mac or Linux packages are typically installed in /usr/local/lib/node\_modules or a user-specific directory like \~/.npm-global.

B. Confirm that the command line tool is installed based on screen feedback.

<figure><img src="/files/rwyVuSKch8Rax6KsW5qE" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Logging in with the VIVERSE CLI

{% stepper %}
{% step %}

#### Login to VIVERSE platform

A. Open a command prompt and type: **viverse-cli auth login**, then click Enter.

B. Enter VIVERSE **email** and **password**.

C. Confirm login was successful.

<figure><img src="/files/oHUcdEPF7rPNra0ShVGC" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Publishing from VIVERSE

{% stepper %}
{% step %}

#### Publish content

A. To publish content to VIVERSE type the following command with the project path to the project's production build folder: **viverse-cli publish \<path>**, then click Enter.

B. Enter an **Application title** and **Application description**.

C. Confirm the content was published successfully.

<figure><img src="/files/HnsDhITbaAW8Ina3gdQk" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Re-publishing content

A. To re-publish content to VIVERSE when a project is already published, type the following command with the project path to the project's production build folder: **viverse-cli publish \<path>**, then click Enter.

B. Confirm the manifest file is updated.

C. Confirm the content was published successfully.

<figure><img src="/files/DIccZsSC8Rkj7v2BLtHe" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Test project

A. Confirm project was published successfully and working properly in VIVERSE by visiting the **URL** that is printed in the **Publish Details**.

<figure><img src="/files/lcubz5hjkOwOcbZ7DuVg" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Godot HTML5

This document provides a guide for exporting a Godot project for HTML 5 and publishing to VIVERSE.

***

### Introduction

In this getting-started guide, we will cover the basics of setting up a [Godot HTML5](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_web.html) project and publishing to VIVERSE using [the VIVERSE CLI](https://www.npmjs.com/package/@viverse/cli).

{% hint style="info" %}
For this guide, we are using the VIVERSE CLI, but it is also possible to compress and [upload your build file directly to the VIVERSE Studio](/publishing-with-your-viverse-account#upload).
{% endhint %}

### Project Settings for Godot

Web support for Godot currently requires the engine version to be Godot 4.1 or higher for successful exports. **Currently using Godot without C# is essential**. However, web export support for C# is expected soon from a recent [announcement](https://godotengine.org/article/live-from-godotcon-boston-web-dotnet-prototype/).

{% hint style="warning" %}
Godot 3 web exports are technically supported but not focused on future support.
{% endhint %}

<figure><img src="/files/2xfu5ClG1m8JpSpag101" alt="" width="366"><figcaption></figcaption></figure>

### Export Setting for Godot

Select the desired platform for export. For web exporting, choose `HTML5`. Also **make sure that the export path is set to Build/index.html**. This will lower your packaged build size and rename project exported .html to index.html so VIVERSE can easily find and run the project.&#x20;

<figure><img src="/files/etysAADgmzqWeL8oA0Q9" alt="" width="375"><figcaption></figcaption></figure>

### Publish to VIVERSE

From here you can publish the project using the CLI tool just like any other platform.&#x20;

<figure><img src="/files/sclLGlqzA1FnG3EE77X8" alt="" width="563"><figcaption></figcaption></figure>

{% stepper %}
{% step %}

#### Log in to VIVERSE platform

A. Open a command prompt and type: **viverse-cli auth login**, then click Enter.

B. Enter VIVERSE **email** and **password**.

C. Confirm login was successful.

<figure><img src="/files/oHUcdEPF7rPNra0ShVGC" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Publish Content

A. To publish content to VIVERSE type the following command with the project path to the project's production build folder: **viverse-cli publish \<path>**, then click Enter.

B. Enter an **Application title** and **Application description**.

C. Confirm the content was published successfully.

<figure><img src="/files/HnsDhITbaAW8Ina3gdQk" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Re-publishing content

A. To re-publish content to VIVERSE when a project is already published, type the following command with the project path to the project's production build folder: **viverse-cli publish \<path>**, then click Enter.

B. Confirm the manifest file is updated.

C. Confirm the content was published successfully.

<figure><img src="/files/DIccZsSC8Rkj7v2BLtHe" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Test project

A. Confirm project was published successfully and working properly in VIVERSE by visiting the **URL** that is printed in the **Publish Details**.
{% endstep %}
{% endstepper %}

### General Limitations of Godot and Web Export

List of Godot WebGL rendering issues: <https://github.com/godotengine/godot/issues/66458>


# Open Brush

This page details how to publish to VIVERSE from the VR illustration tool Open Brush.

***

### Introduction

In December 2025, VIVERSE and the Icosa Foundation, Open Brush's steward, teamed up with digital artist SUTU to create a publishing pipeline for Open Brush creators to host, distribute, and monetize their work on VIVERSE. When publishing to VIVERSE, creators are able to share their work on Mobile, Desktop, and VR through the web browser, complete with networked VRM avatars and real-time chat. The publishing process is a simple and powerful way for Open Brush creators to expand their audiences and make their work more accessible!

### Publishing Tutorial

{% stepper %}
{% step %}

#### Sign into VIVERSE from Open Brush

Within the Open Brush application in VR, navigate to the "Accounts" toolbar and select "VIVERSE" to open the browser window. Enter your VIVERSE account credentials and complete the sign in process, closing the browser window when prompted.

<figure><img src="/files/3pALP73tTIt6tTb90cg1" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Upload to VIVERSE

Navigate to the "Upload" tab and select VIVERSE. Wait for the uploading process to complete.

<figure><img src="/files/uE8PHP7tUfa3KjoP317O" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Test and Finish Publishing on VIVERSE Studio

After uploading, a web browser window will open with studio.viverse.com. From here, sign into VIVERSE if necessary, and finish accessing and testing your world.

<figure><img src="/files/QvAhOg9urPK27G0UlrKe" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Tips & Troubleshooting

<details>

<summary>What should I do after publishing?</summary>

After publishing, we encourage all creators to curate their [world settings](/publishing-with-your-viverse-account#world-settings) and [creator profile](/publishing-with-your-viverse-account#profile-settings). These help with discoverability on VIVERSE.&#x20;

We also encourage creators to take advantage of the easy sharing of your work on VIVERSE. You can quickly share your VIVERSE url with anyone and even embed it on your artistic website. If you post your world, tag @VIVERSEOfficial on all platforms and we will help share!

</details>

<details>

<summary>How can I monetize my Open Brush work?</summary>

We want to help Open Brush creators earn from their awesome work on VIVERSE! Consult our [Monetization Docs](broken://pages/SykyJuZBUJRWAKDzk6j6) and contact our team if you have any questions.

</details>

<details>

<summary>How can I get help or request a new feature?</summary>

In the [VIVERSE Discord Server](https://discord.gg/viversecreators), we have a dedicated #open-brush channel where we want to hear your feedback, bug reports, and feature requests!

</details>

<details>

<summary>Are there any content restrictions on VIVERSE?</summary>

All Open Brush content must abide by the [VIVERSE Terms and Conditions](https://www.viverse.com/terms-of-use). In general, these terms restrict the depiction of NSFW content.

</details>


# Wonderland Engine

Integration example of VIVERSE Avatar SDK with Wonderland Cloud networking for multiplayer VR experiences.

***

### Introduction

In November 2025, VIVERSE and the Wonderland Engine team collaborated to develop a plugin allowing developers to publish to VIVERSE from directly within the Wonderland Engine editor. In addition, we developed examples of using the VIVERSE Avatar and Account SDKs with Wonderland's multiplayer SDK.

This document is a copy of the Wonderland Engine document covering the plugin: <https://github.com/WonderlandEngine/viverse-example/tree/main>

### Overview

This repository is a template project that demonstrates how to integrate the **VIVERSE Avatar SDK** with **Wonderland Cloud** networking. It provides a compact, production-minded workflow to:

* Spawn and persist user data (name, avatar) via the VIVERSE Avatar SDK
* Relay avatar + presence state to Wonderland Cloud for performant, medium-scale multiplayer sessions
* Use spatial audio for immersive multiplayer experiences

> Designed for quick iteration and easy publishing through the VIVERSE / Wonderland tooling.

**Live Demo:** <https://worlds.viverse.com/k99xkYZ>

### Features

The example project includes the following key features:

* **VIVERSE Avatar SDK Integration:** Login, avatar spawn, and user metadata management
* **Wonderland Cloud Networking:** Client + server template for multiplayer functionality
* **VIVERSE CLI Plugin:** Simple integration for publishing and previewing projects
* **Example Components:** Pre-configured components showing where to place `appid` and server path
* **Spatial Audio:** Avatar position replication with spatial audio for medium-scale sessions

### Project Structure

The repository contains the following directories and files:

* `js/` - JavaScript/TypeScript source files
* `models/` - 3D model assets
* `plugins/viverse-publish-plugin/` - VIVERSE CLI plugin for publishing
* `server/` - Wonderland Cloud server package
* `shaders/` - Custom shader files
* `static/` - Static assets
* `app.js` - Main application entry point
* `package.json` - Node.js dependencies and scripts
* `tsconfig.json` - TypeScript configuration
* `viversecli.wlp` - VIVERSE CLI configuration file

### Quick Setup (Editor)

{% stepper %}
{% step %}

#### Open the plugin

Open the `viversePublishPlugin` inside the Wonderland Editor.
{% endstep %}

{% step %}

#### Log In with VIVERSE Credentials

{% endstep %}

{% step %}

#### Create Application

Click **Create Application** — this redirects to VIVERSE Studio.
{% endstep %}

{% step %}

#### Copy the App ID

In VIVERSE Studio create an app and copy the App ID.
{% endstep %}

{% step %}

#### Paste the App ID

Paste the App ID into the `appid` field of the plugin.
{% endstep %}
{% endstepper %}

### Scene Configuration

{% stepper %}
{% step %}

#### Navigate to Avatar Component

In the Wonderland Editor hierarchy, navigate to: `Avatar -> VrmDynamic` and select the `VrmDynamic` object.
{% endstep %}

{% step %}

#### Expand VIVERSE Provider Component

In the Inspector, expand the **Viverse Provider** component (or `viverse-provider-component`).
{% endstep %}

{% step %}

#### Paste the App ID

Locate the `appid` field in the component properties and paste the **App ID** that you copied from VIVERSE Studio.
{% endstep %}

{% step %}

#### Verify Configuration

Double-check that the App ID is correctly set in both the VIVERSE Publish Plugin and the `VrmDynamic -> Viverse Provider` component, then save the scene to preserve your changes.
{% endstep %}
{% endstepper %}

### Networking Setup (Wonderland Cloud)

{% stepper %}
{% step %}

#### Access Wonderland Cloud

Open your web browser and navigate to: `https://cloud.wonderland.dev/create-server`. Log in with your Wonderland Cloud credentials if required.
{% endstep %}

{% step %}

#### Locate Server Package

Locate the server package in your project: `server/wonderland-cloud-example-simple-1.0.0.tgz` and verify the file exists and is not corrupted.
{% endstep %}

{% step %}

#### Upload Server Package

In the Wonderland Cloud interface, click **Upload** or **Create Server**, select the file: `server/wonderland-cloud-example-simple-1.0.0.tgz`, and wait for the upload to complete. Wonderland Cloud will process and deploy your server.
{% endstep %}

{% step %}

#### Copy Server Path

After the server is created, Wonderland Cloud will display server information. **Copy the server path** that is provided (typically looks like: `wss://your-server-id.wonderland.dev`).
{% endstep %}

{% step %}

#### Configure Client Component

Return to the Wonderland Editor, select the `Player` object in the hierarchy, locate the `simple-example-client` component in the Inspector, find the `serverPath` field, paste the **server path** you copied from Wonderland Cloud, and save the scene.
{% endstep %}
{% endstepper %}

### Publish & Preview

{% stepper %}
{% step %}

#### Open VIVERSE CLI Plugin

In the Wonderland Editor, open the VIVERSE CLI plugin.
{% endstep %}

{% step %}

#### Publish Project

Click **Publish** to build and upload your project. Wait for the publishing process to complete. The plugin will show the status of the upload.
{% endstep %}

{% step %}

#### Preview Your Project

After publishing, click **Preview URL** in the plugin. This will open a preview build in your browser. Test all functionality including avatar spawning, multiplayer connectivity, spatial audio, and user interactions.
{% endstep %}

{% step %}

#### Submit for Review

In the VIVERSE CLI plugin, click **Submit for Review**. This will open the VIVERSE Creator page for your application. Complete any required information in the VIVERSE Creator interface and review your application settings before submission.
{% endstep %}

{% step %}

#### Guest Preview Testing

From the VIVERSE Creator page, use the **Guest Preview** link. Share this link with others for testing. Test multiplayer functionality with multiple devices/accounts, verify that avatars appear correctly for all users, and test spatial audio and networking in production-like conditions.
{% endstep %}

{% step %}

#### Final Submission

Once testing is complete and you're satisfied with the build, submit the app for review to list it publicly. Wait for VIVERSE approval. After approval, your application will be available in VIVERSE.
{% endstep %}
{% endstepper %}

### Component Configuration Details

#### VIVERSE Provider Component

The `Viverse Provider` component (or `viverse-provider-component`) handles authentication and avatar management:

* **App ID:** Your VIVERSE application identifier (required)
* **Avatar Spawning:** Automatically spawns user avatars based on VIVERSE account
* **User Metadata:** Retrieves and stores user name and avatar data

#### Simple Example Client Component

The `simple-example-client` component manages networking:

* **Server Path:** WebSocket URL to your Wonderland Cloud server (required)
* **Connection Management:** Handles connection/disconnection events
* **Avatar Replication:** Syncs avatar positions and states across clients
* **Presence State:** Manages user presence in the multiplayer session

### Troubleshooting

#### Avatars Not Appearing

**Problem:** Avatars don't spawn or appear in the scene.

**Solutions:**

* Verify the **App ID** is set correctly in both:
  * The VIVERSE Publish Plugin
  * The `VrmDynamic -> Viverse Provider` component
* Check that the App ID matches the one from VIVERSE Studio
* Ensure you're logged in with a valid VIVERSE account
* Check browser console (F12) for authentication errors
* Verify CORS settings in VIVERSE Studio if using custom domains

#### Networking Failures

**Problem:** Multiplayer connectivity issues or connection errors.

**Solutions:**

* Re-check that the uploaded server package matches the server entry on `cloud.wonderland.dev`
* Confirm the server path is pasted correctly in the `simple-example-client` component
* Verify the server path format (should start with `wss://`)
* Check that the server is running and accessible
* Review browser console (F12) for WebSocket connection errors
* Ensure firewall/network settings allow WebSocket connections

#### Preview URL Issues

**Problem:** Preview URL doesn't work or shows errors.

**Solutions:**

* Use the plugin **Preview URL** to test cross-device connectivity before submitting
* Check that the build was published successfully
* Verify all required assets are included in the build
* Clear browser cache and try again
* Test in different browsers (Chrome, Firefox, Edge)

#### Browser Console Errors

**Problem:** Errors appear in browser console (F12).

**Solutions:**

* Check browser console logs for helpful client-side errors
* Common issues include:
  * **Auth errors:** Verify App ID and VIVERSE credentials
  * **CORS errors:** Check VIVERSE Studio CORS settings
  * **Network errors:** Verify server path and connectivity
* Look for specific error messages and error codes
* Check network tab for failed requests

### Server Upload Issues

**Problem:** Server package upload fails or server doesn't deploy.

**Solutions:**

* Verify the server package file exists: `server/wonderland-cloud-example-simple-1.0.0.tgz`
* Check file size limits on Wonderland Cloud
* Ensure the package is not corrupted
* Try re-uploading the server package
* Check Wonderland Cloud dashboard for server status

### Build and Publishing Issues

**Problem:** Build fails or publishing doesn't complete.

**Solutions:**

* Ensure all required dependencies are installed
* Check that all scene files are saved
* Verify project structure matches expected format
* Review build logs for specific errors
* Try rebuilding the project from scratch
* Check Wonderland Editor console for errors

### Additional Resources

#### Documentation Links

* **Wonderland Engine Documentation:** <https://wonderlandengine.com/>
* **VIVERSE Documentation:** <https://docs.viverse.com/>
* **Wonderland Cloud:** <https://cloud.wonderland.dev/>

#### Support

If you need help setting up this template for your project:

* Reach out via Discord (Wonderland Engine community)
* Check the GitHub repository for issues and discussions
* Review the example code in the repository for implementation details

#### Contributing

Pull requests are welcome for improvements to this template: <https://github.com/WonderlandEngine/viverse-example/tree/main>

* Keep changes small and well-documented
* Open an issue for feature requests or bugs
* Follow the existing code style and structure

### Technical Details

#### Project Dependencies

The project uses the following key technologies:

* **Wonderland Engine:** Web-based 3D engine for VR/AR experiences
* **VIVERSE Avatar SDK:** Avatar system and user authentication
* **Wonderland Cloud:** Multiplayer networking infrastructure
* **TypeScript/JavaScript:** Application logic and components

#### File Structure Details

* `js/` - Contains component scripts and application logic
* `models/` - 3D models and avatar assets
* `server/` - Wonderland Cloud server implementation
* `plugins/viverse-publish-plugin/` - Editor integration for publishing
* `static/` - Static web assets (images, fonts, etc.)
* `shaders/` - Custom shader code for rendering

#### Build Process

1. **Development:** Edit scenes and components in Wonderland Editor
2. **Configuration:** Set App ID and server path in components
3. **Publishing:** Use VIVERSE CLI plugin to build and upload
4. **Deployment:** Preview and submit through VIVERSE Creator

#### Multiplayer Architecture

The example uses a client-server architecture:

* **Client:** Wonderland Engine application running in browser
* **Server:** Wonderland Cloud server handling networking
* **Communication:** WebSocket connection for real-time updates
* **State Sync:** Avatar positions and presence state synchronized across clients


# Toolkit Setup

Learn how to install the VIVERSE Toolkit for the PlayCanvas Editor, create your first project, then publish it to VIVERSE.

***

{% hint style="warning" %}
This documentation covers building with v4 and newer of the VIVERSE PlayCanvas Toolkit. [See here](/playcanvas-toolkit/toolkit-setup-legacy-v3) for legacy documentation on v3 setup.
{% endhint %}

## Introduction

[PlayCanvas](https://playcanvas.com/) is an open-source game engine designed to advance the development of 3D web games, interactive content and rich multimedia. Unlike similar WebGL engines like Three.js or Babylon, PlayCanvas comes with sophisticated in-browser Editor (inspired by early [Unity 2.x - 3.x](https://www.elmundotech.com/2010/09/27/unity-3-game-dev-platform-available-now/)), which provides its users with a rich set of tools — allowing to assemble scenes from imported assets, setup real-time and baked lighting, write custom scripts, create animation graphs, and much much more.

To make creator's experience even richer, VIVERSE has developed a special Toolkit, which consists of two parts complementing each other — the Extension and the Framework:

* **The Extension** lives in your Chrome browser, and its main goal is to add extra functionality to vanilla PlayCanvas Editor, not achievable otherwise — like initializing your project, creating new VIVERSE projects, setting up Local Player, Quests and Post Effects, and of course providing convenient ways to Publish to VIVERSE!
* **The Framework** is a collection of scripts and assets that the Extension adds to your project during initialization, under the `.viverse` folder. It provides important runtime systems for Avatars, Player Locomotion, Networking and so forth, and common building blocks like Triggers and Actions, that you can use to create custom no-code logic in your Worlds

{% hint style="info" %}
Please see [the full collection of documents](/playcanvas-toolkit/building-with-playcanvas-toolkit) to help you get started with the Toolkit. We assume you're already familiar with PlayCanvas Editor itself and have PlayCanvas account. If not, please feel free to explore [PlayCanvas User Manual](https://developer.playcanvas.com/user-manual/) first!
{% endhint %}

## Install PlayCanvas Extension

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

### Get the latest Extension from VIVERSE

* [Download](/playcanvas-toolkit/playcanvas-toolkit-changelog) the latest version of Playcanvas Extension
* Unzip downloaded file on your computer
  {% endstep %}

{% step %}

### Navigate to Extensions manager

* Click **Extensions** icon in Chrome toolbar and open the Extensions Popup
* Click **Manage Extensions** at the bottom and open the Extensions Manager in a new tab
  {% endstep %}

{% step %}

### Install the Extension

* Enable **Developer Mode** at the top right corner
* Click **Load Unpacked** and select the folder with unpacked Extension you downloaded previously
* Verify the Extension is now present in Extensions Manager tab
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/PgQlu1KlSLvRD950UbSk" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/JrL87eIzIzuXGjFAxvhQ" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/psYt9QW1kDGHWpBqRnzo" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/267M4Gy01EEneZQwG6U8" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/278YnLKwIZX7OxCYPUV1" alt=""><figcaption></figcaption></figure>

{% endcolumn %}
{% endcolumns %}

## Create, Test, and Publish your first project

{% hint style="info" %}
Before proceeding any further, we assume you're familiar with the basics of PlayCanvas Editor. If you're looking for a comprehensive introduction to it — please refer to a dedicated [PlayCanvas Editor Manual](https://developer.playcanvas.com/user-manual/editor/) first!
{% endhint %}

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

### Enter a PlayCanvas Project

* Enter the editor of an existing PlayCanvas project or create a new one

{% endstep %}

{% step %}

### Enable the extension

* Click **Extensions** icon in Chrome toolbar and open the Extensions Popup
* Select the **VIVERSE PlayCanvas Toolkit** button

{% endstep %}

{% step %}

### Sign In to VIVERSE

* Select the **Log In to VIVERSE** button that is now in the left-hand toolbar
* **Complete the VIVERSE SSO process** to sign in to your account
  {% endstep %}

{% step %}

### Create VIVERSE Application

* Select the **VIVERSE PlayCanvas Toolkit Settings** button in your left-hand toolbar
* Select **Create New World** and wait for the toolkit to upload all scripts to your project.

{% endstep %}

{% step %}

### Test Scene Locally

* After creating an application, you will notice that a number of files and entities have been added to your project, including a template for VIVERSE avatars.
* Make sure to **add a collidable object** for the VIVERSE avatar to interact with. This process will **import** **ammo.js**, which is the required default for the VIVERSE toolkit.
* Select **Launch** in the PlayCanvas editor. This will start a local session where you can test all features, including multiplayer audio.

{% endstep %}

{% step %}

### Export and Upload

* When you are ready to upload, you must first create a new build in PlayCanvas. This can be completed in **Publish and Download** in the left-hand toolbar.
* Once you have generated a build, **download its .zip file**.
* **Navigate to studio.viverse.com** and [complete the upload process](/how-to-publish#publishing-apps-with-viverse-studio). PLEASE NOTE, when you created your VIVERSE application, a project was automatically generated in studio.viverse.com under your account. You should use this project when uploading your files.
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/UJzWzeDbtsGYgJJHTTvI" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/koDPdTmWcGLOYSvMBmFa" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/jO5BVXO1EVcuwHh9jety" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/KaRoES5QoTU4NF8ZbABk" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/D4R2d1o9yBSe93pnaRfq" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/4X4tbCv3M0a4nJJEWmvG" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/17q5tZ3ESbo3AP3HEufA" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/vbzWZYRTJFhNoPPZbNro" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/eEy4ggPVie5BESo9hu2K" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/We6W3TL8zfJoDaa52Mgu" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/9UXfP7QdtS7XkrGbNRoP" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

## "Factory Reset" PlayCanvas Extension

> Sometimes issues in the installation process can produce persistent bugs that are only fixed by fully resetting the Extension. If you're experiencing these bugs please follow the steps below

{% stepper %}
{% step %}

#### Sign in and out of VIVERSE

Sign out of your account on viverse.com and then sign back in. **MAKE SURE** you are using the same email account as the one associated with your PlayCanvas account
{% endstep %}

{% step %}

#### Delete VIVERSE entities from PlayCanvas project

Go back to your PlayCanvas project and delete:

* The `Extension` Entity from your scene hierarchy
* The `.viverse` folder from your project assets
* The `.index.mjs` folder from your scripts folder
  {% endstep %}

{% step %}

#### Clear Application Storage

Open the [Developer Tools](https://elfsight.com/blog/how-to-work-with-developer-console/) in your Chrome broswer, go to "Application", select "Storage" in the left hand toolbar and click the "Delete Site Data" button
{% endstep %}

{% step %}

#### Refresh the page

Refresh your PlayCanvas project page with the VIVERSE extension enabled
{% endstep %}

{% step %}

#### Sign in back to PlayCanvas

Sign in to PlayCanvas when prompted. **MAKE SURE** you are using the same email account as the one associated with your VIVERSE account
{% endstep %}

{% step %}

#### Sign in back to VIVERSE

Sign in to VIVERSE from within your PlayCanvas project using the VIVERSE Scene Settings
{% endstep %}
{% endstepper %}


# Building with PlayCanvas Toolkit

Learn how to build projects using common no-code functionality, as well as custom scripted entities and advanced APIs.

***

{% hint style="warning" %}
This documentation covers building with v4 and newer of the VIVERSE PlayCanvas Toolkit. [See here](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3) for legacy documentation on v3.
{% endhint %}

{% columns %}
{% column width="50%" %}
{% content-ref url="/pages/B2VyePXnaQR8mFrwPgO1" %}
[Triggers](/playcanvas-toolkit/building-with-playcanvas-toolkit/triggers)
{% endcontent-ref %}

{% content-ref url="/pages/MZ1ek3u9vabGtWkCgf8x" %}
[Conditions](/playcanvas-toolkit/building-with-playcanvas-toolkit/conditions)
{% endcontent-ref %}

{% content-ref url="/pages/J7rA9QJw2MyIe2FHNGZj" %}
[Sitting](/playcanvas-toolkit/building-with-playcanvas-toolkit/sitting)
{% endcontent-ref %}

{% content-ref url="/pages/DhrKQU5V2aBDkjTrSL0P" %}
[Post Effects](/playcanvas-toolkit/building-with-playcanvas-toolkit/post-effects)
{% endcontent-ref %}

{% content-ref url="/pages/ic8aIV6No927a4ysVgei" %}
[Custom Scripts](/playcanvas-toolkit/building-with-playcanvas-toolkit/custom-scripts)
{% endcontent-ref %}
{% endcolumn %}

{% column width="50%" %}
{% content-ref url="/pages/ERXZfd908a0GNhX5mYjO" %}
[Actions](/playcanvas-toolkit/building-with-playcanvas-toolkit/actions)
{% endcontent-ref %}

{% content-ref url="/pages/wTEO0kMWZGIMI5eAxtY4" %}
[Picking](/playcanvas-toolkit/building-with-playcanvas-toolkit/picking)
{% endcontent-ref %}

{% content-ref url="/pages/mpIsw2OBlNvKPcZb4I72" %}
[Quests](/playcanvas-toolkit/building-with-playcanvas-toolkit/quests)
{% endcontent-ref %}

{% content-ref url="/pages/Bo00ZMltMkoyr6jCwOlw" %}
[Video Playback](/playcanvas-toolkit/building-with-playcanvas-toolkit/video-playback)
{% endcontent-ref %}

{% endcolumn %}
{% endcolumns %}


# Sample Project

***


# Triggers

Learn how use Triggers in your VIVERSE Worlds, and how to link them to Actions and / or Custom Scripts

***

## About

Triggers is a fundamental part of VIVERSE Framework, allowing creators to handle a wide range of player- and game-specific events without writing a single line of code! Whenever Player interacts with some Entity — be it a click, or collision, or seat taking — a corresponding event is generated, and any Trigger, associated with this event, also gets activated.

## Usage

Triggers don't do anything on their own — they're typically paired with [Actions](/playcanvas-toolkit/building-with-playcanvas-toolkit/actions) or [Custom Scripts](/playcanvas-toolkit/building-with-playcanvas-toolkit/custom-scripts) to produce desired effects of their activations.

Here is a simple example of using Trigger with an Action:

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

### Setup Trigger

* Create a new empty Entity in your Scene
* Add **3D > Render Component** of type `Box`. Your Entity should be visible in your Scene now
* Add **Physics > Collision Component** of type `Box` as well. Adjust its params if necessary
* Click **Add Viverse Component** button and select **Rule > Trigger**. The `viverseTrigger` script will be added to your Entity
* In Trigger Component, add a new entry to the Trigger List, by entering 1
* Leave Trigger type at `OnSelect` (default), and give it a unique `Name`, for example `box.clicked`
  {% endstep %}

{% step %}

### Setup Action

* With your Entity selected, click **Add Viverse Component** button once again, and select **Rule > Action**. The `viverseAction` script will be added to your Entity
* In Action Component, add a new entry to the Action List, by entering 1
* Populate `Trigger / Condition` field with your Trigger Name, which is `box.clicked` in our case
* Leave all other params at their defaults - `ToggleEntities`, `Toggle`, `Self`&#x20;
  {% endstep %}

{% step %}

### Test and verify

* Now we can test our Trigger / Action pair!
* Launch your Scene in a new tab, walk towards white box Entity and click it
* If you set up everything correctly — the Entity should disappear on your click
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/Y7sbAq2xmMXvq1YsVkPH" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/e8na4jAreOP0yBonIBDs" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Od2EZpkqOiykWqQ3JTgL" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/S57kjvmykETaPFWrMdww" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/fHQQQpZuuKgNl94J8h3w" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/HQjPHg6q66FwmRhiSrv6" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

{% hint style="info" %}
It's not really necessary to have both **Trigger** and **Action** Components on the same Entity! For the purpose of your experience you can have as many Entities as you like, each with its own Trigger or Action, or a combination of those.

The only important rule is that Trigger's `Name` param should match an Action's `Trigger / Condition` one if you want them to be linked together
{% endhint %}

## Reference

***

{% columns %}
{% column width="25%" %}
`OnSelect`<br>
{% endcolumn %}

{% column width="50%" %}
Activates when the Player points and clicks on this Trigger. Requires **Collision Component** to be attached to the Entity
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/EcrulVIO8UqP4TVj8dLc" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`OnCollision`<br>
{% endcolumn %}

{% column width="50%" %}
Activates when another **Rigidbody** interacts with this Trigger. Supported interactions:

* `TriggerEnter` / `TriggerLeave` \
  Requires **Collision Component** to be attached
* `CollisionStart` / `CollisionEnd` \
  Requires both **Collision** and **Rigidbody Components**

You can allow interactions with only the Player and / or some Entity with specific Tag. See **Collision Filter** for available options
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/kqLuUe3KIOpTXXn54HXd" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/KhvA8EbiRaNvhCGFWM83" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`OnAnimation`<br>
{% endcolumn %}

{% column width="50%" %}
Activates when animation state changes for this Trigger entity. Requires **Animation Component** to be attached. Supported animation events:

* `Start` / `End` / `CustomEvent`
  {% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/zTgxPlBAQvv0Oz2FR9tg" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`On`\
`SeatState`\
`Changed`<br>
{% endcolumn %}

{% column width="50%" %}
Activates when this Seat Entity changes its state. Works in tandem with **Seat Component**, therefore requires **viverseSeat** script to be attached to this Entity. Supported events:

* `Occupied` / `Vacated`
  {% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/4K9JOQkSZzkQSbD41uFb" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`On`\
`Notification`\
`Event`<br>
{% endcolumn %}

{% column width="50%" %}
Activates when another Entity with **Action Component** fires `PublishNotification` event with particular `Event Name` matching the one in this Trigger. You can use it to send / receive custom Notification Events between different Entities in your Scene
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/GQxv4FSS3ABxmEGixkfm" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`On`\
`Action`\
`Executing`<br>
{% endcolumn %}

{% column width="50%" %}
Activates when another Entity with **Action Component** starts executing an Action with a given `Action Name`. You can use it to sequentially chain multiple Triggers and Actions together, for example `Trigger A` → `Action A` → `Trigger B` → `Action B`
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/zTgxPlBAQvv0Oz2FR9tg" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***


# Actions

Learn how use Actions in your VIVERSE Worlds, and how to link them to Triggers and / or Custom Scripts

***

## About

Actions is another fundamental part of VIVERSE Framework, allowing creators to devise a wide range of possible "verbs" that their experience can do, without writing a single line of code. The typical examples are:

* Enable / Disable / Toggle Entity or a group of Entities
* Teleport Player to a new location
* Fade In or Fade Out some Entity
* Play Animation / Particle Effect or Sound
* Initialize Quest or advance particular Quest Task
* And so on, and so forth

## Usage

Actions are typically paired with [Triggers](/playcanvas-toolkit/building-with-playcanvas-toolkit/triggers), where one Trigger can execute multiple Actions in parallel, or one Action can be executed by any Trigger matching its name. It's also possible to execute Actions from your [Custom Scripts](/playcanvas-toolkit/building-with-playcanvas-toolkit/custom-scripts), opens up even more possibilities for interactivity!

Here is a simple example of an Action being executed by a Trigger:

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

### Setup Trigger

* Create a new empty Entity in your Scene
* Add **3D > Render Component** of type `Box`. Your Entity should be visible in your Scene now
* Add **Physics > Collision Component** of type `Box` as well. Adjust its params if necessary
* Click **Add Viverse Component** button and select **Rule > Trigger**. The `viverseTrigger` script will be added to your Entity
* In Trigger Component, add a new entry to the Trigger List, by entering 1
* Leave Trigger type at `OnSelect` (default), and give it a unique `Name`, for example `box.clicked`
  {% endstep %}

{% step %}

### Setup Action

* With your Entity selected, click **Add Viverse Component** button once again, and select **Rule > Action**. The `viverseAction` script will be added to your Entity
* In Action Component, add a new entry to the Action List, by entering 1
* Populate `Trigger / Condition` field with your Trigger Name, which is `box.clicked` in our case
* Leave all other params at their defaults - `ToggleEntities`, `Toggle`, `Self`&#x20;
  {% endstep %}

{% step %}

### Test and verify

* Now we can test our Trigger / Action pair!
* Launch your Scene in a new tab, walk towards white box Entity and click it
* If you set up everything correctly — the Entity should disappear on your click
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/Y7sbAq2xmMXvq1YsVkPH" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/e8na4jAreOP0yBonIBDs" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Od2EZpkqOiykWqQ3JTgL" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/S57kjvmykETaPFWrMdww" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/fHQQQpZuuKgNl94J8h3w" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/HQjPHg6q66FwmRhiSrv6" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

{% hint style="info" %}
It's not really necessary to have both **Trigger** and **Action** Components on the same Entity! For the purpose of your experience you can have as many Entities as you like, each with its own Trigger or Action, or a combination of those.

The only important rule is that Trigger's `Name` param should match an Action's `Trigger / Condition` one if you want them to be linked together
{% endhint %}

## Reference

***

{% columns %}
{% column width="25%" %}
`Toggle`\
`Entities`
{% endcolumn %}

{% column width="50%" %}
Enables or disables a given Entity according to desired `State` parameter. Supported states:

* `Enable` / `Disable` / `Toggle`&#x20;

What Entity is enabled or disabled can be controlled by **Entity Filter** selector:

* `Self` - current Entity (default)
* `Tag` - all Entities with specific Tag
* `TargetEntity` - particular Entity in the Scene
  {% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/1FiC4NuY4nFejFQtSinf" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/5Nsuu4q7jXF3OOjiCuFj" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Toggle`\
`Component`
{% endcolumn %}

{% column width="50%" %}
Enables or disables particular Component on a given Entity according to desired `State` parameter. Supported states:

* `Enable` / `Disable` / `Toggle`&#x20;

Supported Components to destroy:

* `Collision` / `Rigidbody`

As with `ToggleEntities` type, **Entity Filter** selector controls what Entity is affected by this Action
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/ObwTwaeBNEw8TCXoGzlg" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Destroy`\
`Entities`
{% endcolumn %}

{% column width="50%" %}
Destroys a given Entity based on **Entity Filter** selector. See `ToggleEntities` above for available filter options
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/x8FO636WKWaJ2VwN4wa8" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Fade`\
`Transition`
{% endcolumn %}

{% column width="50%" %}
Fades In or Out a given Entity over `Duration` period. Supported Fade Types:

* `FadeIn` / `FadeOut`&#x20;

The Entity should have **Mesh** or **Render Component** attached, with transparent Materials.

As with `ToggleEntities` type, **Entity Filter** selector controls what Entity is affected by this Action
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/vmzG4bdOjdOiSjz6e2vk" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/lVvILXWQwaMgoX0TmWqw" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Push`\
`Notification`
{% endcolumn %}

{% column width="50%" %}
Fires a Notification Event with a given `Event Name`. This event can be picked up by another Trigger Entity of type `OnNotificationEvent`. You can use it to send / receive custom Notification Events between different Entities in your Scene
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/DlKgEMX9rOpoEXSCylZJ" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Teleport`\
`Player`
{% endcolumn %}

{% column width="50%" %}
Teleports the Player to a given 3D point in your Scene
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/4vDsiJNxYEdbYRnuthnS" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Push`\
`Entity`
{% endcolumn %}

{% column width="50%" %}
Applies a 3D impulse to a given Entity, provided this Entity has **Rigidbody Component** attached. As with `ToggleEntities` type, **Entity Filter** selector controls what Entity is affected by this Action
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/5iky30TtFq5eTdR5LqyN" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Sound`\
`Control`
{% endcolumn %}

{% column width="50%" %}
Plays or stops a Sound on a given Entity, provided this Entity has **Sound Component** attached. `Audio Name` refers to a **Sound Slot** name. Supported actions:

* `Play` / `Stop`

As with `ToggleEntities` type, **Entity Filter** selector controls what Entity is affected by this Action
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/40fWuJPhKVdCVuhsmeKj" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Animation`\
`Control`
{% endcolumn %}

{% column width="50%" %}
Plays an Animation on a given Entity with `Duration` blend time, provided this Entity has **Animation Component** attached. `Animation Name` refers to an **Animation Clip**. As with `ToggleEntities` type, **Entity Filter** selector controls what Entity is affected by this Action
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/53purvlIS8SgTuQqufPg" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Particle`\
`System` \
`Control`
{% endcolumn %}

{% column width="50%" %}
Plays a Particle Effect on a given Entity, provided this Entity has **Particle System Component** attached. As with `ToggleEntities` type, **Entity Filter** selector controls what Entity is affected by this Action
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/wZLaWKa0fHKktc2AdBkl" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Quest`
{% endcolumn %}

{% column width="50%" %}
Updates state of Quest with a given `Quest Name`, based on `Command` provided. Supported commands:

* `Start Quest` / `Reset Quest`
* `AddTaskProgress` / `CompleteTask`&#x20;
  {% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/uJwnckakvN6POZhfuNuo" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Set` \
`Spawn`\
`Point`
{% endcolumn %}

{% column width="50%" %}
Sets the next Spawn Point for the Player to a 3D point from provided list. If the list contains multiple options - the point will be selected randomly out of them
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/ZVInD9gcZHCjKiftC5G7" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Seat`&#x20;
{% endcolumn %}

{% column width="50%" %}
Executes Player sitting mechanic for a given Seat Entity, according to provided `Command`. The Entity should have **Seat Component** attached. Supported commands:

* `Sit` / `Leave`
  {% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/AGpke3QkkLrHUnvlLse6" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Open` \
`Link`
{% endcolumn %}

{% column width="50%" %}
Opens URL with a given `Link`
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/ANDxUXVwUeUzBGfgqDjd" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***


# Conditions

Learn how use Conditions in your VIVERSE Worlds, and how to link them to Triggers

***

## About

Conditions is additional convenience layer on top of Triggers, allowing creators to string multiple Triggers together, either in parallel or in sequential mode. Conditions can be used with Triggers interchangeably, for example when linking them to Actions.

## Usage

Conditions support 2 types of configuration:

* **Sequential (AND)** — creates an activation only when **both** input Triggers were activated within a given **Time Window**
* **Parallel (OR)** — creates an activation when at **any** input Trigger is activated. The **Time Window** here helps to prevent or control **double activation** when both inputs are triggered one after another

For practical usage examples, please refer to [Triggers](/playcanvas-toolkit/building-with-playcanvas-toolkit/triggers#usage) or [Actions](/playcanvas-toolkit/building-with-playcanvas-toolkit/actions#usage) respectively!

## Reference

***

{% columns %}
{% column width="25%" %}
`And`
{% endcolumn %}

{% column width="50%" %}
Activates when both **Trigger A** and **Trigger B** are activated within a given `Time Window`. Leaving time window at 0 (default) means no time restriction is applied
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/75BfwN0Zhuq3tEWqSg67" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Or`
{% endcolumn %}

{% column width="50%" %}
Activates when either **Trigger A** or **Trigger B** is activated within a given `Time Window`. Leaving time window at 0 (default) means no time restriction is applied
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/CGTKb4Y4ynxTLHjZQLZU" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***


# Picking

Learn how to add pickable objects to your World, and how to configure them

***

## About

PlayCanvas Toolkit provides no-code solutions for various modes of interactivity, and one of those modes is Entity Picking. It allows the Player to grab an object by particular local point, and use it as a melee weapon, with corresponding attack animations. In addition to that, when the Entity is possessed by the Player, a special UI Overlay is displayed, showing basic controls for melee attack or droping the Entity.

## Usage

All picking functionality is implemented by a single `viversePick` component. Here is how you can add a Pickable Object to your Scene:

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

### Create Pickable Entity

* Create a new Entity in your Scene, or use an already existing one with 3D mesh attached to it
* Click **Add Viverse Component** button and select **Interaction > Pick**
* Add a **Grip Point** as a child of your Pickable Entity. This point will be used to attach your Entity to Avatar's hand bone
  {% endstep %}

{% step %}

### Launch and Test

* Adjust Entity position and rotation in your Scene, and hit Launch
* When approaching Pickable Entity you should see **Hint Icon**. Click it to grab the Entity!
* Once it's picked, you should see UI Overlay at the bottom right corner, featuring 3 buttons — Attack `F`, Attack `G` and Drop `R`
* The way that Entity is located in Player's hand depends on internal orientation of **Grip Point** inside your Pickable Entity. Adjust its position and rotation to make your Entity look better when it's carried in Player's hand
* Don't forget to relaunch your Scene for any changes to take an effect!
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/zJBbW51RhZ2gIWHDhzcV" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Nyy8fuNTU6TxTtaVNKQ6" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/1GnDxSRr7ZWFojDWIqus" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/mrcfcY82OvwZzVNBCfI0" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/pivQ9t6bxIYQWpn8fTYt" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}


# Sitting

Learn how to implement Sitting functionality in your project

***

## About

PlayCanvas Toolkit comes with no-code solutions for all sorts of interactive experiences, and one of those types is virtual conference rooms. The promising idea of VIVERSE is that interactive 3D Worlds can be used not only for entertainment, but as lush 3D environments for virtual meetings as well!

The Seat is a concept designed to facilitate that. It's a special trigger that shows a dedicated Seat Icon, which upon click results in Player taking a seat at particular spot of the world.

## Usage

All Sitting functionality is implemented by a single `viverseSit` component. Here is how you can integrate it into your project:

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

### Create Seat Entity

* Create a new Entity in your Scene
* Click **Add Viverse Component** button and select **Interaction > Sit**
* If you did everything correctly - you should see a **viverseSit** script attached to your Entity, and a green trigger indicating Seat area
  {% endstep %}

{% step %}

### Launch and Test

* Adjust Seat position and rotation in your Scene, and hit Launch
* When approaching Seat Entity you should see **Hint Icon.** If it's not the case — please check whether `Show Hint Icon` is enabled
* Adjust `Inner` and `Outer Range` to control at what distance the **Hint Icon** starts fading in or out. Don't forget to relaunch your Scene for any changes to take an effect!
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/zJBbW51RhZ2gIWHDhzcV" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/uyFrmaW4ESpk8omRQCIo" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Mhp2sVDeh3pqwWm6D8Ls" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/PHLG0CTciDfzEIRqW8E0" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

{% hint style="info" %}
While **Hint Icon** is a convenient way to interact with Seat Entities, it's not the only way! You can disable Hint Icon and use your custom Trigger / Action pair instead, for example `Trigger (OnSelect)` → `Action (Seat)`. For more details, please refer to [Actions](/playcanvas-toolkit/building-with-playcanvas-toolkit/actions) usage.
{% endhint %}


# Quests

Learn how to add Quests to your VIVERSE Worlds, and how to configure Quest Tasks

***

## About

PlayCanvas Toolkit comes with an integrated Quest System, unlocking a new dimension of interactive storytelling for creators. Each Quest can feature multiple Tasks, which can be completed by the Player in any order. Once all Tasks for a given Quest are completed, the Quest is marked as done, which can advance the experience further by triggering some Action — for example opening a locked door in your World, or granting the Player a new weapon.

## Usage

The Quest Configurator can be found under <img src="/files/LIbvRsU51N1QmihzQ3AD" alt="" data-size="line"> VIVERSE Menu, which is located in the left sidebar. You can have multiple Quests, each featuring multiple dedicated Tasks, as long as you devise unique names for all of them.

In order to control Quest flow in your experience, you should use [Actions](/playcanvas-toolkit/building-with-playcanvas-toolkit/actions#usage) of type `Quest`, which provide a useful set of commands:

{% columns %}
{% column width="75%" %}

* **Start Quest** — start a particular Quest with a given `Quest Name`. Starting the Quest will display Quest UI Overlay in your experience, showing current Quest Title, Description, and a list of Tasks that Player should complete in order to resolve this Quest. If you're already running another Quest while starting a new one, this Action will be ignored
* **Complete Task** — mark Simple Task with a given `Task Name` as completed. This leads to crossing off that Task in Quest UI todo list. Once Task is completed, it cannot be completed again until the entire Quest is reset
* **Add Task Progress** — advance Progress Task with a given `Task Name` by 1 step. This leads to incrementing a counter related that Task in Quest UI todo list. Once the counter reaches `Total Progress`, the Task is marked as completed and is crossed off todo list
* **Reset Quest** — reset a particular Quest with a given `Quest Name`. Resetting the Quest will erase its current progress if any. If Quest is already marked as completed — this property will be reset as well
  {% endcolumn %}

{% column width="25%" %}

<figure><img src="/files/dwhW3XvIb0aPndGY1oNi" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/2dmnaz1MS1KC4Acos3n0" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/TKX9R7P6QrHVT3BLoVxy" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/T0S5qAxtoZ3rgzbs9LdZ" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

## Task Reference

***

{% columns %}
{% column width="16.666666666666664%" %}
`Simple`<br>
{% endcolumn %}

{% column width="50%" %}
The Simple Task represents a single activity, like collecting an item or opening a door. To complete it, the Player should trigger an **Action** of type `Quest` with `CompleteTask` command
{% endcolumn %}

{% column width="33.33333333333332%" %}

<div><figure><img src="/files/faRxyI3DCAPz1IG9gjAV" alt=""><figcaption></figcaption></figure> <figure><img src="/files/PnzccSRIKaI4y2UDWiTt" alt=""><figcaption></figcaption></figure></div>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="16.666666666666664%" %}
`Progress`<br>
{% endcolumn %}

{% column width="50%" %}
The Progress Task represents sequential activity, where multiple actions can contribute to task's progress — like collecting 3 gems, or killing 10 zombies. To advance a Progress Task, the Player should trigger an **Action** of type `Quest` with `AddTaskProgress` command
{% endcolumn %}

{% column width="33.33333333333332%" %}

<div><figure><img src="/files/UMi21jdBPqjDiiyGREtG" alt=""><figcaption></figcaption></figure> <figure><img src="/files/B57ohzCug2tOq5LXilr2" alt=""><figcaption></figcaption></figure></div>
{% endcolumn %}
{% endcolumns %}

***


# Post Effects

Learn how add Post Effects to your VIVERSE Worlds, and explore their settings

***

## About

PlayCanvas Toolkit offers a rich collecion of Post Effects, allowing creators to customize look and feel of their Worlds without touching any shader code! Below is a list of Post Effect you can add to your experience:

* Overlays and vignettes
* Bloom, Depth of Field (Bokeh) and Tilt Shift
* Hue / Saturation and Brightness / Contrast
* Sepia and Luminosity (Black and White)
* Screen Space Ambient Occlusion

## Usage

The Post Effects section can be found under <img src="/files/LIbvRsU51N1QmihzQ3AD" alt="" data-size="line"> VIVERSE Menu, which is located in the left sidebar. You can stack multiple effects on top of each other, but please be mindful of possible performance impact! Here is an example of adding two effects to your Scene — Sepia and Vignette:

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

### Effect 01: Sepia

* Navigate to Post Effects section and click **Edit**, then **Add Post Effect**
* Click on **Type** selector and find `Sepia`
* You'll see bright yellow prompt informing you about unsaved changes. Click **Save** button to save your current Post Effect configuration
* Launch your Scene — you should see it in Sepia tone now!
  {% endstep %}

{% step %}

### Effect 02: Vignette

* Go through the same steps and add another **Post Effect** on top your current one, but this time choose `Vignette` at the end of the list
* As previously, save your changes and launch your Scene. Now you have both Sepia and Vignette applied!
* Try adding more if you like! You can have as much effects as you like, as long as rendering performance is not hindered
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/IYj9exjdMPIlEiCvbzTu" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/H6BatIm3PRg2ZkqtJEV3" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/dk0VIlOLgHCase7dZgcD" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/UlBC1sl7NwauERSPVgS1" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

## Reference

***

{% columns %}
{% column width="25%" %}
`Blend` \
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adds a fullscreen transparent overlay on top of your experience. Typically used for vignettes, dirty camera effects, and so on
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/iOysl2V7W1VBOGlOLeUj" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Bloom` \
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adds soft fringe of light around bright areas in your world, simulating imaging artefacts of real world cameras
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/KqxmHho0mkjNKygCyvP4" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Bokeh` \
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adds Depth of Field effect, blurring out of focus areas, similar to how cinematic cameras behave in the real world
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/IEIC4th1VRkIePh8pFJK" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Brighness` \
`Contrast`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adjusts Brightness and Contrast of your experience, similar to Photoshop image adjustment with the same name
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/O3oDh65c0FzqhxbYpgrt" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Edge` \
`Detect`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Extracts edges from rendered frame and draws them on top of your experience in an overlay
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/miWxONundCxRR2JaYc7Z" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Fxaa` \
`Effect`
{% endcolumn %}

{% column width="50%" %}
Applies Fast Approximate Anti-Aliasing to your rendering pipeline. Particularly useful when your world has a lot of alpha blending and per-pixel shader effects
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/xvpaoofwxQowH89hnvF4" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Horizontal` \
`TiltShift`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adds subtle Horizontal Tilt Shift to Depth of Field effect, blurring out left and right portions of the screen
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/IVokadGgxCpZ6BKNtOyp" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Hue`\
`Saturation`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adjusts Hue and Saturation of your experience, similar to Photoshop image adjustment with the same name
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/FEiV7OkVNI1PPN1JJjei" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Luminosity`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Renders your experience in black and white mode, based on per-pixel luminosity values
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/4QayJUjYy2JrS4pye2i2" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Sepia`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Applies Sepia filter to your experience, similar to Photoshop filter with the same name
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/saZ2tQgT4h6uhNkNMJ4H" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`SSAO`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adds Screen-Space Ambient Occlusion to your scene. This is a computationally expensive effect, so consider using it in desktop-only experiences
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/CJ4SbmBBv657gpbs9tn7" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Vertical`\
`TiltShift`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adds subtle Vertical Tilt Shift to Depth of Field effect, blurring out top and bottom portions of the screen
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/RLc9VDmYwvZ409kg1tAI" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***

{% columns %}
{% column width="25%" %}
`Vignette`\
`Effect`
{% endcolumn %}

{% column width="50%" %}
Adds subtle darkening to screen corners, simulating real world camera lenses
{% endcolumn %}

{% column width="24.999999999999986%" %}

<figure><img src="/files/w3chw6sjQTIhwTTynDqt" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

***


# Video Playback

Learn how to add playable Videos to your VIVERSE Worlds, and to show optional Media Controls

***

## About

PlayCanvas Toolkit comes with an integrated Video Player, allowing creators to place Videos inside their VIVERSE Worlds — both as flat screens and 360 spheres! The Video file can be hosted both inside PlayCanvas project or external URL, and supports autoplay, looping and muting, as well as positional sound. In addition, the Toolkit comes with convenient Media Control component, which enables the Player to control Video playback while experiencing your 3D World.

## Usage

Please refer to instructions below to setup a Video Entity in your VIVERSE World, with optional Media Control component:

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

### Create Video Entity

* Create a new empty Entity in your Scene
* Click **Add Viverse Component** button and select **Rendering > Video Texture**
* If you did everything correctly - you should see a **viverseVideoTexture** script attached to your Entity
  {% endstep %}

{% step %}

### Setup Video asset or URL

* Upload Video asset to your PlayCanvas project
* Drag and drop it into **Asset** attribute of **viverseVideoTexture** script
* Now you should see the first frame of your Video in PlayCanvas Editor!
* As alternative, you can use public URL as a Video source
  {% endstep %}

{% step %}

### Adjust and Test

* Adjust your Entity's **Position**, **Rotation** and **Scale** to match Video's aspect ratio and desired position / orientation in your Scene
* Launch your Scene and confirm that the Video is playing as expected
* Please note that muted Videos can play automatically, but unmuted ones requires user's interaction with the page to start playing
* Feel free to adjust other attributes like **Render Side**, **Auto Play**, **Loop**, **Muted**, **Volume** and **Positional Sound**, as you see fit
* Also feel free to experiment with **Projection** attribute, since it allows playing 360 videos on a spherical surface!
  {% endstep %}

{% step %}

### Optional: Add Controls

* With your Entity selected, click **Add Viverse Component** button and choose **Helper > Media Control**
* Relaunch your Scene once again — now when you hover over your Video Entity you can see Media Controls and interact with them!
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/UFLlWxBDxzYwHhi4Jzah" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/q6V1H64sPVbYmPyemajF" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/wvzGhmVmjRxRJEbLzITF" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/QaAku1NiorT2sBIaUduH" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/wQcJqFFx3JmpfWkZhWp5" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/mSBXvdea4ez5tvOxauOf" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}


# Custom Scripts

Learn how to link your custom code to Triggers and Actions

***

## Introduction

While VIVERSE Toolkit provides a huge range of no-code tools covering a lot of typical use cases, it doesn't stop you from writing [Custom Scripts](https://developer.playcanvas.com/user-manual/scripting/) and using full [PlayCanvas API](https://api.playcanvas.com/engine/) to your advantage! Below is a collection of recipes designed to help you create a bridge between Toolkit's Framework and PlayCanvas Scripting API.

## Using Triggers to execute Custom Code

One of the most useful integrations is to have a Custom Script that can execute some function when particular Trigger is activated. Here is how it can be done:

* Create a new [Trigger](/playcanvas-toolkit/building-with-playcanvas-toolkit/triggers) Entity of desired type, or use an already existing one. Make sure it has unique `Name` since it will be called inside our Custom Script
* Create a new .mjs Script, parse it and attach to some Entity in your Scene. **It doesn't have to be** our Trigger Entity — any other Entity will do
* Use the boilerplate below to link Custom Script function to your Trigger. Replace `some.trigger.name` in our example with your own unique Trigger's `Name`&#x20;
* Add your custom code inside `onTriggerActivated ()` handler, then launch your Scene and test!

{% tabs %}
{% tab title="script.mjs" %}

```javascript
// @ts-nocheck
import { Script } from 'playcanvas';
import { TriggerDispatcher } from '../.viverse/toolkit/extension.mjs';

export class TriggerHandler extends Script
{
    static scriptName = 'Trigger Handler';

    initialize ()
    {
        // Retrieve singleton instance of TriggerDispatcher
        this.dispatcher = TriggerDispatcher.getInstance ();
        
        // Use .on or .once to subscribe to your custom trigger events
        // Note how full event id is retrieved via .getTriggerEventName first
        this.dispatcher.on
        (
            this.dispatcher.getTriggerEventName ('some.trigger.name'),
            this.onTriggerActivated,
            this
        );
    }

    // Your custom code can be placed here
    onTriggerActivated ()
    {
        alert ('Custom code executed!');
    }
}
```

{% endtab %}
{% endtabs %}

## Executing Actions from Custom Code

Another widely used integration is to execute some Action from inside your Custom Script, under certain conditions — for example when user pressed some keyboard key. Here is how it can be done:

* Create a new [Action](/playcanvas-toolkit/building-with-playcanvas-toolkit/actions) Entity of desired type, or use an already existing one. Make sure it has unique `Name` since it will be called inside our Custom Script
* Create a new .mjs Script, parse it and attach to some Entity in your Scene. Unlike Trigger example above, **this script has to be attached** to our Action Entity in order to work prolery
* Use the boilerplate below to execute this Action inside your Custom Script function. Replace `some.action.name` in our example with your own unique Action's `Name`&#x20;
* Launch your Scene and test! Feel free to replace our keyboard activator by any other logic of your choice, and just keep `executeAction`&#x20;

{% tabs %}
{% tab title="script.mjs" %}

```javascript
// @ts-nocheck
import { Script } from 'playcanvas';

export class ActionExec extends Script
{
    static scriptName = 'Action Exec';

    initialize ()
    {
        // Subscribe to keyboard event to execute your custom code
        this.app.keyboard.on ('keydown', this.onKeyDown, this);
    }

    // Your custom code can be placed here
    onKeyDown (event)
    {
        switch (event.key)
        {
            // Execute Action with `some.action.name` name by pressing P on keyboard
            case pc.KEY_P:
                this.executeAction ('some.action.name')
                break;
        }
    }
    
    // Helper to execute desired Action found in `viverseAction` script instance
    executeAction (name)
    {
        const script = this.entity.findScript ('viverseAction');
        const action = script?.actions.find (action => action.activation === name);
        const execution = action && script?._getExecution (action);
        if (execution) execution (action);
    }
}
```

{% endtab %}
{% endtabs %}


# PlayCanvas Toolkit Changelog

Get download link to the latest version of VIVERSE Extension and explore the changelog.

***

{% hint style="success" %}

## VIVERSE PlayCanvas Toolkit v4.1.17

[DOWNLOAD](https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/4.1/4.1.17_viverse-world_production-sso.zip)
{% endhint %}

<table><thead><tr><th width="132.94921875" align="center">Release Date</th><th width="99.625" align="center">Version</th><th>Release Notes</th></tr></thead><tbody><tr><td align="center">08/05/2026</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/4.1/4.1.17_viverse-world_production-sso.zip">4.1.17</a></td><td><ul><li>Fixed preview link error in extension.</li></ul></td></tr><tr><td align="center">07/15/2026</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/4.1/4.1.16_viverse-world_production-sso.zip">4.1.16</a></td><td><ul><li>Updated version display in the PlayCanvas UI</li></ul></td></tr><tr><td align="center">6/16/2026</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/4.1/4.1.14_viverse-world_production-sso.zip">4.1.14</a></td><td><ul><li>New framework for PlayCanvas VIVERSE development</li></ul></td></tr><tr><td align="center">6/11/2026</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.62/3.62.2_viverse-world_production-sso_06b7b33.zip">3.62.2</a></td><td><ul><li>Patched issue with publishing API</li></ul></td></tr><tr><td align="center">5/1/2026</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.62/3.62.1_viverse-world_production-sso_c8fa510.zip">3.62.1</a></td><td><ul><li>Patched issue with publishing API</li></ul></td></tr><tr><td align="center">12/2/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.58/3.58.2_viverse-world_production-sso_248f45e.zip">3.58.2</a></td><td><ul><li>Fix the issue where adding or removing a post effect doesn’t immediately update the UI</li><li>Update Polygon Streaming SDK to 2.6.8</li></ul></td></tr><tr><td align="center"><p></p><p>8/15/2025</p></td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.57/3.57.1_viverse-world_production-sso_6985abe.zip">3.57.1</a></td><td><ul><li>Adds support for publishing PlayCanvas projects that utilize multiple scenes, as well as an <code>IWorldNavigationService</code> to the Create SDK API to allow for programmatic scene switching.</li><li>Adds the ability to set nearClip and farClip camera configuration:<br><img src="/files/tBwKLaDoPQPZIYgt04c1" alt=""></li></ul></td></tr><tr><td align="center">8/5/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.55/3.55.0_viverse-world_production-sso_ad3f520.zip">3.55.0</a></td><td><ul><li>Quest Celebration Event Bug: Fix the issue where quests with celebrations configured are not triggered as completed after the user finishes the task.</li><li>Sync the Trigger Fix: Fix the issue where "Sync the trigger" in Trigger and Action is not working.</li></ul></td></tr><tr><td align="center">7/18/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.52/3.52.0_viverse-world_production-sso_a987bb3.zip">3.52.0</a></td><td><ul><li>Flying in VR: switch to Smooth Locomotion using the XRService, then in any World where flying is enabled in World Settings, press down/click the Smooth Locomotion joystick to enter flight mode. Once flying, pressing forward on the joystick will fly forward along the VR camera's forward axis (i.e. wherever you're looking), and vice versa backwards.</li><li>The Enter VR button has been made more reliable.</li></ul></td></tr><tr><td align="center">7/7/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.51/3.51.0_viverse-world_production-sso_a21001e.zip">3.51.0</a></td><td><ul><li><p>Fixes most cases where the no-code extension was losing being lost when the PlayCanvas editor disconnects &#x26; reconnects to necessary backend services.</p><ul><li>Also now displays a warning modal if the extension does enter a disconnected state.</li></ul></li><li><code>XRService.start()</code>  now accepts a callback so code execution can be paused prior to successful VR entry.</li></ul></td></tr><tr><td align="center">6/23/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.50/3.50.1_viverse-world_production-sso_d04b010.zip">3.50.1</a></td><td><ul><li><p><code>XRService</code>  now includes new locomotion options you can set per controller: smooth, teleport, and none</p><ul><li>As a minor optimization, we now hide the VR cursor when its <code>inputSource</code> is lost</li></ul></li><li>1st- and 3rd-person camera FOVs now settable in PlayCanvas:<br><img src="/files/CdVPUsFkk7Re7VlPqVX6" alt=""></li></ul></td></tr><tr><td align="center">5/23/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.49/3.49.0_viverse-world_production-sso_cf2d0a7.zip">3.49.0</a></td><td><ul><li>Bug fix: prevent duplicate injection of custom loading screen scripts during publishing.</li><li>Re-enables debugging in the browser by adding publishing modes: <code>Debug</code> mode is designed to assist in development and debugging processes, whereas <code>Standard</code> mode delivers an optimized, minified build intended for distribution.<br><br><img src="/files/yETavHBus7TT98paoaBW" alt=""></li></ul></td></tr><tr><td align="center">4/29/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.48/3.48.0_viverse-world_production-sso_7918fed.zip">3.48.0</a></td><td><p>Add SDK support to functions</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IXrService.html">IXrService</a></li></ul><p>-Methods: start</p><p>-Methods: end</p><p></p><ul><li>Re-enable debugging in the browser</li></ul><p>-Align game_script.js behavior with esm.js to allow setting breakpoints</p><p></p><ul><li>Enable custom loading screen</li></ul><p></p></td></tr><tr><td align="center">4/16/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.46/3.46.1_viverse-world_production-sso_1b7da7e.zip">3.46.1</a></td><td><ul><li>Add 'Enable Flying' option in VIVERSE scene settings</li><li>Fixed turnToward method</li></ul></td></tr><tr><td align="center">4/9/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.45/3.45.3_viverse-world_production-sso_1a16ced.zip">3.45.3</a></td><td><p>Add SDK support to functions</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IQuest.html">IQuest</a></li></ul><p>-Methods: addCheckTask</p><p>-Methods: addProgressBarTask</p><p>-Methods: fire</p><p>-Methods: getTaskById</p><p>-Methods: off</p><p>-Methods: on</p><p>-Methods: reset</p><p>-Methods: start</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IQuestService.html">IQuestService</a></li></ul><p>-Methods: addQuest</p><p>-Methods: fire</p><p>-Methods: getQuestById</p><p>-Methods: getQuestByName</p><p>-Methods: off</p><p>-Methods: on</p><p>-Methods: resetAllQuests</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/ITask.html"><sub>ITask</sub></a></li></ul><p>-Methods: fire</p><p>-Methods: off</p><p>-Methods: on</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IProgressBarTask.html">IProgressBarTask</a></li></ul><p>-Methods: addProgress</p><p>-Methods: fire</p><p>-Methods: off</p><p>-Methods: on</p></td></tr><tr><td align="center">3/27/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.45/3.45.0_viverse-world_production-sso_67c0a75.zip">3.45.0</a></td><td><p>Add SDK support to functions</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/ILocalPlayer.html">ILocalPlayer</a></li></ul><p>-Methods: resetToViverseAvatar</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IPlayer.html">IPlayer</a></li></ul><p>-Properties: avatar</p><p>-Properties: network</p><p>-Properties: nametag</p><p>-Properties: profile</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IPlayerService.html">IPlayerService</a></li></ul><p>-Properties: localPlayer</p><p>-Properties: remotePlayers</p><p>-Properties: playerCount<br><br>Add enable/disable toggle of microphone and microphone permission function in “Player Config > Disable Microphone”</p></td></tr><tr><td align="center">3/12/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.44/3.44.12_viverse-world_production-sso_4cd812f.zip">3.44.12</a></td><td><p>Add SDK support to functions</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IXrController.html">IXrController</a></li></ul><p>-Properties: handedness</p><p>-Properties: inputSource</p><p>-Properties: modelEntity</p><p>-Methods: resetModelAsset</p><p>-Methods: setModelAsset</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IXrControllerEvents.html">IXrControllerEvents</a></li></ul><p>-Properties: add</p><p>-Properties: remove</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IXrService.html">IXrService</a></li></ul><p>-Properties: controllers</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/interfaces/IXrServiceEvents.html">IXrServiceEvents</a></li></ul><p>-Properties: controller:addInput</p><p>-Properties: controller:removeInput</p><ul><li><a href="https://viveportsoftware.github.io/pc-lib/classes/CameraService.html">CameraService</a></li></ul><p>-Properties: canRotate</p><p>-Properties: canZoom</p><p>-ILocalPlayer</p><p>-Methods: scaleAvatar</p></td></tr><tr><td align="center">2/26/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.44/3.44.7_viverse-world_production-sso_704de4f.zip">3.44.7</a></td><td><ul><li>Adding switchPov methods to change 1st &#x26; 3rd person POV through code</li></ul></td></tr><tr><td align="center">2/6/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.43/3.43.1_viverse-world_production-sso_090fcab.zip">3.43.1</a></td><td><ul><li>Add 3 features in Trigger &#x26; Action - EntityAssetUnload, EntityAssetReload, EntityAssetDestroy</li></ul></td></tr><tr><td align="center">1/7/2025</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.40/3.40.7_viverse-world_production-sso_836bc16.zip">3.40.7</a></td><td><ul><li>Add post effects settings</li><li>Add scene ownership check on publish tab</li></ul></td></tr><tr><td align="center">12/3/2024</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.38/3.38.0_viverse-world_production-sso_17a1014.zip">3.38.0</a></td><td><ul><li>Handle response err message from PlayCanvas server job api</li><li>Fix service worker error</li></ul></td></tr><tr><td align="center">12/27/2024</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.36/3.36.4_viverse-world_production-sso_5956237.zip">3.36.4</a></td><td><ul><li>Error handling when publishing the scene</li></ul></td></tr><tr><td align="center">10/21/2024</td><td align="center"><a href="https://htc-directus-dev-usw2-pc-ui-ext.s3.amazonaws.com/viverse-world/production-sso/3.35/3.35.19_viverse-world_production-sso_5646fef.zip">3.35.19</a></td><td><ul><li>Support latest Chrome version</li></ul></td></tr></tbody></table>


# Toolkit Setup \[Legacy v3]

Learn how to install the VIVERSE Chrome Extension for PlayCanvas Editor, create your first world, then publish it to VIVERSE.

***

### Introduction

PlayCanvas is an open-source game engine designed to advance the development of 3D web games, interactive content and rich multimedia. Unlike similar WebGL engines like Three.js or Babylon, PlayCanvas comes with sophisticated in-browser Editor (inspired by early Unity 2.x - 3.x), which provides its users with a rich set of tools — allowing to assemble scenes from imported assets, setup real-time and baked lighting, write custom scripts, create animation graphs, and much much more.

To make creator experience even smoother, VIVERSE has developed a special Extension for Chrome browsers which extends PlayCanvas Editor functionality with additional set of no-code tools and publishing options. It provides common building blocks like Triggers, Actions and Quests, enables Player locomotion and Avatar system in your project, and allows one-click testing and publishing right from the Editor.

Below is a useful set of tutorials to help you get started with VIVERSE PlayCanvas Extension. We assume you're already familiar with PlayCanvas Editor itself and have PlayCanvas account. If not, please feel free to explore [PlayCanvas User Manual](https://developer.playcanvas.com/user-manual/) first!

### Install PlayCanvas Extension

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

#### Get the latest Extension from VIVERSE

* [Download](/playcanvas-toolkit/playcanvas-toolkit-changelog) the latest version of Playcanvas Extension
* Unzip downloaded file on your computer
  {% endstep %}

{% step %}

#### Navigate to Chrome Extensions manager

* Click Extensions icon in Chrome toolbar and open the Extensions Popup
* Click Manage Extensions at the bottom and open the Extensions Manager in a new tab
  {% endstep %}

{% step %}

#### Install the Extension

* Enable Developer Mode at the top right corner
* Click Load Unpacked and select the folder with unpacked Extension you downloaded previously
* Verify the Extension is now present in Extensions Manager tab
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/PgQlu1KlSLvRD950UbSk" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/JrL87eIzIzuXGjFAxvhQ" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/psYt9QW1kDGHWpBqRnzo" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/267M4Gy01EEneZQwG6U8" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/278YnLKwIZX7OxCYPUV1" alt=""><figcaption></figcaption></figure>

{% endcolumn %}
{% endcolumns %}

### Create and publish your first project

{% hint style="info" %}
Before proceeding any further, we assume you're familiar with the basics of PlayCanvas Editor. If you're looking for a comprehensive introduction to it — please refer to a dedicated [PlayCanvas Editor Manual](https://developer.playcanvas.com/user-manual/editor/) first!
{% endhint %}

{% columns %}
{% column width="66.66666666666666%" %}
{% stepper %}
{% step %}

#### Initialize your new project

* Create a new PlayCanvas project or open an already existing one
* Make sure VIVERSE Extension is properly initialized — you should see **ExtensionEntity** in your scene hierarchy and `@viverse` folder added to your project. Refresh the page if it's not the case
* Login into VIVERSE via dedicated button in the left toolbar. Once logged in, you should see **VIVERSE Scene Settings** button there
  {% endstep %}

{% step %}

#### Setup 3D environment

* First, delete default **Camera** in Scene Hierarchy. The Extension will provide its own Camera system that will be added to your Scene at runtime
* Import **Ammo** physics library and add a Floor plane with **Collision** component and static **Rigidbody**. This is crucial because VIVERSE Player system relies on built-in PlayCanvas physics engine for moving around. Without a floor, your player will be simply falling through
* Add additional geometry and lights if want!
  {% endstep %}

{% step %}

#### Create a Spawn Point

* In order for a Player to appear in your world, it should be spawned somewhere first. VIVERSE Extension provides a convenient way to control that!
* Simply create a new empty **Entity** in your scene and add `spawn-point` tag to it
* Make sure it's positioned at the floor level or above. Feel free to rotate it to change the initial orientation of your Player
  {% endstep %}

{% step %}

#### Preview your project

* Once you have a walkable floor and a spawn point, it's time to test your project live!
* Go to the **Publish / Download** menu and open the **Builds** popup window. In that window, click **Publish to Viverse** button and wait until your project is successfully uploaded
* Once it's uploaded and ready, the **Preview** button should appear. Clicking it will open your project in a new tab, in preview mode. This preview URL is public and you can share it with your friends and colleagues to test the current version of your work
* Return back to PlayCanvas Editor and modify your project as you see fit. Keep publishing and previewing to see your most recent changes live in VIVERSE environment
  {% endstep %}

{% step %}

#### Final: Create a VIVERSE World for your project

* After confirming everything is working correctly in preview mode, you can publish your project to production environment!
* In preview mode, click "**Create World**" button, give your World a name and click the "**Create**" button to publish it to the VIVERSE Create platform
* The resulting URL is the public link to your World. You can return back to your project any time and republish an updated version as you see fit
* Feel free to configure your [World Settings](/publishing-with-your-viverse-account#world-settings) as well
  {% endstep %}
  {% endstepper %}
  {% endcolumn %}

{% column width="33.33333333333334%" %}

<figure><img src="/files/RbCTe7rg2vc9cfrSeaUv" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/0cteUHC0hdBHurDlV7kK" alt=""><figcaption></figcaption></figure>

<div><figure><img src="/files/l2txk1BfuSuAjq8qaGij" alt=""><figcaption></figcaption></figure> <figure><img src="/files/pzHozj8z1j6eL7YOa5jA" alt=""><figcaption></figcaption></figure></div>

<figure><img src="/files/zSygLZEyIDXp9JxCFSnr" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/ouX3G0TSfAUChaoSX0hS" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/vxkQKRElnjG2j5fucUa1" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/NRIyqUHZ7nVt2sbuhvqx" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/xa4lZtYRNCJl6ASOkVU6" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/W3cbF9zqLuJCyfH4b9fg" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/J2ZPiDP7R0mKyuZINecC" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

{% embed url="<https://www.youtube.com/watch?v=w3MFHo4v69E>" %}

## "Factory Reset" PlayCanvas Extension

> Sometimes issues in the installation process can produce persistent bugs that are only fixed by fully resetting the Extension. If you're experiencing these bugs please follow the steps below

{% stepper %}
{% step %}

#### Sign in and out of VIVERSE

Sign out of your account on viverse.com and then sign back in. **MAKE SURE** you are using the same email account as the one associated with your PlayCanvas account
{% endstep %}

{% step %}

#### Delete VIVERSE entities from PlayCanvas project

Go back to your PlayCanvas project and delete:

* The `Extension` Entity from your scene hierarchy
* The `.viverse` folder from your project assets
* The `.index.mjs` folder from your scripts folder
  {% endstep %}

{% step %}

#### Clear Application Storage

Open the [Developer Tools](https://elfsight.com/blog/how-to-work-with-developer-console/) in your Chrome broswer, go to "Application", select "Storage" in the left hand toolbar and click the "Delete Site Data" button
{% endstep %}

{% step %}

#### Refresh the page

Refresh your PlayCanvas project page with the VIVERSE extension enabled
{% endstep %}

{% step %}

#### Sign in back to PlayCanvas

Sign in to PlayCanvas when prompted. **MAKE SURE** you are using the same email account as the one associated with your VIVERSE account
{% endstep %}

{% step %}

#### Sign in back to VIVERSE

Sign in to VIVERSE from within your PlayCanvas project using the VIVERSE Scene Settings
{% endstep %}
{% endstepper %}


# Building with the PlayCanvas Toolkit \[Legacy v3]

Learn how to build projects using common no-code functionality, as well as custom scripted entities and advanced APIs.

***

{% content-ref url="/pages/2wx42cauhmiTAcMtGCIA" %}
[Scene Settings](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/scene-settings)
{% endcontent-ref %}

{% content-ref url="/pages/IpbmOZucQ2rtlssDnRFV" %}
[No Code Tools](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools)
{% endcontent-ref %}

{% content-ref url="/pages/hut9TMT0PHR7lROPZcYs" %}
[Custom Code](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/custom-code)
{% endcontent-ref %}


# Scene Settings

This page overviews the interface for controlling global settings for your VIVERSE world in PlayCanvas.

***

### Introduction&#x20;

The VIVERSE scene settings interface is used to control several settings for your entire VIVERSE world. The interface can be opened by clicking on the triangle button that is added to the left-hand toolbar when the extension is enabled:  <img src="/files/wEurMQMatpNSPSU7JHhB" alt="" data-size="line">

<figure><img src="/files/gmC9juWBEQrR9Lq8giNV" alt=""><figcaption></figcaption></figure>

### Polygon Streaming

The Polygon Streaming settings control how all assets embedded with VIVERSE's [Polygon Streaming](https://www.viverse.com/polygon-streaming) service are rendered in your world. For details on these settings, please consult this documentation: [Polygon Streaming Project Settings](/polygon-streaming/integrations-and-plugins/polygon-streaming-playcanvas-sdk/plugin-usage/option-1-browser-extension/polygon-streaming-project-settings)

### Quest Config

The Quest Config settings allow you to create and add steps to Quests in your world. For details on these settings, please consult this documentation: [Quests](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/quests)

### Post Effect

The Post Effect settings allow you to add and control the values of post-processing effects on your avatar's camera. For more details on the specific post effects and their values, please consult this documentation: <https://developer.playcanvas.com/user-manual/graphics/posteffects/>


# No Code Tools

Explore all major building blocks of no-code functionality provided by the VIVERSE PlayCanvas Toolkit.

***

{% columns %}
{% column %}
{% content-ref url="/pages/SgztGDsqFKoGlm6As2Sy" %}
[Sample Project](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/sample-project)
{% endcontent-ref %}

{% content-ref url="/pages/Oq1QKp8FLqtlCqeymh9P" %}
[Trigger & Action](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/trigger-and-action)
{% endcontent-ref %}

{% content-ref url="/pages/GRpHMM2q9Xuh21ip2276" %}
[Pick and Throw](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/pick-and-throw)
{% endcontent-ref %}

{% content-ref url="/pages/DoxAAn8LHroMKOf4bvJb" %}
[Seat](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/seat)
{% endcontent-ref %}
{% endcolumn %}

{% column %}
{% content-ref url="/pages/hWGd1wvShfiSjAmUb2vi" %}
[Quests](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/quests)
{% endcontent-ref %}

{% content-ref url="/pages/o3AupaoR9ugAg0bHTZrV" %}
[Media](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/media)
{% endcontent-ref %}

{% content-ref url="/pages/kVQmg06mCVRtP2P5YwDA" %}
[Networked](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/networked)
{% endcontent-ref %}

{% endcolumn %}
{% endcolumns %}


# Sample Project

A PlayCanvas sample project that provides demonstrations of how to use the features in the VIVERSE Extension.

***

We created a PlayCanvas sample project that contains examples of how to utilize the PlayCanvas Extension. Download the project and import it into a PlayCanvas account. Publish the project to VIVERSE and enter Preview mode to test out the functionality. The pages listed under API Reference are the functionality that the sample project is demoing.

<table><thead><tr><th width="295">Project Name</th><th width="121">Download</th><th>Version</th></tr></thead><tbody><tr><td>VIVERSE_PlayCanvas_Sample_1.2</td><td><a href="https://github.com/ViveportSoftware/viverse-docs/raw/refs/heads/main/samples/VIVERSE_PlayCanvas_Sample_1.2_2025_2_18-20_30_18%201.zip">Zip File</a></td><td><p>Sample Project Version: <strong>1.2</strong>  </p><p>PlayCanvas Extension Version: <strong>3.44.1</strong></p></td></tr></tbody></table>

This project is separated into different stations. Each station has **Tags** added to help identify the VIVERSE functionality the station is demonstrating.

<figure><img src="/files/l3qauderQjmY5YOk276f" alt="" width="375"><figcaption></figcaption></figure>

<table><thead><tr><th width="361">Name</th><th>Station</th></tr></thead><tbody><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/event-listeners#entitysubscribetriggerenter">EntitySubscribeTriggerEnter</a></td><td>1</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/event-listeners#entitysubscribetriggerleave">EntitySubscribeTriggerLeave</a></td><td>1</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-enabling-and-disabling#entityenablebyid">EntityEnableById</a></td><td>1</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-enabling-and-disabling#entitydisablebyid">EntityDisableById</a></td><td>1</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-enabling-and-disabling#entityenablebytag">EntityEnablyByTag</a></td><td>2</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-enabling-and-disabling#entitydisablebytag">EntityDisableByTag</a></td><td>2</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/event-listeners#entitysubscribecollisionstart">EntitySubscribeCollisionStart</a></td><td>3</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-enabling-and-disabling#entitydisable">EntityDisable</a></td><td>3</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/event-listeners#entitysubscribecollisionend">EntitySubscribeCollisionEnd</a></td><td>4</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-enabling-and-disabling#entityfadeout">EntityFadeOut</a></td><td>4</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-enabling-and-disabling#entityfadein">EntityFadeIn</a></td><td>4</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-collision-enabling-and-disabling#entityenablecollision">EntityEnableCollision</a></td><td>5</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-collision-enabling-and-disabling#entitydisablecollision">EntityDisableCollision</a></td><td>5</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/event-listeners#notificationcenterpublish-and-notificationcentersubscribe">NotificationCenterPublish</a></td><td>6</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/event-listeners#notificationcenterpublish-and-notificationcentersubscribe">NotificationCenterSubscribe</a></td><td>6</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-collision-enabling-and-disabling#entitytogglecollision">EntityToggleCollision</a></td><td>6</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/animation-and-sound#entityplaysound">EntityPlaySound</a></td><td>7</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/animation-and-sound#entitystopsound">EntityStopSound</a></td><td>7</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/avatar-teleport-and-checkpoint#teleportavatar">TeleportAvatar</a></td><td>8</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/animation-and-sound#entityplayanimation">EntityPlayAnimation</a></td><td>9</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/event-listeners#notificationcentersubscribeentitypicking">NotificationCenterSubscribeEntityPicking</a></td><td>10</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/seat#seat">Seat</a></td><td>11</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-physics#entityrigidbodyaddforceinphysics">EntityRigidbodyAddForceInPhysics</a></td><td>12</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/entity-enabling-and-disabling#entitytoggleenabled">EntityToggleEnabled</a></td><td>13</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/avatar-teleport-and-checkpoint#entitycheckpoint">EntityCheckPoint</a></td><td>14</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/animation-and-sound#entitysubscribeanimationstart">EntitySubscribeAnimationStart</a></td><td>15</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/animation-and-sound#entitysubscribeanimationend">EntitySubscribeAnimationEnd</a></td><td>15</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/animation-and-sound#entitysubscribeanimationevent">EntitySubscribeAnimationEvent</a></td><td>15</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/quests#quest-system">Quest</a></td><td>16</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/media/images#media-images">Image</a></td><td>17</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/media/video#media-video">Video</a></td><td>18</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/media/audio#media-audio">Audio</a></td><td>19</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/media/polygon-streaming#media-polygon-streaming">Polygon Streaming</a></td><td>20</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/asset-management#entityassetunload">EntityAssetUnload</a></td><td>21</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/asset-management#entityassetreload">EntityAssetReload</a></td><td>21</td></tr><tr><td><a href="https://docs.viverse.com/playcanvas-sdk/api-reference/trigger-and-action/asset-management#entitydestroy">EntityDestroy</a></td><td>22</td></tr></tbody></table>


# Quests

This document provides a guide that can be used to setup a Quest-based system in a VIVERSE project.

***

### Quest System

#### **Understanding the Quest System**

{% hint style="info" %}
Currently, the Quest system is unavailable for testing in Preview mode and can only be seen after clicking the Create World button in Preview mode.
{% endhint %}

In the sample app below, the trigger area is outlined in blue. Once the avatar enters the trigger area, the Quest system starts.

The first task requires the user to click the red box. Once the user clicks the red box, the first task is completed and the Quest system updates.

The second task requires the user to click multiple green boxes. Each green box that is clicked adds progress toward completing the task. Once all green boxes have been clicked, the task is completed and the Quest is finished.

If the user clicks a blue box during the quest, the Quest system resets. The user will need to re-enter the trigger area to restart the quest.

| <img src="/files/zjobgExgZmwhxFfmeAHY" alt="" data-size="original">                                              | <img src="/files/zjZIrhLJiThON9mbGvYq" alt="" data-size="original">                                                        | <img src="/files/QupkXGruFTGPTtH1fMdY" alt="" data-size="original">        |
| ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| The avatar enters the trigger area and the Quest system dialog appears.                                          | With the Quest system started, the user clicks on the red box and the first task is completed.                             | When the user clicks each green box, progress is added to the second task. |
| <img src="/files/D34dIxDC0j7bxKWEA3lc" alt="" data-size="original">                                              | <img src="/files/ipOLsP5HpZC7hvkJXDSL" alt="" data-size="original">                                                        |                                                                            |
| Once the user clicks on the final green box, the second task is complete and the Quest system dialog disappears. | If the user clicks on a blue box before both tasks are completed, the Quest system resets and needs to be triggered again. |                                                                            |

### How to Create a Quest System

{% stepper %}
{% step %}

#### Open Viverse Scene Settings

A. Click on the Viverse Scene Settings button.

<figure><img src="/files/ZvWbxzA7ZSddSdShQipJ" alt="" width="188"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create the quest

A. Give the quest a name in the **Quest name** field. The text: **Find the boxes!** was added.

B. Give the quest a description in the **Quest description** field. The text: **Click on the boxes that are a specific color.** was added.

C. Create the first task and give the task a description in the **Task description** field. The text: **Click on the red box.** was added.

D. Set the **Task type** to **check**.

E. Create the second task and give the task a description in the **Task description** field. The text: **Click on the green boxes.** was added.

F. Set the **Task type** to **progressBar**.

G. Add the value **4** to the **Progress Steps** field.

<figure><img src="/files/wo4vT1Pgr9Q0OdBw7lDR" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create the trigger area that will start the quest

A. Create a new **3D Box** entity.

B. Add a **Collision** component.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/tmjkroUXMnKwWd7RBJqS" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Starting the quest

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **Quest.**

E. In the **selected quest** field, choose **Find the boxes!**

F. In the **quest response** field, choose **startQuest**.

<figure><img src="/files/XAzwpExYh4sYWP1fANvV" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create an object that can be clicked to complete the first task

A. Create a 3D object.

B. Add a **Collision** component.

C. Add a material. Red has been added because this will be the box that will be clicked on to complete the first task.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/FeIfVKdEPNDEZlwP9Fni" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Completing the first task

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribeEntityPicking**.

C. Add an **Action** and select **Quest.**

D. In the **selected quest** field, choose **Find the boxes!**

E. In the **quest response** field, choose **completeTask**.

F. In the **selected task field,** choose **Click on the red box.**

<figure><img src="/files/zF0NySK5B1NUaTO1F9oi" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create multiple objects that can be clicked to complete the second task

A. Create multiple 3D objects.

B. Add a **Collision** component to each object.

C. Add a material. Green has been added because these will be the boxes that will be clicked on to complete the second task.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/Bfz3kWum3SYTEqCBLaX6" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Completing the second task

The following steps are completed for all green boxes.

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribeEntityPicking**.

C. Add an **Action** and select **Quest.**

D. In the **selected quest** field, choose **Find the boxes!**

E. In the **quest response** field, choose **addTaskProgress**.

F. In the **selected task field,** choose **Click on the green boxes.**

<figure><img src="/files/CKtIuP1mCxBFmAxnYSwA" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create multiple objects that can be clicked to reset the quest

A. Create multiple 3D objects.

B. Add a **Collision** component to each object.

C. Add a material. Blue has been added because these will be the boxes that will be clicked on to reset the quest.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/6zkcyuberxATwJOzto6g" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Resetting the quest

The following steps are completed for all blue boxes.

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribeEntityPicking**.

C. Add an **Action** and select **Quest.**

D. In the **selected quest** field, choose **Find the boxes!**

E. In the **quest response** field, choose **resetQuest**.

<figure><img src="/files/wOcmOL3THnvihMVhHlk0" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Trigger & Action

Explore all common types of Triggers and Actions implemented in the PlayCanvas Toolkit.

***

{% columns %}
{% column %}
{% content-ref url="/pages/42ncIF2SLaGI0ZZo59QJ" %}
[Event Listeners](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/trigger-and-action/event-listeners)
{% endcontent-ref %}

{% content-ref url="/pages/jX2fn0DFiVNdggWcfwk7" %}
[Entity Collision Enabling & Disabling](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/trigger-and-action/entity-collision-enabling-and-disabling)
{% endcontent-ref %}

{% content-ref url="/pages/EwDrpTcSuZwZouma3zWd" %}
[Avatar Teleport & Checkpoint](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/trigger-and-action/avatar-teleport-and-checkpoint)
{% endcontent-ref %}

{% content-ref url="/pages/nwh28KOwIswrbL6whFD3" %}
[Asset Management](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/trigger-and-action/asset-management)
{% endcontent-ref %}

{% endcolumn %}

{% column %}
{% content-ref url="/pages/6WgctwB3Z9TOmJscnf4C" %}
[Entity Enabling & Disabling](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/trigger-and-action/entity-enabling-and-disabling)
{% endcontent-ref %}

{% content-ref url="/pages/V71shqJohZCBuJh2kQUk" %}
[Entity Physics](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/trigger-and-action/entity-physics)
{% endcontent-ref %}

{% content-ref url="/pages/FSn5kSm0hTn8Cz2uAIEw" %}
[Animation & Sound](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/trigger-and-action/animation-and-sound)
{% endcontent-ref %}

{% endcolumn %}
{% endcolumns %}


# Event Listeners

This page provides several guides that can be used to setup event listeners in a VIVERSE project. The event listeners use triggers. Triggers can be configured to perform actions once activated.

***

### EntitySubscribeTriggerEnter

This guide provides instructions for setting up the **EntitySubscribeTriggerEnter** trigger. In the sample app below, the trigger areas are outlined in blue. Once the avatar enters into the blue area, an action occurs.

| <img src="/files/woPFo38JprYg1l0CbgA5" alt="" data-size="original">           | <img src="/files/lhdQRy7iFkHB0iWy6z5Z" alt="" data-size="original">        |
| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| The avatar is outside the blue trigger area and the gold coin is not visible. | The avatar has entered the blue trigger area and the gold coin is visible. |

In our example above, a trigger area is created. When the avatar enters the trigger area, an action is then initiated. Any object can be used as a trigger, as long as the object has a collision component. Our example uses a 3D box as the trigger area.

#### **Create a trigger for when an object enters an area**

{% stepper %}
{% step %}
**Create a trigger area**

A. Create a new **3D Box** entity.

B. Add a **Collision** component.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/OUYMJyKLnggmwt38xKhS" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntitySubscribeTriggerEnter* **trigger**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityEnableById.**

E. Add an object that will be enabled when the avatar enters the trigger area. The **golden\_coin** entity has been added to the **pick up specify execution entity**. The **golden\_coin** entity has also been disabled by default.

<figure><img src="/files/DWOpPIJcWLxdjJDzbOor" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntitySubscribeTriggerLeave

This guide provides instructions for setting up the **EntitySubscribeTriggerLeave** trigger. In the sample app, the trigger areas are outlined in blue. Once the avatar leaves the blue area, an action occurs.

| <img src="/files/Fg2yOBaaKQTAxLDUBN6m" alt="" data-size="original">          | <img src="/files/sUIkrN8JR6aDeEjqHsMp" alt="" data-size="original">             |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| The avatar is inside the the blue trigger area and the gold coin is visible. | The avatar leaves the blue trigger area and the gold coin is no longer visible. |

In this example, a trigger area is created and when the avatar leaves the trigger area, an action is initiated. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

#### **Create a trigger for when an object leaves an area**

{% stepper %}
{% step %}
**Create a trigger area**

A. Create a new **3D Box** entity.

B. Add a **Collision** component.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

<figure><img src="/files/9pk16qv4aMjjlrzik6ig" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntitySubscribeTriggerLeave* **trigger**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerLeave**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityDisableById.**

E. Add an object that will be disabled when the avatar leaves the trigger area. The **golden\_coin** entity has been added to the **pick up specify execution entity**.

<figure><img src="/files/nYxmvYIAj73PZT7op6lU" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntitySubscribeCollisionStart

This guide provides instructions for setting up the **EntitySubscribeCollisionStart** trigger. In the sample app, the **golden\_coin** is a trigger. Once the avatar collides with the **golden\_coin**, an action occurs.

| <img src="/files/lydwYkURBnCSwvGcQy3f" alt="" data-size="original"> | <img src="/files/cprMzrWyERZPDeOlNCM5" alt="" data-size="original">    |
| ------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| The avatar approaches the gold coin.                                | Once the avatar collides with the gold coin, the gold coin is removed. |

In this example, a trigger is created and when the avatar collides with the trigger, an action is initiated. Any object can be used as a trigger, as long as the object has a collision component. Because this specific example uses a 3D object that the avatar can collide with, a **RigidBody** component needs to be added. In this example, the object **golden\_coin** is used, but a simple cube will suffice.

#### **Create a trigger for when an object collides with another object**

{% stepper %}
{% step %}
**Create the 3D object that the avatar will collide with**

A. Add 3D object to the scene.

B. Add a **Collision** component.

C. Add a **Rigidbody** component.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/mzZd0iLF4t0i5vqMgr7F" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntitySubscribeCollisionStart* **trigger**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeCollisionStart**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityDisable.**

<figure><img src="/files/lV2QefBQ0C8FhgDcifvO" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntitySubscribeCollisionEnd&#x20;

This guide provides instructions for setting up the **EntitySubscribeCollisionEnd** trigger. In the sample app, a flattened 3D cube is a trigger. Once the avatar stops colliding with the cube, an action occurs.

| <img src="/files/Nwc81bsiwPdRozrBjpwB" alt="" data-size="original"> | <img src="/files/McMOaCYyZHml72Lbe0mD" alt="" data-size="original">                         |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| The avatar is colliding with the red platform.                      | Once the avatar stops colliding with the red platform, the red platform begins to fade out. |

In this example, a trigger is created and when the avatar or other objects collide with the trigger, an action is initiated. Any object can be used as a trigger, as long as the object has a collision component. Because this specific example uses a 3D object that the avatar can collide with, a **RigidBody** component needs to be added.

#### **Create A Trigger Based On When An Object Stops Colliding With Another Object**

{% stepper %}
{% step %}
**Create the 3D object that the avatar will collide with**

A. Add 3D object to the scene.

B. Add a **Collision** component.

C. Add a **Rigidbody** component.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/iTJb6j7UzNmiRCyJlkNU" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntitySubscribeCollisionEnd* **trigger**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeCollisionEnd**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityFadeOut.**

<figure><img src="/files/j8xmfHCfJXdlf6AaC7Wd" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### NotificationCenterSubscribeEntityPicking

This guide provides instructions for setting up the **NotificationCenterSubscribeEntityPicking** trigger. In the sample app, the user clicks on the buttons to show and hide the whale.

| <img src="/files/a1n5PVakbU2jRnHbuyMg" alt="" data-size="original"> | <img src="/files/y6xox1c30IbQbH8Ld4ED" alt="" data-size="original"> |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |
| The whale is visible.                                               | When the user clicks on the whale, the whale is disabled.           |

In this example, a trigger area is created on a button. When the user clicks on the trigger area, an action causes the 3D model to be disabled. Any object can be used as a trigger, as long as the object has a collision component.

#### **Create a trigger for when a user clicks an object**

{% stepper %}
{% step %}
**Create the 3D object that the user will click on**

A. Add a 3D object to the scene. In this example, a button is used, but any 3D object will work.

B. Add a **Collision** component.

C. Click the **Edit Viverse Extension** button.

<figure><img src="/files/ejQQTq8AttQ6w9LfEUwf" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *NotificationCenterSubscribeEntityPicking* **trigger**&#x20;

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribeEntityPicking**.

C. Add an **Action** and select **EntityDisable.** Add an object that will be disabled.

<figure><img src="/files/JgQmdmIx69XPT91EvUjb" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### NotificationCenterPublish & NotificationCenterSubscribe

This guide provides instructions for setting up the **NotificationCenterPublish** action and the **NotificationCenterSubscribe** trigger. In the sample app, every time the avatar enters the green trigger area, a notification is sent to the wall and the collider on the wall is toggled on/off. The wall is green when the avatar can pass through and red when the avatar can not pass through.

| <img src="/files/w3AlbwhZj4hbgcZgeiGJ" alt="" data-size="original">                                            | <img src="/files/Kk8gOcix0pXcCkU9MpEq" alt="" data-size="original">                                                                         |
| -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Before the avatar enters the green trigger area, the wall is red and the avatar can not pass through the wall. | Once the avatar enters the green trigger area, the green trigger sends a notification to the wall and the collider on the wall is disabled. |

In this example, a trigger is created and when the avatar enters the trigger area, an action sends a notification to another object. The other object receives the notification via trigger, then initiates an action.&#x20;

#### **Action and Trigger combination to send a notification from one object to another**

{% stepper %}
{% step %}
**Create the 3D object that will send the notification**

A. Add a 3D object to the scene.

B. The **Collision** component is not required for **NotificationCenterPublish** action or **NotificationCenterSubscribe** trigger to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Click the **Edit Viverse Extension** button.

<figure><img src="/files/CczDGWyMtazqlTSVWoTM" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *NotificationCenterPublish* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **NotificationCenterPublish.**

E. Create a unique notification name and add it to the **notification name to publish** field. In this example, the notification is called **ToggleWall**.

<figure><img src="/files/54U8Kr6kobwfgIq7hKoM" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Create the 3D object that will receive the notification**

A. Add a 3D object to the scene.

B. The **Collision** component is not required for **NotificationCenterPublish** action or **NotificationCenterSubscribe** trigger to work. The **Collision** component is required for the **EntityToggleCollision** action that will be used in this example.

C. The **Rigidbody** component is not required for the **NotificationCenterPublish** action or **NotificationCenterSubscribe** trigger to work. The **Rigidbody** component is required for the **EntityToggleCollision** action that will be used in this example.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/dNgqtcZbjC4BFovDofD3" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *NotificationCenterSubscribe* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribe**.

C. The same text that was added to the **notification name to publish** needs to be added to the **notification name to subscribe**.

D. Add an **Action** and select **EntityToggleCollision.**

<figure><img src="/files/qvKOGYMjK01sHqSqwcRu" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Entity Enabling & Disabling

This document provides several guides that can be used to show and hide objects in a VIVERSE project. These actions can be configured to execute when triggers are activated.

***

### EntityDisable

This guide provides instructions for setting up the **EntityDisable** action. In the sample app, once the avatar collides with the gold coin, the gold coin is disabled.

| <img src="/files/tq2IjsluQxaOyXxdSOO8" alt="" data-size="original"> | <img src="/files/fn6bS18j0bcXCM9J9lHI" alt="" data-size="original"> |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |
| The avatar is not colliding with the gold coin.                     | Once the avatar collides with gold coin, the gold coin is disabled. |

In this example, a trigger is created and when triggered, an action disables an object.&#x20;

#### **Create an action that disables an object**

{% stepper %}
{% step %}
**Create the 3D Object that will be disabled**

A. Add 3D object to the scene.

B. The **Collision** component is not required for **EntityDisable** action to work. The **Collision** component is required for the **EntitySubscribeCollisionStart** trigger that will be used in this example.

C. The **Rigidbody** component is not required for the **EntityDisable** action to work. The **Rigidbody** component is required for the **EntitySubscribeCollisionStart** trigger that will be used in this example.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/YGnXcvLMgQzqM1i0pA4d" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the EntityDisable action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeCollisionStart**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityDisable.**

<figure><img src="/files/jJkTANLqBZQ8W0v7LBCD" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntityEnableById

This guide provides instructions for setting up the **EntityEnableById** action. In the sample app, once the avatar enters the trigger area, the gold coin is enabled.

| <img src="/files/o70rQ9RYxD4JxLn2SFOy" alt="" data-size="original">             | <img src="/files/UwPVbpFPZtvxAYWw7oUH" alt="" data-size="original">     |
| ------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| When the avatar is outside of the blue trigger area, the gold coin is disabled. | When the avatar enters the blue trigger area, the gold coin is enabled. |

In this example, a trigger is created and when triggered, an action enables an object. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

#### **Create an action that enables a single object**

{% stepper %}
{% step %}
**Create a trigger area**

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityDisableById** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/zaiIipm5heNRUta7W6J0" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntityEnableById* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityEnableById.**

E. Add an object that will be enabled when the avatar enters the trigger area. The **golden\_coin** entity has been added to the **pick up specify execution entity**. The **golden\_coin** entity has also been disabled by default.

<figure><img src="/files/jFC3U0goQAL2UTCvoV9S" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntityDisableById

This guide provides instructions for setting up the **EntityDisableById** action. In the sample app, once the avatar leaves the trigger area, the gold coin is disabled.

| <img src="/files/HUeKioKFNd4geHMfNzNS" alt="" data-size="original">            | <img src="/files/I3MRcnzgPfc3lIl80X8w" alt="" data-size="original">      |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| When the avatar is inside the blue the trigger area, the gold coin is enabled. | When the avatar leaves the blue trigger area, the gold coin is disabled. |

In this example, a trigger is created and when triggered, an action disables an object. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

#### **Create an action that disables a single object**

{% stepper %}
{% step %}
**Create a trigger area**

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityDisableById** action to work. The **Collision** component is required for the **EntitySubscribeTriggerLeave** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

<figure><img src="/files/7rTcMhn8l1AQ1sJwshLz" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntityDisableById* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerLeave**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityDisableById.**

E. Add an object that will be disabled when the avatar leaves the trigger area. The **golden\_coin** entity has been added to the **pick up specify execution entity**. The **golden\_coin** entity has also been disabled by default.

<figure><img src="/files/e3Wtn6jCueWywuarMrIA" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntityEnableByTag

This guide provides instructions for setting up the **EntityEnableByTag** action. In the sample app, once the avatar enters the trigger area, multiple balls are enabled.

| <img src="/files/m5xh82h7rLBJau8uPF4E" alt="" data-size="original">       | <img src="/files/ROJJg6ZGQhmZuv7HP8D6" alt="" data-size="original">  |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| When the avatar is outside the blue trigger area, the balls are disabled. | When the avatar enters the blue trigger area, the balls are enabled. |

In this example, a trigger is created and when triggered, an action enables multiple objects. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

#### **Create an action that enables multiple objects**

{% stepper %}
{% step %}
**Create a trigger area**

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityEnableByTag** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/k8HWU8nzEr63HWWNiSxm" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntityEnableByTag* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityEnableByTag.**

E. Create a unique **Tag** and add it to the **enable entity with tag** field. In this example, the **Balls** tag is added.

<figure><img src="/files/gqpZgvU6gK4gV0Phtb53" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add Tag to multiple objects**

A. Multiple objects can be enabled using the **EntityEnableByTag** action. Create multiple objects in the scene.

B. In the **Tags** field, add the same tag from the **enable entity with tag** field. Do this for all of the objects or parents of multiple objects.

<figure><img src="/files/nLZhvm0Utt76XHIdTa8s" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntityDisableByTag

This guide provides instructions for setting up the **EntityDisableByTag** action. In the sample app, once the avatar leaves the trigger area, multiple balls are disabled.

| <img src="/files/pWZnAZoZNYOYMgH52ibN" alt="" data-size="original">  | <img src="/files/nghbyxpLI85r8r6Ng28K" alt="" data-size="original">   |
| -------------------------------------------------------------------- | --------------------------------------------------------------------- |
| When the avatar enters the blue trigger area, the balls are enabled. | When the avatar leaves the blue trigger area, the balls are disabled. |

In this example, a trigger is created and when triggered, an action disables multiple objects. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

#### **Create an action that disables multiple objects**

{% stepper %}
{% step %}
**Create a trigger area**

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityDisableByTag** action to work. The **Collision** component is required for the **EntitySubscribeTriggerLeave** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

Click the **Edit Viverse Extension** button.

<figure><img src="/files/KEeasKJSA46kNQj1Ci1J" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntityDisableByTag* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerLeave**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityDiableByTag.**

E. Create a unique **Tag** and add it to the **enable entity with tag** field. In this example, the **Balls** tag is added.

<figure><img src="/files/b6TbXfDHRulsadnPiNxJ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add** *EntityEnableByTag* **to multiple objects**

A. Multiple objects can be enabled using the **EntityEnableByTag** action. Create multiple objects in the scene.

B. In the **Tags** field, add the same tag from the **enable entity with tag** field to all of the objects or parents of multiple objects.

<figure><img src="/files/R6Oo47u6wwIzPTpUfyc9" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntityFadeIn

This guide provides instructions for setting up the **EntityFadeIn** action. In the sample app, once the avatar starts colliding with the red platform, the red platform will fade in.

| <img src="/files/VRGg7lrW5rwJSTMMY2uQ" alt="" data-size="original">                             | <img src="/files/lCvgEivuJE2WkrPyL83r" alt="" data-size="original">                                             |
| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| The red platform has been faded out and is invisible, but the avatar can still collide with it. | Once the avatar begins colliding with the invisible red platform, the red platform fades in and is now visible. |

In this example, a trigger is created and when triggered, an action fades in the object it has been added to. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

#### **Create an action that fades in an object**

{% stepper %}
{% step %}
**Create the 3D object that will fade in**

A. Add a 3D object to the scene.

B. A **Collision** component is not required for the **EntityFadeIn** action to work. The **Collision** component is required for the **EntitySubscribeCollisionStart** trigger that will be used in this example.

C. A **Rigidbody** component is not required for the **EntityFadeIn** action to work. The **Rigidbody** component is required for the **EntitySubscribeCollisionStart** trigger that will be used in this example.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/MpJ2qZy2jQCxQoc2mRph" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntityFadeIn* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeCollisionStart**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityFadeIn**. To customize how long before the fade begins or duration of the fade, update **delay in ms** and **duration in ms** fields.

<figure><img src="/files/stYqsvano601fh4m3mjA" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntityFadeOut

This guide provides instructions for setting up the **EntityFadeOut** action. In the sample app, once the avatar stops colliding with the red platform, the red platform will fade out.

| <img src="/files/gl6i3Gxs6O1sDgAXbFCK" alt="" data-size="original"> | <img src="/files/aIdCSvgWsePMOtivXqJm" alt="" data-size="original">                         |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| The avatar is colliding with the red platform.                      | Once the avatar stops colliding with the red platform, the red platform begins to fade out. |

In this example, a trigger is created and when triggered, an action fades an object. Any object can be used as a trigger, as long as the object has a collision component.

#### Create an action that fades out an object

{% stepper %}
{% step %}
**Create the 3D object that will fade out**

A. Add a 3D object to the scene.

B. A **Collision** component is not required for the **EntityFadeOut** action to work. The **Collision** component is required for the **EntitySubscribeCollisionEnd** trigger that will be used in this example.

C. A **Rigidbody** component is not required for the **EntityFadeOut** action to work. The **Rigidbody** component is required for the **EntitySubscribeCollisionEnd** trigger that will be used in this example.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/NuhijiWL0fQD9vlRcXGJ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *EntityFadeOut* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeCollisionEnd**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityFadeOut**. To customize how long before the fade begins or duration of the fade, update **delay in ms** and **duration in ms** fields.

<figure><img src="/files/PS9TNORL2DrqArdPQBT0" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### EntityToggleEnabled

This guide provides instructions for setting up the **EntityToggleEnabled** action. In the sample app, every time the avatar enters the blue trigger area, a notification is sent to the purple box to toggle it’s enabled state.

<table data-header-hidden data-full-width="false"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><img src="/files/C8fPiJ2GIw4MkRc7Johg" alt="" data-size="original"></td><td><img src="/files/uOUY92ixnD0dGtCUa1pl" alt="" data-size="original"></td></tr><tr><td>The purple box is enabled/visible before the avatar enters the blue trigger area.</td><td>Once the avatar enters the blue trigger area, a notification is sent to the purple box to toggle it’s enabled stated. The purple box is disabled.</td></tr><tr><td><img src="/files/j8VM6rbYxFRXJtXBWef2" alt="" data-size="original"></td><td><img src="/files/NzYK0Nm0U3eIB6WfFNdH" alt="" data-size="original"></td></tr><tr><td>The avatar leaves the blue trigger area.</td><td>Once the avatar enters the blue trigger area again, a notification is sent to the purple box to toggle it’s enabled stated. The purple box is enabled again.</td></tr></tbody></table>

In this example, a trigger is created and when the avatar or other objects enter the trigger area, an action sends a notification to toggle another object's enabled state. Any object can be used as a trigger, as long as the object has a collision component.

#### **Create an action that toggles an object's enabled state**

{% stepper %}
{% step %}
**Create a trigger area**

A. Create a new **3D Box** entity.

B. A **Collision** component is not required for the **EntityToggleEnabled** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/FOc3bI6sD7nc01WFv2tr" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *NotificationCenterPublish* **action**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **NotificationCenterPublish.**

E. Create a unique name for the notification and add it to the **notification name to publish** field. In this example, the **ToggleEnabled** name is added.

<figure><img src="/files/1zP4i6XA9xDzpfUpExIG" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Create a 3D object that will have it's enabled state toggled**

A. Add a 3D object to the scene.

B. Click the **Edit Viverse Extension** button.

<figure><img src="/files/06ooBCx3r1ymDu4IoAl3" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Add the** *NotificationCenterSubscribe* **trigger**

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribe**.

C. In the **notification name to subscribe** field, add the same name from the **notification name to publish** field.

D. Add an **Action** and select **EntityToggleEnabled**.

<figure><img src="/files/Km4iVxUYGMXBSO6FjaG1" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Entity Collision Enabling & Disabling

This document provides several guides that can be used to enable and disable colliders in a VIVERSE project. These actions can be configured to execute when triggers are activated.

***

### EntityToggleCollision

This guide provides instructions for setting up the **EntityToggleCollision** action. In the sample app, every time the avatar enters the green trigger area, a notification is sent to the wall and the collider on the wall is toggled on/off. The wall is green when the avatar can pass through and red when the avatar can not pass through.

| <img src="/files/LhA4YV8bGrcSieLZzCAf" alt="" data-size="original">                                      | <img src="/files/8m4Bnh1KNRhcDFsZS4YB" alt="" data-size="original">                                                                                      |
| -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Before the avatar enters the green trigger area, the wall is red and the avatar can not pass through it. | Once the avatar enters the green trigger area, the collider on the wall is toggled to be disabled. The wall is green and the avatar can pass through it. |

In this example, a trigger is created and when the avatar enters the trigger area, an action sends a notification to toggle another object's collider on/off. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

#### **An action that toggles an object's collider on/off**

{% stepper %}
{% step %}
**Create the 3D object that will send the notification**

A. Add a 3D object to the scene.

B. The **Collision** component is not required for **EntityToggleCollision** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Click the **Edit Viverse Extension** button.

<figure><img src="/files/FLPh2Pg74oeht0CwDdwb" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the NotificationCenterPublish action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **NotificationCenterPublish**

E. Create a unique notification name and add it to the **notification name to publish** field. In this example, the notification is called **ToggleWall**.

<figure><img src="/files/hLxZNsiqgSnOCw2l5osd" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Create the 3D object that will receive the notification

A. Add a 3D object to the scene.

B. Add a **Collision** component.

C. Add a **Rigidbody** component.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/lshIJGGz8DoeEH6DuHSC" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityToggleCollision action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribe**.

C. The same text that was added to the **notification name to publish** needs to be added to the **notification name to subscribe**.

D. Add an **Action** and select **EntityToggleCollision.**

<figure><img src="/files/kH7ET3yK4zyxOzFuHcwx" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntityEnableCollision

**Create An Action That Enables An Object's Collider**

This guide provides instructions for setting up the **EntityEnableCollision** action. In the sample app, every time the avatar enters the red trigger area, a notification is sent to the wall object and the collider on the wall object is enabled. The wall is green when the avatar can pass through and red when the avatar can not pass through.

| <img src="/files/KsPRHCpbtdMJGve9iQ4H" alt="" data-size="original">               | <img src="/files/Ld3nWyovcCUzv9Jp4F05" alt="" data-size="original">                                       | <img src="/files/TwEQ9SQKtHjCzJ9mLtPR" alt="" data-size="original">                |
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| The avatar is able to pass through the wall when the wall’s collider is disabled. | Once the avatar enters the red trigger area, the EntityEnableCollision action enables the wall’s collider | The avatar is unable to pass through the wall once the wall’s collider is enabled. |

In this example, a trigger is created and when the avatar enters the trigger area, an action sends a notification to enable another object's collider. Any object can be used as a trigger, as long as the object has a collision component.&#x20;

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. Add a **Collision** component.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/BtGdXazdWYwo8Pr3fOCr" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the **EntitySubscribeTriggerEnter** trigger

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

E. Add an **Action** and select **NotificationCenterPublish.**

F. Create a unique notification name and add it to the **notification name to publish** field. In this example, the notification is called **EnableWall**.

<figure><img src="/files/7Y7hltDcNYctcPhqq1ak" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Create the object that will have it's collider enabled

A. Create a new **3D Box** entity.

B. Add a **Collision** component.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/TlGCXd43VamDT0bxwLv3" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityEnableCollision action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSusbscribe**.

C. The same text that was added to the **notification name to publish** needs to be added to the **notification name to subscribe**.

D. Add an **Action** and select **EntityEnableCollision.**

<figure><img src="/files/GZohvDK54K2fXBliVtqE" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntityDisableCollision

**Create An Action That Disables An Object's Collider**

This guide provides instructions for setting up the **EntityDisableCollision** action. In the sample app, every time the avatar enters the green trigger area, a notification is sent to the wall object and the collider on the wall object is disabled. The wall is green when the avatar can pass through and red when the avatar can not pass through.

| <img src="/files/5EQL2xKDGWQenuAySw0k" alt="" data-size="original">                | <img src="/files/1FJ2SW0FPlhy5bzplIvg" alt="" data-size="original">                    | <img src="/files/OWvQPbC6Z6CXqEgHpzuS" alt="" data-size="original">               |
| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| The avatar is unable to pass through the wall when the wall’s collider is enabled. | Once the avatar enters the green trigger area, the action disables the wall’s collider | The avatar is able to pass through the wall when the wall’s collider is disabled. |

In this example, a trigger is created and when the avatar enters the trigger area, an action sends a notification to disable another object's collider. Any object can be used as a trigger, as long as the object has a collision component.&#x20;

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. Add a **Collision** component.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/PaneXOCMbwKCo5GSWeua" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntitySubscribeTriggerEnter trigger

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

E. Add an **Action** and select **NotificationCenterPublish.**

F. Create a unique notification name and add it to the **notification name to publish** field. In this example, the notification is called **DisableWall**.

<figure><img src="/files/XeirmndTuQI7j1iYWomY" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Create the object that will have it's collider disabled

A. Create a new **3D Box** entity.

B. Add a **Collision** component.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/VzTvTZjOVo0bLfSwZH7d" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityDisableCollision action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribe**.

C. The same text that was added to the **notification name to publish** needs to be added to the **notification name to subscribe**.

D. Add an **Action** and select **EntityDisableCollision.**

<figure><img src="/files/JDqkQstIyg3x14vF6nxy" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Entity Physics

This document provides a guide that can be used to add a physics force to objects in VIVERSE project. The action can be configured to execute when a trigger is activated.

***

## EntityRigidbodyAddForceInPhysics

**Create An Action That Adds Force To An Object**

This guide provides instructions for setting up the **EntityRigidbodyAddForceInPhysics** action. In the sample app, once the avatar enters the blue trigger area, a force is applied to the sphere.

| <img src="/files/WCLEXo2VwooELkvVfFrA" alt="" data-size="original"> | <img src="/files/TGqC6YORYct8cFxvR0Vy" alt="" data-size="original">                                                 |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| The sphere is not moving before the avatar enters the trigger area. | Once the avatar enters the trigger area, a force is applied to the sphere in the direction based on the parameters. |

In this example, a trigger is created and when the avatar or other objects enters the trigger area, an action adds force to an object. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. This **Collision** component is not required for the **EntityRigidbodyAddForceInPhysics** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/5zXBJmZVvXgwnDSC0z9Z" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntitySubscribeTriggerEnter trigger

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **NotificationCenterPublish.**

E. Create a unique name for the notification and add it to the **notification name to publish** field. In this example, the **AddForce** name is added.

<figure><img src="/files/XAltr4Gyb4yzzCDVCgkk" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Create the 3D object that the physics force will be applied to

A. Create a new 3D object.

B. Add a **Collision** component.

C. Add a **Rigidbody** component.

D. Set the **Collision Type** to **Dynamic**.

E. Click the **Edit Viverse Extension** button.

<figure><img src="/files/JD9TAzAKUToBJKLfqrVQ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityRigidbodyAddForceInPhysics action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **NotificationCenterSubscribe**.

C. The same text that was added to the **notification name to publish** needs to be added to the **notification name to subscribe**.

D. Add an **Action** and select **EntityRigidbodyAddForceInPhysics.**

E. In the **X Force**, **Y Force** and **Z Force** fields, add values for the amount of force to apply in each direction.

<figure><img src="/files/IRZkK6Ou5BLvA88aL7q0" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Avatar Teleport & Checkpoint

This document provides guides that can be used to change the spawn location of an avatar and transfer an avatar to a different location. These actions can be configured to execute with triggers.

***

## EntityCheckPoint

**Create An Action That Sets A New Spawn Point**

This guide provides instructions for setting up the **EntityCheckPoint** action. In the sample app, once the avatar enters one of the green trigger areas, the area becomes the new spawn location.

| <img src="/files/lOqpjzrl4KRSYzwEZnG8" alt="" data-size="original">                           | <img src="/files/wlzdct0dEL6s5B3ngHOX" alt="" data-size="original"> | <img src="/files/0tYI5DQ9n3AR1S5I2h7g" alt="" data-size="original"> |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
| The avatar enters the green trigger area and the trigger area becomes the new spawn location. | The avatar has fallen off the map and needs to be respawned.        | When the avatar respawns, it is spawned at the new location.        |

In this example, a trigger area is created and when triggered, an action sets a new spawn location. Any object can be used as a trigger, as long as the object has a collision component.&#x20;

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **Sphere** entity.

B. The **Collision** component is not required for **EntityCheckPoint** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/EBXUqK6byY2VnTzMvWWS" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityCheckPoint action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityCheckPoint.**

E. Add an entity that has a position that will be used for the new spawn location. **SpawnLocation1** has been added to the **pick up an entity id**.

<figure><img src="/files/b64viewY5sO09cr9cPN5" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## TeleportAvatar

**Create An Action That Teleports An Object To A Specific Location**

This guide provides instructions for setting up the **TeleportAvatar** action. In the sample app, once the avatar enters one of the green cylinder trigger areas, the avatar is teleported to another location.

| <img src="/files/k3cUUtzNG33c7Ox4GaYI" alt="" data-size="original"> | <img src="/files/JhLwiDErnsTmnEVthJKo" alt="" data-size="original">                  |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| The avatar enters the green trigger area.                           | After entering the green trigger area, the avatar is teleported to another location. |

In this example, a trigger is created and when the avatar or other objects enter the trigger area, an action teleports the avatar to a specific location. Any object can be used as a trigger, as long as the object has a collision component.&#x20;

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **Sphere** entity.

B. The **Collision** component is not required for **TeleportAvatar** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/yekPA3QJ3vZiDIaCR79x" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the TeleportAvatar action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityTeleportAvatar.**

E. Add an entity that has a position that will be used for the teleport location. **TeleportLocation2** has been added to the **Specify the Entity whose location you want to teleport to**.

<figure><img src="/files/vbq8wV0d5oL6tJSr0ipW" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Animation & Sound

This document provides several guides that can be used to setup event listeners for animations, control animations and control audio in a VIVERSE project.

## EntityPlayAnimation

**Create An Action That Plays An Animation**

This guide provides instructions for setting up the **EntityPlayAnimation** action. In the sample app, once the avatar enters the trigger area, the character switches to the dancing animation state. When the avatar leaves the trigger area, the character switches back to the idle animation state.

| <img src="/files/x6h3rNS9Mpc1QtN7jKbC" alt="" data-size="original">                | <img src="/files/BT0BnZTtlTDggcNOUtTN" alt="" data-size="original">                            |
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| The character is in the idle state when the avatar is outside of the trigger area. | The character switches to the dancing animation state when the avatar enters the trigger area. |

In this example, a trigger area is created and when triggered, an action plays an animation. Any object can be used as a trigger, as long as the object has a collision component.&#x20;

{% stepper %}
{% step %}

### Create the animation state graph

A. Create an **Animation State Graph** and rename the initial state to **Idle**.

B. Create an additional animation state and name it **Dancing**.

<figure><img src="/files/TRe3uR62n1XX5R7ekRyy" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Configure 3D model for animation

A. Add a 3D model to the scene.

B. Add an **Anim** component.

C. Add the **Animation State Graph**.

D. Add the **Idle** animation.

E. Add the **Dancing** animation.

<figure><img src="/files/oFAltoGFz8GTNQOcLqvS" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityPlayAnimation** to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/65h5VUJlAo3d1rj0ECzK" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityPlayAnimation action for the dancing animation state

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityPlayAnimation.**

E. Add the name of an animation state to the **animate state to play** field. In this example, the animation state name is **Dancing**.

F. Add the 3d model to the **pick up specify execution entity** field.

<figure><img src="/files/8TvmFRAqZMWwz2TGPjWv" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityPlayAnimation action for the Idle animation state

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerLeave**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityPlayAnimation.**

E. Add the name of an animation state to the **animate state to play** field. In this example, the animation state name is **Idle**.

F. Add the 3d model to the **pick up specify execution entity** field.

<figure><img src="/files/dYABjUifk8t2S4ELHhUf" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntitySubscribeAnimationStart

**Create A Trigger Based On When An Animation Starts**

This guide provides instructions for setting up the **EntitySubscribeAnimationStart** trigger. In the sample app, when the avatar enters the blue trigger area, the character’s dancing animation begins to play. The dancing animation has an animation event added and the animation event is triggered whenever the animation starts to play.

| <img src="/files/2giJXrDTHh57xzxR3of4" alt="" data-size="original"> | <img src="/files/N04RSOZNev4EH6PyqoLg" alt="" data-size="original">                                                                                   |
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Before the character’s dancing animation begins.                    | When the character’s dancing animation begins, the animation event is triggered and text is displayed to show that the animation event was triggered. |

In the **EntityPlayAnimation** example, a 3D model was added, an animation state graph was created, animation states were created and a trigger area was created to initiate the animations. In this example, an animation event is created for the start of the animation.

{% stepper %}
{% step %}

### Open animation file that will have the animation event

A. Click on the animation file to open up the properties.

<figure><img src="/files/KUZNEWWX2TJMPf5JezaQ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Create animation event

A. Create an event, add value **0** to the **time** field and **start** to the **name** field.

<figure><img src="/files/c8rcZcmGowtoTldmKeMJ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntitySubscribeAnimationStart trigger

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeAnimationStart**.

C. Add an **Action** and select **EntityEnableById.**

D. For the **EntityEnableById** action to work, an object needs to be added that will be enabled. In this example, the **AnimationStartText** object is added.

<figure><img src="/files/szg82ebWvOSSxSb8xjKp" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntitySubscribeAnimationEnd

**Create A Trigger Based On When An Animation Ends**

This guide provides instructions for setting up the **EntitySubscribeAnimationEnd** trigger. In the sample app, when the avatar enters the blue trigger area, the character’s dancing animation begins to play. The dancing animation has an animation event added and the animation event is triggered whenever the animation stops playing.

| <img src="/files/st0zNbgDrMI2Ycl9HRdX" alt="" data-size="original"> | <img src="/files/StEssyCiWKop8p6nwjXx" alt="" data-size="original">                                                                                 |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| The character’s dancing animation has started.                      | When the character’s dancing animation ends, the animation event is triggered and text is displayed to show that the animation event was triggered. |

In the EntityPlayAnimation example, a 3D model was added, an animation state graph was created, animation states were created and a trigger area was created to initiate the animations. In this example, an animation event is created for the end of the animation.

{% stepper %}
{% step %}

### Open animation file that will have the animation event

A. Click on the animation file to open up the properties.

<figure><img src="/files/mmSWGYQwnCBkgR6UfwY2" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Create animation event

A. Create an event, add value **8** to the **time** field and **end** to the **name** field. The value 8 is used because the dancing animation has a duration of 8.83 seconds.

<figure><img src="/files/6M92huwe4lC5sGBKfeXk" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntitySubscribeAnimationEnd trigger &#x20;

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeAnimationEnd**.

C. Add an **Action** and select **EntityEnableById.**

D. For the **EntityEnableById** action to work, an object needs to be added that will be enabled. In this example, the **AnimationEndText** object is added.

<figure><img src="/files/PKOfdxdN0Ir9mKnawVVg" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntitySubscribeAnimationEvent

**Create A Trigger Based On A Specific Time During An Animation**

This guide provides instructions for setting up the **EntitySubscribeAnimationEvent** trigger. In the sample app, when the avatar enters the blue trigger area, the character’s dancing animation begins to play. The dancing animation has an animation event added and the animation event is triggered during the animation.

| <img src="/files/tsfPURadZHPQ8pZFAvBh" alt="" data-size="original"> | <img src="/files/Ge0sSdM6kt2Qi0HdnGcN" alt="" data-size="original">                                                                              |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| The character’s dancing animation has started.                      | During the character’s dancing animation, the animation event is triggered and text is displayed to show that the animation event was triggered. |

In the EntityPlayAnimation example, a 3D model was added, an animation state graph was created, animation states were created and a trigger area was created to initiate the animations. In this example, an animation event is created for during the animation.

{% stepper %}
{% step %}

### Open animation file that will have the animation event

A. Click on the animation file to open up the properties.

<figure><img src="/files/ZwVk28yKsV8txtC04SmG" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Create animation event

A. Create an event, add value **4** to the **time** field and create a unique name for the event. In this example, the event name **animate** was added to the **name** field.

<figure><img src="/files/Mza95YmP0Q3V36WoQjbs" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntitySubscribeAnimationEvent trigger

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeAnimationEvent**.

C. Add the event name to the **event name to subscribe**.

D. Add an **Action** and select **EntityEnableById.**

E. For the **EntityEnableById** action to work, an object needs to be added that will be enabled. In this example, the **AnimationEventText** object is added.

<figure><img src="/files/YgPtDpNok5tmgUQsJIfl" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntityPlaySound

**Create An Action That Plays A Sound**

This guide provides instructions for setting up the EntityPlaySound action. In the sample app, once the avatar enters the trigger area, a sound plays.

| <img src="/files/AE20akvgaiydyFSYBBKB" alt="" data-size="original">         | <img src="/files/l4cA7Y0vd14fSxJ7iu0d" alt="" data-size="original"> |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| The avatar is outside of the blue trigger area and the sound does not play. | The avatar enters the blue trigger area and the sound plays.        |

In this example, a trigger is created and when triggered, an action plays a sound. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityPlaySound** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Add a **Sound** component.

E. Create a unique name for the sound file and add it to the **name** field. In this example, **Slot2** is added.

F. Add the audio file to the **Asset** field.

G. Click the **Edit Viverse Extension** button.

<figure><img src="/files/bX76l82e3ZQJvj3f23F0" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityPlaySound action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityPlaySound.**

E. In the **sound name to play** field, add the same name that was created on the **Sound** component.

F. Add the entity with the **Sound** component to the **pick up specify execution entity**.

<figure><img src="/files/WaNcluV8ChOZb8iGBgXJ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntityStopSound

**Create An Action That Stops Playing A Sound**

This guide provides instructions for setting up the **EntityStopSound** action. In the sample app, once the avatar leaves the trigger area, the sound stops playing.

| <img src="/files/B0xnTH2PTIdP8QrWvVpK" alt="" data-size="original"> | <img src="/files/rKr5NQ4owtazyQd9fVOI" alt="" data-size="original">         |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| The avatar enters the blue trigger area and the sound plays.        | The avatar is outside of the blue trigger area and the sound stops playing. |

In this example, a trigger is created and when triggered, an action plays a sound. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityStopSound** action to work. The **Collision** component is required for the **EntitySubscribeTriggerLeave** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Add a **Sound** component.

E. Create a unique name for the sound file and add it to the **name** field. In this example, **Slot2** is added.

F. Add the audio file to the **Asset** field.

G. Click the **Edit Viverse Extension** button.

<figure><img src="/files/BMufnBpfn3uJb7CvztOj" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityStopSound action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerLeave**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityStopSound.**

E. In the **sound name to play** field, add the same name that was created on the **Sound** component.

F. Add the entity with the **Sound** component to the **pick up specify execution entity**.

<figure><img src="/files/eAYHyBsgcEcJfqJeitbb" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Asset Management

This document provides several guides that can be used to optimize VIVERSE projects by controlling when assets are rendered.

***

## EntityAssetUnload

**Create An Action That Unloads An Asset From The Scene, Stops The Asset From Rendering And Retains The Asset In Memory**

This guide provides instructions for setting up the **EntityAssetUnload** action. In the sample app, the trigger area is outlined in blue. Once the avatar enters into the trigger area, the asset stops rendering in the scene. This will decrease draw calls and improve frames per second, but the asset will be retained in memory.&#x20;

| <img src="/files/qJqb6H5pdjAEB42z7AMs" alt="" data-size="original">                  | <img src="/files/msXOa8YWqT3JKiJcdjv4" alt="" data-size="original">                                   |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| When the avatar is outside the blue trigger area, the house asset is being rendered. | When the avatar enters the blue trigger area, the house asset is unloaded, but is retained in memory. |

In this example, a trigger is created and when triggered, an action unloads an object. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityAssetUnload** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/02n8ZdEJRSPvqSxgf8wJ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityAssetUnload action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityAssetUnload.**

E. Add an object that will be unloaded when the avatar enters the trigger area. The **House2** entity has been added to the **pick up specify execution entity**.

<figure><img src="/files/LceDwCXWaqjr6i0goQLz" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntityAssetReload

**Create An Action That Reloads An Asset Into The Scene From Memory And Causes The Asset To Be Rendered**

This guide provides instructions for setting up the **EntityAssetReload** action. In the sample app, the trigger area is outlined in blue. Once the avatar leaves the trigger area, the asset is reloaded from memory and starts rendering in the scene.

| <img src="/files/SAfTZxDIfIUfomyzxyTk" alt="" data-size="original">                                   | <img src="/files/0F9jUOqp0H9suqrAb5A1" alt="" data-size="original">                    |
| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| When the avatar enters the blue trigger area, the house asset is unloaded, but is retained in memory. | When the avatar leaves the blue trigger area, the house asset is reloaded from memory. |

In this example, a trigger is created and when triggered, an action reloads an object. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityAssetReload** action to work. The **Collision** component is required for the **EntitySubscribeTriggerLeave** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/S0NAcw710wPznvC3lmH2" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityAssetReload action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerLeave**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityAssetReload.**

E. Add an object that will be unloaded when the avatar enters the trigger area. The **House2** entity has been added to the **pick up specify execution entity**.

<figure><img src="/files/zb2g4JMrogeyJ1CCNyyP" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## EntityDestroy

**Create An Action That Destroys An Asset, Removes It From Memory and Stops It From Rendering**

This guide provides instructions for setting up the **EntityDestroy** action. In the sample app, the trigger area is outlined in blue. Once the avatar enters the trigger area, the asset is destroyed, which removes it from memory and stops it from rendering.

| <img src="/files/WX1yDJxzLOvbSqsVh6YE" alt="" data-size="original">                  | <img src="/files/jSe3jmrZ6uf0ZQkLZlTr" alt="" data-size="original">                                 |
| ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| When the avatar is outside the blue trigger area, the house asset is being rendered. | When the avatar enters the blue trigger area, the house asset is destroyed and removed from memory. |

In this example, a trigger is created and when triggered, an action destroys an object. Any object can be used as a trigger, as long as the object has a collision component. This example uses a 3D box as the trigger area.

{% stepper %}
{% step %}

### Create a trigger area

A. Create a new **3D Box** entity.

B. The **Collision** component is not required for **EntityDestroy** action to work. The **Collision** component is required for the **EntitySubscribeTriggerEnter** trigger that will be used in this example.

C. Adding a material is optional. A transparent material has been added so that the trigger area is visible in play mode.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/9HVrsqtx0PLM81IsaTXK" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the EntityDestroy action

A. In the VIVERSE extension, select the **TriggerAndAction** plugin for the **Select plugins** dropdown.

B. Add a **Trigger** and select **EntitySubscribeTriggerEnter**.

C. Add **local-player** to the **tags to filter** field.

D. Add an **Action** and select **EntityDestroy.**

E. Add an object that will be destroyed when the avatar enters the trigger area. The **House2** entity has been added to the **pick up specify execution entity**.

<figure><img src="/files/5BuSFtOB86q4jGhQbu8A" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Media

Explore all common types of Media supported by the PlayCanvas Toolkit.

***

{% columns %}
{% column %}
{% content-ref url="/pages/gksrTSDZX2JeBQ4Fhanc" %}
[Polygon Streaming](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/media/polygon-streaming)
{% endcontent-ref %}

{% content-ref url="/pages/K6Xw3zXw15k4Y3i61wNI" %}
[Video](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/media/video)
{% endcontent-ref %}
{% endcolumn %}

{% column %}
{% content-ref url="/pages/9pjqZuQP7CVtb6XBMGlx" %}
[Images](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/media/images)
{% endcontent-ref %}

{% content-ref url="/pages/wgoZ7ZrRS2wnNSJyyBDZ" %}
[Audio](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/no-code-tools/media/audio)
{% endcontent-ref %}
{% endcolumn %}
{% endcolumns %}


# Polygon Streaming

This document provides a guide that can be used to setup Polygon Streaming in a VIVERSE project.

***

### Media Polygon Streaming

#### Streaming 3D Models into The Environment

{% hint style="info" %}
For Polygon Streaming using the PlayCanvas Extension, you must use the Polygon Streaming Asset ID url, not the link url.
{% endhint %}

| <img src="/files/SWGy68E122YLWX7p5Plh" alt="" data-size="original">                  | <img src="/files/RuZB2PihD2RYg6nS0l13" alt="" data-size="original"> |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
| The 3D models are visible in the PlayCanvas editor when utilizing Polygon Streaming. | The 3D model streaming into the environment with the avatar.        |

{% stepper %}
{% step %}

#### Create the entity that will stream in the 3D model

A. Create a new entity.

B. Change the size of the model using the **Scale**.

C. Click the **Edit Viverse Extension** button.

<figure><img src="/files/SYkaSvWPLC162cl9aF23" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add the PolygonStreaming module

A. In the VIVERSE extension, select the **Media** plugin for the **Select plugins** dropdown.

B. Select the **PolygonStreaming** module and add it.

C. Add the **polygon streaming url.** The URL should be in the following format: **<https://stream-stage.viverse.com/polygon\\_file/b9e62012-11e5-49cb-8ede-de596eec537e/aee7496b-b459-4e5d-87fe-2658955eb4f0/model.xrg>**

<figure><img src="/files/HA3qXGTTc6xmdIpRuHq8" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Images

This document provides a guide that can be used to setup images and extend the functionality of images in a VIVERSE project.

***

### Media Images

#### Adding Images And Extending Image Functionality

<figure><img src="/files/dRwZUjAUwbRDf8U7khS6" alt="" width="375"><figcaption></figcaption></figure>

An image has been added to the scene and the image has been configured to always face the avatar

{% stepper %}
{% step %}

#### Setup image

A. Create a new **Image Element**.

B. Add the image to the **Texture** field.

C. Resize the image using the **Width** and **Height**.

D. Click the **Edit Viverse Extension** button.

<figure><img src="/files/AtbDAtp2YHHs6Amy6DMi" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add the Image module

A. In the VIVERSE extension, select the **Media** plugin for the **Select plugins** dropdown.

B. Select the **Image** module and add it.

C. Place a checkmark by the **billboard** property to have the image always facing the avatar.

<figure><img src="/files/uPDsDjuxCQh5SThjDFMf" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Video

This document provides a guide that can be used to setup videos and extend the functionality of videos in a VIVERSE project.

***

### Media Video

#### Adding Video And Extending Video Functionality

| <img src="/files/Y3FzVRscfS7Xg4104D0W" alt="" data-size="original">     | <img src="/files/hre4NQJF1BR9mgvv1uut" alt="" data-size="original"> |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------- |
| The user uses the mouse to hover over the video to access the controls. | The video is playing.                                               |

{% stepper %}
{% step %}

#### Setup video

A. Create a new entity

B. Resize the video using the **Scale**.

C. Click the **Edit Viverse Extension** button.

<figure><img src="/files/2gE4mLXre2UERFrTlrI1" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add the video module

A. In the VIVERSE extension, select the **Media** plugin for the **Select plugins** dropdown.

B. Select the **Video** module and add it, then select either "Asset" for files contained in your PlayCanvas project, or "URL" for a video to stream, such as a YouTube URL (but please note: YouTube or other embeds are run inside iframe elements, which do not render in VR). For this example, choose "Asset."

C. Add the video to the **Asset** field by selecting it within your project.

D. Uncheck the **auto play** property to prevent the video from starting when the avatar enters the environment.

<figure><img src="/files/A7xUOmjG4Sh0yALiPOxs" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Audio

This document provides a guide that can be used to setup audio and extend the functionality of audio in a VIVERSE project.

***

### Media Audio

#### **Adding Audio and Extending Audio Functionality**

<figure><img src="/files/IJymB0ujFxcs0gfj0DTG" alt="" width="375"><figcaption></figcaption></figure>

An audio clip has been added to the scene.

{% stepper %}
{% step %}

#### Setup audio

A. Create a new entity.

B. Click the **Edit Viverse Extension** button.

<figure><img src="/files/U7b548eON4dAKh5L8FO5" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add the audio module

A. In the VIVERSE extension, select the **Media** plugin for the **Select plugins** dropdown.

B. Select the **Audio** module and add it.

C. Add the audio to the **Asset** field.

D. Uncheck the **auto play** property to prevent the audio from starting when the avatar enters the environment.

<figure><img src="/files/mS41sKRGewftRBJIrgIq" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Pick and Throw

This document provides a guide that can be used to setup an entity that can be picked up and threw out. Users in the World can pick the object with "G", Throw it with "T", and put it down with "H".

***

### Pick and Throw

This guide provides instructions for setting up the pick and throw object your Scene.

{% stepper %}
{% step %}

#### Create an entity that you wish to implement pick and throw.

A. Create an new entity

B. Setup collision component

C. Setup render component

D. Setup rigidbody and select "Dynamic" in Type

<figure><img src="/files/7etDyNEZHLsn1wGU93w5" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Setup Pick and Throw mechanism

A. Select "PickAndThrow" in Select plugins

B. click "+" button

C. adjust throwing force level

<figure><img src="/files/VYEZKxNmoQtVTPBRZoVo" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Networked

This page details the usage of the networked component on individual entities in PlayCanvas.

***

### Overview

The Networked plugin allows local updates to certain properties of an entity's components to be sent to other connected avatars.

<figure><img src="/files/X1s698YX7f84NGgg6AMM" alt="" width="375"><figcaption></figcaption></figure>

#### Transform

By adding the Transform component, an entity's position and rotation will be networked across clients.

{% hint style="warning" %}
At this time, Transform does not network the Scale property of the entity.
{% endhint %}

#### Anim

By adding the Anim component, an entity's animation state will be networked across clients.

### Networking Example

In this video, we have created an arena with a floor, four walls and a ball. The networking module has been added to the ball. After publishing and creating the world in VIVERSE, multiple players can join in the environment. The location of the ball is tracked across all player sessions.

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F4pMiThqqrBzfvP8uy5am%2Fuploads%2FxbEue0WzcXozheoJtWwi%2F2025-04-11%2013-07-02%20(online-video-cutter.com).mp4?alt=media&token=b0d3e416-7d21-4694-8610-a2b01b3951df>" %}

To create the arena and ball in the video, you can follow the [Create Your First PlayCanvas Project](/playcanvas-toolkit/tutorials/create-your-first-playcanvas-project) tutorial. The instructions below can be used to add Networking functionality to the ball or any other entity of your choosing.

{% stepper %}
{% step %}

#### Create the entity

A. We have already created the **Ball** entity in the tutorial linked above. Here's a screenshot of the  **Ball** entity.

<figure><img src="/files/DzLDlko2M85LnYG9TBEM" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add the Networking module

A. In the VIVERSE extension, select the **Networking** plugin for the **Select plugins** dropdown.

B. Change the dropdown to **Transform** in the **Select a module and add** field. Click the plus sign.

C. Confirm the **enabled** checkbox is checked.

<figure><img src="/files/b6HdpQa6GzDIkwm0vcG4" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Publish and create world

A. To test the project, publish to VIVERSE and create the world.

<figure><img src="/files/dFB3roeYiQhQPeHrpxYp" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Share and play

A. Share the world link with another player and have them join the environment. We the other player interacts with the ball, you should be able to see the ball movement in your session. When you move the ball, the other player should be able to see the ball movement in their session.

<figure><img src="/files/ar1VV7KtAn4LwUoudyGl" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Seat

This document provides a guide that can be used to add a seat to objects in VIVERSE project. This allows the avatar to sit down.

***

### Seat

#### Add functionality to allow avatar to sit down

| <img src="/files/bwVYOqwEWMYcdyuVcdmg" alt="" data-size="original">                                                         | <img src="/files/BC5nBsesxudmX35UhWcp" alt="" data-size="original">                                                       |
| --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| When the avatar enters the **SeatHintFarAwayTriggerSphere**, the **SeatHintFarAway** icon (white dot) shows above the seat. | When the avatar enters the **SeatHintTriggerSphere**, the **SeatHint** button (sitting person icon) shows above the seat. |
| <img src="/files/R1c6czvfP4tv5TMcmEkl" alt="" data-size="original">                                                         |                                                                                                                           |
| When the avatar clicks on the **SeatHint** button, the avatar sits down.                                                    |                                                                                                                           |

{% stepper %}
{% step %}

#### Add the chair&#x20;

A. In this example, the chairs and table have been created in the scene under a single entity.

B. Click the **Edit Viverse Extension** button.

<figure><img src="/files/SRWX8Msve93dsyDUFQZE" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Add the Seat module

A. In the VIVERSE extension, select the **Seat** plugin for the **Select plugins** dropdown.

B. Select the **Seat** module and add it.

C. Add a value to the **number of seats** field.

<figure><img src="/files/MRaOd106opDv1IccKL8V" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Adjust the sitting boundary

A. The **Seat** is automatically generated.

B. Adjust the **Radius** on the **Collision** component to modify the sitting boundary.

<figure><img src="/files/mPBRoJoY2kzfylLf4RAa" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### View the SeatHintFarAway icon

A. The **SeatHintFarAway** icon is a white dot that is automatically generated and hovers above each seat.

<figure><img src="/files/ua9aksYnlhA8W66CqNdB" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Adjust the boundary for the SeatHintFarAway icon

A. The **SeatHintFarAwayTriggerSphere** is automatically generated.

B. Adjust the **Radius** on the **Collision** component to modify the distance away before the **SeatHintFarAway** icon displays. Also ensure the height of the collider is above ground level, so the player capsule collider is sure to intersect with it.

<figure><img src="/files/5x8PLEG8jZjPT3i9o3Il" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### View the SeatHint button

A. The **SeatHint** button has an icon of a sitting person. The button is automatically generated and hovers above each seat. When clicked, the avatar will sit.

<figure><img src="/files/fALxpnDUc3sQI7xDUjNk" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Adjust the boundary for the SeatHint button

A. The **SeatHintTriggerSphere** is automatically generated.

B. Adjust the **Radius** on the **Collision** component to modify the distance away before the **SeatHint** icon displays.

<figure><img src="/files/czRO9Pulz7Ob6WdflYLg" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Post Effect

This document provides a guide that can be used to setup post effect in a VIVERSE project.

***

## Post Effect

This guide provides instructions for setting up the post effect to your Scene. Currently, there are 11 different effects you can choose from.&#x20;

{% stepper %}
{% step %}

### To configure the Post effects, click on the **VIVERSE Scene Settings** button.

<figure><img src="/files/eIqZ15OInXRRwcK28BuU" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### In the **VIVERSE Scene Settings** window, expand the **Post Effect** section.

A. Select the effect in dropdown.

B. Click the **plus sign** beside the **Add quest** field.

C. Adjust brightness and contrast settings.

<figure><img src="/files/HExcABC7869ChrBLcyE1" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# GPU Mesh Instancing

The document provides a guide for setting up a 3D model for GPU Mesh Instancing.

***

**GPU Mesh Instancing** is a method of leveraging a GPU to render duplicated instances of a mesh with the same material in a single draw call. This is useful for optimizing draw calls when populating a scene with multiple trees, bushes, grass or other duplicated objects.

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F4pMiThqqrBzfvP8uy5am%2Fuploads%2F0sGbGGScrHUR7WdP8uJk%2F2025-04-15%2013-31-18.mp4?alt=media&token=f70862fe-9a06-4473-9e46-6687403d8831>" %}

In this example, a 3D model of grass is created in Blender and then exported into PlayCanvas where it's used for GPU Mesh Instancing. You can download the **Grass\_Grp.glb** file below for testing. The model has already been exported from Blender and is ready for importing into PlayCanvas.

{% file src="/files/ahxBJSks6yQDz9VOCWJR" %}

{% stepper %}
{% step %}

### Prepare the model

A. In a 3D modeling application, such as Blender, create an **Empty**. Give the **Empty** a name. In this example, the **Empty** has been given the name **Grass\_Grp**.

B. Duplicate the instances of the object so that every object uses the same mesh and material. All of the instances need to parented under the **Empty**.

<figure><img src="/files/tGPk2cPAJXbkAJztaH3V" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Export the model

A. Export the model as a **GLTF** **(.glft/.glb)** file. Give the file the same name as the name given to the **Empty.** In this example, the file is named **Grass\_grp.glb**.

B. Expand **Scene Graph** and place checkmark by **GPU Instances**.

<figure><img src="/files/C9Ok8wmxEi631lD0s0Le" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Confirm exported model is configured correctly

A. Visit the site <https://modelviewer.dev/editor/>

B. Drag the 3d model file into the browser and click on the **magnifying glass** icon to view the data.

C. Confirm the **EXT\_mesh\_gpu\_instancing** extension was added. If **EXT\_mesh\_gpu\_instancing** is not visible, then the model was not configured properly in the 3D modeling application. Retry steps 1-2.

<figure><img src="/files/h6TeehLCEjHU25YrYyAn" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Import the model into PlayCanvas

A. Add the model to PlayCanvas by dragging the **Grass\_grp.glb** file to the **Assets** window. The model will unpackage itself, producing multiple files.

B. Drag the **Grass\_grp Template** file to the **Hierarchy**.

C. Click the **Edit Viverse Extension** button.

<figure><img src="/files/LaZJ56V46pZRJxA53fYJ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the **GPUMeshInstancing** module

A. In the VIVERSE extension, select the **GPUMeshInstancing** plugin for the **Select plugins** dropdown.

B. Add the **Grass\_Grp.glb Container** to the **GLB container asset id** field.

<figure><img src="/files/PSqT4kRlJb9qhLKh5j9G" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Test project in VIVERSE

A. Publish the project to VIVERSE to see the GPU mesh instancing results.

<figure><img src="/files/grtHkserUUo2XtnigsMB" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Custom Code

The page introduces the basic information about implementing custom code with PlayCanvas in VIVERSE.

***

### Fundamentals

VIVERSE allows developers to use nearly any of the custom scripting interfaces [provided by PlayCanvas](https://developer.playcanvas.com/user-manual/scripting/), including but not limited to WebRequests like http & websockets, GLSL shaders, and 3rd party libraries imported directly into your project's hierarchy.&#x20;

However, by default, VIVERSE's PlayCanvas SDK does automatically handles avatar transform and audio networking. For customizing any features related to the VIVERSE avatar, we provide an additional API reference (see VIVERSE SDK APIs below).

### VIVERSE SDK APIs

The documentation for VIVERSE's SDK APIs is currently housed here: <https://viveportsoftware.github.io/pc-lib/>. We provide interfaces for customizing the default VIVERSE Cameras, Player, Network, and Environment, as well as the unique attributes when in an XR/immersive session in a VR headset.

These interfaces are automatically injected when you activate the VIVERSE Chrome Extension; The folder @viverse is added to your asset folder, which contains all interfaces in the create-sdk.mjs file. All interfaces can be imported and manipulated in your custom scripts.

{% hint style="warning" %}
For accessing the APIs in the VIVERSE SDK, you must use .mjs file types, which support import and export statements.
{% endhint %}

### A Note on No-Code Tools

In addition to adding the create-sdk.mjs file on initialization, the VIVERSE Chrome Extension adds create-extensions-sdk.mjs, which contains all of the interfaces between the no-code tools and PlayCanvas.

Within this file, you can see that each no-code function is assigned a unique trigger string, such as 'trigger:300006' for EntitySubscribeTriggerEnter. This is useful, because you can arbitrarily listen for and fire these exact same event names in custom scripts, connecting together the VIVERSE No-Code Functions and your custom code.

### Read More

{% content-ref url="/pages/hZjwUZio5knMkojfXFSm" %}
[Connecting No-Code Events to Custom Scripts](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/custom-code/connecting-no-code-events-to-custom-scripts)
{% endcontent-ref %}

{% content-ref url="/pages/USTQQvOPMnFnugqxxs5F" %}
[Introduction to MJS](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/custom-code/introduction-to-mjs)
{% endcontent-ref %}

{% content-ref url="/pages/CKZzSprklzt9bF4DOmdu" %}
[Camera Management: Settings and Switching](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/custom-code/camera-management-settings-and-switching)
{% endcontent-ref %}

{% content-ref url="/pages/0x91ZsXGiNDhHKoIbUlF" %}
[Custom Loading Screens](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/custom-code/custom-loading-screens)
{% endcontent-ref %}

{% content-ref url="/pages/nkxEhsOkbamW7NsVtl7I" %}
[Change Avatars Programatically](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/custom-code/change-avatars-programatically)
{% endcontent-ref %}


# Connecting No-Code Events to Custom Scripts

This document provides a guide for setting up no-code events in custom scripts.

***

The VIVERSE no-code events can be accessed through custom scripts. The events can be found in the root directory of your PlayCanvas project in the **@viverse/create-extensions-sdk.mjs** file. Here's a look at the file.

```javascript
export const TriggerTypes = {
  Base: 'trigger:0',
  Echo: 'trigger:1',
  PCAppEventSubscribe: 'trigger:100001',
  NotificationCenterSubscribe: 'trigger:200001',
  NotificationCenterSubscribeEntityPicking: 'trigger:200002',
  TheatreJSSubscribe: 'trigger:210001',
  TheatreJSSubscribeSheetEnd: 'trigger:210002',
  EntitySubscribeAnimationEvent: 'trigger:300001',
  EntitySubscribeAnimationStart: 'trigger:300002',
  EntitySubscribeAnimationEnd: 'trigger:300003',
  EntitySubscribeCollisionStart: 'trigger:300004',
  EntitySubscribeCollisionEnd: 'trigger:300005',
  EntitySubscribeTriggerEnter: 'trigger:300006',
  EntitySubscribeTriggerLeave: 'trigger:300007',
  SitInSeat: 'trigger:300008',
  SharePhoto: 'trigger:400001',
  EnterAnyWorld: 'trigger:400002',
  EnterMyWorld: 'trigger:400003'
}

export const ActionTypes = {
  Base: 'action:0',
  Echo: 'action:1',
  PCAppEventPublish: 'action:100001',
  NotificationCenterPublish: 'action:200001',
  TheatreJSPublish: 'action:210001',
  TheatreJSPlaySheet: 'action:210002',
  EntityRigidbodyAddForceInPhysics: 'action:300001',
  EntityPlayAnimation: 'action:300002',
  EntityEnable: 'action:300003',
  EntityDisable: 'action:300004',
  EntityToggleEnabled: 'action:300005',
  EntityFadeIn: 'action:300006',
  EntityFadeOut: 'action:300007',
  EntityPlaySound: 'action:300008',
  EntityEnableCollision: 'action:300009',
  EntityDisableCollision: 'action:300010',
  EntityToggleCollision: 'action:300011',
  InworldNpcWaitingSpeak: 'action:300012',
  InworldNpcStopWaitingSpeak: 'action:300013',
  EntityEnableByTag: 'action:300014',
  EntityDisableByTag: 'action:300015',
  EntityCheckPoint: 'action:300016',
  EntityEnableById: 'action:300017',
  EntityDisableById: 'action:300018',
  EntityStopSound: 'action:3000019',
  EntityAssetUnload: 'action:3000020',
  EntityAssetReload: 'action:3000021',
  EntityDestroy: 'action:3000022',
  ParticleSystemPlay: 'action:3000023',
  Quest: 'action:400001',
  TeleportAvatar: 'action:400002',
  AssignUserAsset: 'action:400003',
  Vote: 'action:400004',
  ShowToastMessage: 'action:400005',
  TaskComplete: 'action:400006'
}
```

### Trigger usage

#### Calling custom code from script when the player clicks on an object

Let's say we want to give our players the ability to click on an object and when that object is clicked, we want to call a method from a custom script. This is a good use-case for the **NotificationCenterSubscribeEntityPicking** trigger. In this example, when the user clicks on the specific object, a door opens.

{% stepper %}
{% step %}

#### Add the NotificationCenterSubscribeEntityPicking trigger

A. Select the object that the player will click on and add the **NotificationCenterSubscribeEntityPicking** trigger using the PlayCanvas extension. No need to add an action.

<div align="left"><figure><img src="/files/GN7AAbmT4jVStUL1BJCc" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

#### Create the ClickableObject.mjs script

A. Create the following script and name it **ClickableObject.mjs**.

```javascript
import { Script } from 'playcanvas';
import { TriggerTypes } from '../@viverse/create-extensions-sdk.mjs';

export class ClickableObject extends Script {
    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
        const event = TriggerTypes.NotificationCenterSubscribeEntityPicking;
        this.entity.on(event, () => {
            this.objectClicked();
            this.app.fire('DoorRotator:rotateDoor');
        });
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
    }

    objectClicked() {
        console.log('Objected Clicked!');
    }
}
```

B. Add **ClickableObject.mjs** script to the object that the player will click on.
{% endstep %}

{% step %}

#### Create the DoorRotator.mjs script

A. Create the following script and name it **DoorRotator.mjs**.

```javascript
import { Script } from 'playcanvas';

export class DoorRotator extends Script {
    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
        // listen for the DoorRotate:rotateDoor event
        this.app.on('DoorRotator:rotateDoor', this.rotateDoor);
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
    }

    rotateDoor() {
        console.log('Rotate Door!');
    }
}
```

B. Add the **DoorRotator.mjs** script to an object or entity.
{% endstep %}

{% step %}

#### Confirm the event is being fired

A. Test the functionality by publishing to VIVERSE and clicking on the object that has the **ClickableObject.mjs** script added to it.&#x20;

B. Confirm the event was fired by checking in the browser debug console.

<div align="left"><figure><img src="/files/RdCcbhWwxH2kBH8Cc44b" alt=""><figcaption></figcaption></figure></div>
{% endstep %}
{% endstepper %}

### Tigger usage with more flexibility

#### Calling custom code from script when the player clicks on an object and making the script more flexible

In the previous example, we created a custom script called **ClickableObject.mjs.** The script added functionality that rotates a door whenever a specific object is clicked.  Let's say there was another scenario where we wanted to have a 2nd object that the user could click on, but we wanted to fire a different event other than the rotateDoor method. We could easily copy our **ClickableObject.mjs** script, give it a different name and change the rotateDoor call inside the script. The downside here is that we now have 2 scripts that we have to maintain. As alternative, we can remove the rotateDoor event call, add an attribute to our script for the event name and then add the different event names through the PlayCanvas editor.

{% stepper %}
{% step %}

#### Update the ClickableObject.mjs script

A. Modify the code in **ClickableObject.mjs** script to the following.

```javascript
import { Script } from 'playcanvas';
import { TriggerTypes } from '../@viverse/create-extensions-sdk.mjs';

export class ClickableObject extends Script {
    /**
     * @attribute
     * eventToFire
     * @type { string }
     * @title Event To Fire
     */
    eventToFire

    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
        const event = TriggerTypes.NotificationCenterSubscribeEntityPicking;
        this.entity.on(event, () => {
            this.objectClicked();
            this.app.fire(this.eventToFire);
        });
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
    }

    objectClicked() {
        console.log('Objected Clicked!');
    }
}
```

{% endstep %}

{% step %}

#### Add the event name in to the script inside the PlayCanvas editor

A. Add the **DoorRotator:rotateDoor** text to the **Event To Fire** attribute text field.

<div align="left"><figure><img src="/files/t4Dwsl1YiqP7xdtgM9Ed" alt=""><figcaption></figcaption></figure></div>

B. If we added **ClickableObject.mjs** to a 2nd object and wanted to fire a different method, it could look like the following screenshot.

<div align="left"><figure><img src="/files/q6N5c3NirFQn56eOhfdr" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

#### Confirm the event is being fired

A. Test the functionality by publishing to VIVERSE and clicking on the object that has the **ClickableObject.mjs** script added to it.&#x20;

B. Confirm the event was fired by checking in the browser debug console. It should yield the same results as in the previous example.

<div align="left"><figure><img src="/files/Y0Dg9huR5dNyCp3kAAP3" alt=""><figcaption></figcaption></figure></div>
{% endstep %}
{% endstepper %}

### Additional trigger usage

**Calling custom code from script when a player enters a trigger area**

Another common scenario that creators may face is firing an event when a player enters a trigger area. This is a good use-case for the **EntitySubscribeTriggerEnter** trigger. In this example, when the user enters a trigger area, a door opens.

{% stepper %}
{% step %}

#### Add the EntitySubscribeTriggerEnter trigger

A. Select the trigger area that the player will walk through and add the **EntitySubscribeTriggerEnter** trigger using the PlayCanvas extension. No need to add an action.

<div align="left"><figure><img src="/files/W1jZVukrk12hmSb8oTYq" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

#### Create the TriggerArea.mjs script

A. Create the following script and name it **TriggerArea.mjs**.

```javascript
import { Script } from 'playcanvas';
import { TriggerTypes } from '../@viverse/create-extensions-sdk.mjs';

export class TriggerArea extends Script {
    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
        const event = TriggerTypes.EntitySubscribeTriggerEnter;
        this.entity.on(event, () => {
            this.triggerEntered();
            this.app.fire('DoorRotator:rotateDoor');
        });
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
    }

    triggerEntered() {
        console.log('Trigger Entered!');
    }
}
```

B. Add **TriggerArea.mjs** script to trigger area that the player will walk through.
{% endstep %}

{% step %}

#### Confirm the event is being fired

A. Test the functionality by publishing to VIVERSE and walking through the trigger area that has the **TriggerArea.mjs** script added to it.&#x20;

B. Confirm the event was fired by checking in the browser debug console. It should yield the following results.

<div align="left"><figure><img src="/files/hxCHureuJTjM0oogk4hr" alt=""><figcaption></figcaption></figure></div>
{% endstep %}
{% endstepper %}

### Action usage

#### Calling custom code from script when an action executed

Let's say we want to give our players the ability to click on an object and when that object is clicked, we want to call a method from a custom script. This is a good use-case for the **NotificationCenterSubscribeEntityPicking** trigger. In this example, when the user clicks on the specific object, a door opens.

{% stepper %}
{% step %}

#### Add the EntitySubscribeTriggerEnter trigger and the EntityDisable action

A .Select the trigger area that the player will walk through and add the **EntitySubscribeTriggerEnter** trigger along with the EntityDisable action using the PlayCanvas extension.

<div align="left"><figure><img src="/files/3Nk2Lv1ggku3Lp9t1sRe" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

#### Create the DisabledObject.mjs script

A. Create the following script and name it **DisableObject.mjs**.

```javascript
import { Script } from 'playcanvas';
import { ActionTypes } from '../@viverse/create-extensions-sdk.mjs';

export class DisabledObject extends Script {
    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
        const event = ActionTypes.EntityDisable;
        this.entity.on(event, () => {
            this.objectDisabled();
            this.app.fire('DoorRotator:rotateDoor');
        });
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
    }

    objectDisabled() {
        console.log('Object Disabled!');
    }
}
```

B. Add **DisabledObject.mjs** script to trigger area that the player will walk through. In this example the **DisabledObject.mjs** script was added to the trigger area in the previous example. Be sure to disable or remove the **TriggerArea.mjs** script from the previous example.

<div align="left"><figure><img src="/files/o6UoZzoR4HtOViF82a8n" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

#### Confirm the event is being fired

A. Test the functionality by publishing to VIVERSE and walking through the trigger area that has the **DisabledObject.mjs** script added to it.&#x20;

B. Confirm the event was fired by checking in the browser debug console. It should yield the following results.

<div align="left"><figure><img src="/files/S6CdBDt0K3cS0lr5GE7o" alt=""><figcaption></figcaption></figure></div>
{% endstep %}
{% endstepper %}


# Introduction to MJS

This document provides an introduction to MJS (.mjs) also known as Modular JavaScript

***

Modular JavaScript is an evolved form of JavaScript in which related functionality is kept in a single file or module and that functionality is exposed when required using import and export functionality.&#x20;

### CommonJS (NodeJS) example

Here's a comparison between MJS and CommonJS. The latter requires node.js, and relies on `require()`  statements to import other scripts/modules:

```javascript
// To include the File System module, use the require() method
const fs = require('fs');

// Using the File System module to read files
fs.readFile();

// Exporting the greet function so it can be used in other modules, use module.exports
module.exports = function greet(name) {
    return 'Hello, ${name}!';
};
```

MJS, on the other hand, can use import statements like other languages.

```javascript
// To include the File System module, you no longer use require() method
// Use import instead.
import fs from 'fs';

// Using the File System module to read files
fs.readFile();

// Exporting the greet function so it can be used in other modules, you no longer
// use `module.exports`. Use `export` instead.
export const greet = (name) => {
    return 'Hello, ${name}!';
};
```

### MJS PlayCanvas Script Structure

Further, MJS is now supported by PlayCanvas and the VIVERSE Create SDK, to make script imports easier and mroe modern. Here's an example of a script called `GameManager.mjs` (critically, the file extension is `.mjs`, not just `.js` as usual) created for PlayCanvas, extending its built-in `Script` class:

```javascript
import { Script } from 'playcanvas';

export class GameManager extends Script {
    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
    }
}
```

### Accessing a function in a MJS(.mjs) file from regular JavaScript (.js) code example

**DoorRotator.mjs** can can be added to a PlayCanvas entity. This allows functions to be executed on that entity such as a door that needs to be opened. After adding additional code inside the rotateDoor function, this function can be called from another script to open the door.

```javascript
import { Script } from 'playcanvas';

export class DoorRotator extends Script {
    /**
     * @attribute
     * @type {number}
     * @title First Number
     */
    firstNumber = 10;
    
    initialize() {
        // Add the DoorRotator script to this.app
        this.app.doorRotator = this;
    }

    rotateDoor() {
        console.log('Rotate Door!');
    }
}
```

**TriggerArea.js** - Script can be added to a trigger area entity. When another entity or the avatar enters the trigger area, the rotateDoor function is called and the door will open.

```javascript
var TriggerArea = pc.createScript('triggerArea');

TriggerArea.prototype.initialize = function() {
    // Setup listening for the triggerenter event
    this.entity.collision.on('triggerenter', this.onTriggerEnter, this);
};

// Handle the onTriggerEnter event
TriggerArea.prototype.onTriggerEnter = function(entity) {
    // Calling function in DoorRotator.mjs
    this.app.doorRotator.rotateDoor();

    // Accessing property in DoorRotator.mjs
    console.log("FirstNumber: " + this.app.doorRotator.firstNumber);
}
```

### Debugging MJS files

The process for debugging MJS files is simple. You can add the `debugger;` statement to any of the methods.

```javascript
import { Script } from 'playcanvas';

export class Debug extends Script {
    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
        debugger;
        console.log("Debug initialize!");

        this.testFunction();
    }

    testFunction() {
        debugger;
        console.log('Test Function!');
    }
}
```

With the project running in Chrome, pressing **F12** will bring up the Chrome DevTools window. **Refresh** the page with the DevTools window open and the project will stop at the debugger statement.

<figure><img src="/files/0d6kezhRXj3IFAi3ya5V" alt=""><figcaption></figcaption></figure>

When testing your project in PlayCanvas Launch mode, the Chrome Dev Tools can also be opened by right-clicking on the screen and selecting Inspect.

{% stepper %}
{% step %}

#### Right-click on the viewing area of the project

<figure><img src="/files/KtRUScW4thQ2fv4pcJPR" alt="" width="368"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Select Inspect

<figure><img src="/files/8dHQe3l5R1sY0aMPTbeZ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

Open up Chrome DevTools through browser menu

{% stepper %}
{% step %}

#### Click the Customize and control Google Chrome Button in the browser

<figure><img src="/files/xSQ7ThjUeXXpCwypHWYs" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Click More Tools

<figure><img src="/files/UdBpet1ddKyGjSC15oW2" alt="" width="356"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Click Developer Tools

<figure><img src="/files/fOi7rR0UU9LOuOUFzRsS" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

Keyboard shortcut for DevTools window

{% tabs %}
{% tab title="Windows" %}

<div align="left"><figure><img src="/files/fDxdXcRwbziDnn2bJ8Tj" alt=""><figcaption></figcaption></figure></div>
{% endtab %}

{% tab title="Mac" %}

<div align="left"><figure><img src="/files/hc89pbjZmFsnP0X5FEBL" alt="" width="443"><figcaption></figcaption></figure></div>
{% endtab %}
{% endtabs %}

Breakpoints can also be set manually by clicking on the line code number in the margin. The line code number will be highlighted in blue when a breakpoint is set.

<figure><img src="/files/4jC27YllFYlEy5YV9PAU" alt=""><figcaption></figcaption></figure>

### MJS file with custom properties and methods example

**GameManager.mjs** - GameManager script can be used to control the different game states of the project. This example script has multiple properties of different types added (including arrays), some with default values. In addition to the properties, there are multiple functions added that show how these properties can be utilized and their values are printed to the console.

````javascript
import { Script } from 'playcanvas';

export class GameManager extends Script {
    /**
     * @attribute
     * @type {number}
     * @title First Number
     */
    firstNumber

    /**
     * @attribute
     * @type {number}
     * @title Second Number
     */
    secondNumber = 5;

    /**
     * @attribute
     * @type {string}
     * @title First String
     */
    firstString

    /**
     * @attribute
     * @type {string}
     * @title Second String
     */
    secondString = 'Default Text';

    /**
     * @attribute
     * @type {boolean}
     * @title First Boolean
     */
    firstBoolean

    /**
     * @attribute
     * @type {boolean}
     * @title Second Boolean
     */
    secondBoolean = false;

    /**
    * @attribute
    * @type { pc.Entity }
    * @title Entity
    */
    entity

    /**
     * @attribute
     * @type {number[]}
     * @title Number Array
     */
    numberArray

    /**
     * @attribute
     * @type {string[]}
     * @title String Array
     */
    stringArray

    /**
     * @attribute
     * @type {boolean[]}
     * @title Boolean Array
     */
    booleanArray

    /**
    * @attribute
    * @type { pc.Entity[] }
    * @title Entities
    */
    entityArray

    /**
     * @attribute
     * @type { CustomObject[] }
     * @title Custom Objects Array
     */
    customObjectArray

    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
        this.changeFirstNumber();
        this.printValue("FirstNumber: " + this.firstNumber);
        this.printValue("SecondNumber: " + this.secondNumber);

        this.modifyFirstString();
        this.printValue("FirstString: " + this.firstString);
        this.printValue("SecondString: " + this.secondString);

        this.modifyFirstBoolean();
        this.printValue("FirstBoolean: " + this.firstBoolean);
        this.printValue("SecondBoolean: " + this.secondBoolean);

        this.printValue("Entity Name: " + this.entity.name);

        this.printValue("Number Array: ");
        this.printArray(this.numberArray);

        this.printValue("String Array: ");
        this.printArray(this.stringArray);

        this.printValue("Boolean Array: ");
        this.printArray(this.booleanArray);

        this.printValue("Entity Array: ");
        this.printEntityArray(this.entityArray);

        this.printValue("CustomObjectArray: ");
        this.printCustomObjectArray(this.customObjectArray);
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
    }

    changeFirstNumber() {
        this.firstNumber = 3;
    }

    modifyFirstString() {
        this.firstString = "modified string";
    }

    modifyFirstBoolean() {
        this.firstBoolean = true;
    }

    printValue(value) {
        console.log(value);
    }

    printArray(array) {
        for (var x = 0; x < array.length; x++) {
            console.log(array[x]);
        }
    }

    printEntityArray(array) {
        for (var x = 0; x < array.length; x++) {
            console.log(array[x].name);
        }
    }

    printCustomObjectArray(array) {
        for (var x = 0; x < array.length; x++) {
            console.log(array[x].valueOf());
        }
    }
}

/** @interface */
class CustomObject {
    /**
     * @attribute
     * @type { number }
     * @title Number
     */
    number

    /**
     * @attribute
     * @type { pc.Entity[] }
     * @title Entities Array
     */
    entities
}
```
````

### Key Examples

<details>

<summary>localPlayerManager.mjs - Control <a href="https://viveportsoftware.github.io/pc-lib/interfaces/ILocalPlayer.html">Local Player Properties</a></summary>

```javascript
import { Script } from 'playcanvas';
import * as pc from 'playcanvas';
import { PlayerService } from '../@viverse/create-sdk.mjs'


export class LocalPlayerManager extends Script 
{
    initialize() {
        this.playerService  = new PlayerService();
        
        //attach playerService for global access, reference this.app.playerServiceManager in other files
        this.app.playerServiceManager = this;
        
        //FOR ALL CUSTOMIZABLE PROPS AND METHODS, see: https://viveportsoftware.github.io/pc-lib/interfaces/ILocalPlayer.html
        
        //enable flight
        this.playerService.localPlayer.canFly = true;
        
        //enable movement
        this.playerService.localPlayer.canMove = true;
        
        //hide avatar
        this.playerService.localPlayer._entity.visibility = false;
    }

    update(dt)
    {
    }
}
```

</details>

<details>

<summary>cameraServiceManager.mjs - Control <a href="https://viveportsoftware.github.io/pc-lib/interfaces/ICameraService.html">Camera Service Properties</a></summary>

```javascript
import { Script } from 'playcanvas';
import * as pc from "playcanvas"
import { CameraService } from './@viverse/create-sdk.mjs'

/**
 * The {@link https://api.playcanvas.com/classes/Engine.Script.html | Script} class is
 * the base class for all PlayCanvas scripts. Learn more about writing scripts in the
 * {@link https://developer.playcanvas.com/user-manual/scripting/ | scripting guide}.
 */
export class CameraServiceManager extends Script {
    /**
     * Called when the script is about to run for the first time.
     */
    initialize() {
        this.cameraService  = new CameraService();
        
        //FOR ALL CUSTOMIZABLE PROPS AND METHODS, see https://viveportsoftware.github.io/pc-lib/interfaces/ICameraService.html
        
        //switch to 1st person pov
        this.cameraService.switchPov(0);
        
        //prevent pov switching
        this.cameraService.canSwitchPov = false;
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
    }
}
```

</details>


# Camera Management: Settings and Switching

How to use VIVERSE SDK's CameraService to manage camera settings and switch between different VIVERSE and custom cameras.

***

VIVERSE provides a powerful camera system alongside its player controller and input scripting that allows developers to switch between different views and control various camera behaviors. This guide will show you how to implement camera switching in your VIVERSE project.

### Basic Camera Switching

The VIVERSE player rig creates several cameras and switches between them depending on your settings and/or world scripting: `CAMERA_3RD`, `CAMERA_1ST`, `CAMERA_VR`, `CAMERA_ORBITAL` are all separate in the scene hierarchy.

The [`CameraService`](https://viveportsoftware.github.io/pc-lib/classes/CameraService.html) provides methods to switch between different camera views provided by the VIVERSE avatar rig. Here's a basic .mjs script importing the `CameraService`  and switching to first-person camera mode on init. Simply attach this script to any entity in your project.&#x20;

```javascript
import { Script } from "playcanvas";
import { CameraService, CameraTypes } from "../@viverse/create-sdk.mjs";

export class CameraManager extends Script {
  initialize() {
    this.cameraService = new CameraService();

    // Switch to first person view to first person 
    this.cameraService.switchPov(CameraTypes.PovTypes.FirstPerson);
    // At any time, you can switch back to CameraTypes.PovTypes.ThirdPerson

    // Prevent users from switching POV, which is usually done in user settings or with the keyboard shortcut "V"
    this.cameraService.canSwitchPov = false;
  }
}
```

> **NOTE:** *this script asset must be placed in `/scripts` or another subfolder, since it assumes the VIVERSE SDK is one level up, located at: `"../@viverse/create-sdk.mjs"` - or you can alter this import path as needed.*

### Custom Camera Override Example

If the VIVERSE player rig cameras don't totally meet your needs, you can switch to your own camera entity for cut scenes or preview cameras. Here's a practical example of how to override the default VIVERSE camera with a custom camera on init:

```javascript
import { Script, Entity } from "playcanvas";
import { CameraService } from "../@viverse/create-sdk.mjs";

export class OverrideViverseCamera extends Script {
  /**
   * @attribute
   * @type {Entity}
   */
  overrideCamera = null;
  
  initialize() {
    if (!this.overrideCamera) {
      console.error("No override camera set!");
      return;
    }

    this.cameraService = new CameraService();
    this.cameraService.switchCamera(this.overrideCamera);
  }
}
```

### Camera Switching on Events: VR and Player Ready

You can listen for certain events from other services that may be related to your desired camera behavior. For example, on the `player:ready` event, we know that all player cameras and scripting have loaded in and are ready to interact with the scene:

```javascript
// Wait for the XR start event then get the VR camera
this.app.xr.on("start", () => {
  // The VIVERSE player rig will switch to its "CAMERA_VR" automatically
  // So getting reference to the activeCamera at this time will point to
  // the "CAMERA_VR" entity
  const vrCamera = this.cameraService.activeCamera;
  
  // You could also search for this by name
  // const vrCamera = this.app.root.findByName("CAMERA_VR");
  
  // Then raycast from the forward vector, apply scripts, etc.
});

// Or wait for player to be ready before switching cameras
this.playerService.localPlayer.on("player:ready", () => {
  this.cameraService.switchCamera(this.previewCamera);
});
```

### Additional Camera Properties

The `CameraService` provides several properties to customize camera behavior:

```javascript
// Set minimum and maximum zoom distances
this.cameraService.minZoomDistance = 1;
this.cameraService.maxZoomDistance = 10;

// Disable camera zooming
this.cameraService.canZoom = false;

// Disable camera rotation
this.cameraService.canRotate = false;

// Get current POV type
const currentPov = this.cameraService.pov; // Returns PovTypes enum value

// Get active camera entity
const activeCamera = this.cameraService.activeCamera; // Returns null | Entity
```

### Additional Resources

* [VIVERSE SDK API Documentation](https://viveportsoftware.github.io/pc-lib/)
* [PlayCanvas Scripting Guide](https://developer.playcanvas.com/user-manual/scripting/)


# Custom Loading Screens

Learn how to create a custom loading screen on VIVERSE during asset pre-loading.

***

### Default Loading Experience

Because web games and assets take time to transfer over the internet and load onto the player's device, user experience during loading is important. If shown a blank or broken-looking screen during this time, users may leave early, and never get to try the full experience.

While creators should [optimize load time as much as possible](https://developer.playcanvas.com/user-manual/optimization/load-time/), it's also somewhat inevitable. As such, VIVERSE provides a default loading experience during the asset pre-load phase:

<div align="center"><figure><img src="/files/xlxIFLujYPzGJXDq3fOu" alt="" width="375"><figcaption></figcaption></figure></div>

### Custom Loading Screens

However, some creators may wish to make custom loading screens more in line with the aesthetics of their game or app. This can help sell an overall sense of quality and immediately set the tone for users.

VIVERSE creators can make full use of [PlayCanvas' built-in loading screen feature](https://developer.playcanvas.com/user-manual/editor/launch-page/loading-screen/). Just navigate to PlayCanvas Project Settings > Loading Screen and click "Create Default."

<figure><img src="/files/zFSi8XCnwRgwgXAYSAlU" alt=""><figcaption></figcaption></figure>

If you have the [VIVERSE-PlayCanvas browser extension](https://docs.viverse.com/playcanvas-sdk/playcanvas-extension-setup#playcanvas-extension-download) installed, this will inject a VIVERSE logo into the loading screen template, **which is required for all projects**.

Other than that, `loading-screen.js` is yours to customize. Add your logo, a tagline in a custom font, or a cool background image, grounding your users in your experience immediately.&#x20;

<figure><img src="/files/p9qpI7qSg3BG8lfbAnEA" alt="" width="375"><figcaption><p>The custom loading screen from <em>In Tirol</em>.</p></figcaption></figure>

### Control Which Assets Download During the Loading Screen

All assets with the "Preload" box checked in asset settings will be downloaded during the loading screen phase. Limiting pre-loaded assets can help get users to your game view faster, which will result in fewer overall "bounces" from your game (meaning users who quit the game during loading). So only check "Preload" if the asset is immediately needed - otherwise it will stream in behind the scenes as needed, [per PlayCanvas' docs](https://developer.playcanvas.com/user-manual/assets/preloading-and-streaming/).

<figure><img src="/files/JymvoDU606PMyFep4p4p" alt="" width="188"><figcaption></figcaption></figure>


# Change Avatars Programatically

Control how users see and express themselves with the .changeAvatar() method on LocalPlayer.

***

If you don't want to use default VIVERSE avatars for your world, the PlayCanvas SDK features a simple method to swap to any .vrm avatar asset in your project.

[Per the API docs](https://viveportsoftware.github.io/pc-lib/index.html), import the [`PlayerService`](https://viveportsoftware.github.io/pc-lib/interfaces/IPlayerService.html) into your .mjs script, which has a [`localPlayer` property](https://viveportsoftware.github.io/pc-lib/interfaces/ILocalPlayer.html) of type `LocalPlayer`, which in turn has a [`.changeAvatar()` method](https://viveportsoftware.github.io/pc-lib/interfaces/ILocalPlayer.html#changeAvatar.changeAvatar-1).

```javascript
import { Script, Asset } from "playcanvas";
import { PlayerService } from "../@viverse/create-sdk.mjs";

export class VvSwitchAvatars extends Script {
  static scriptName = "vvSwitchAvatars";

  /**
   * @attribute
   * @type {Asset}
   */
  vrmAsset = null;

  initialize() {
    this.playerService = new PlayerService();
    this.playerService.localPlayer.changeAvatar(this.vrmAsset);
  }
}

```

> **NOTE:** *this script asset must be placed in `/scripts` or another subfolder, since it assumes the VIVERSE SDK is one level up, located at: `"../@viverse/create-sdk.mjs"` - or you can alter this import path as needed. For more information on how .mjs scripts and imports work, see* [*Introduction to MJS*](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/custom-code/introduction-to-mjs)*.*

Because `vrmAsset` is defined as an attribute of type `Asset`, we can then select which asset to use directly in the PlayCanvas editor once the script is added to an entity.

<figure><img src="/files/YbyQnmtajcOTS549litS" alt=""><figcaption></figcaption></figure>

When we publish to VIVERSE and load the experience, the .vrm is loaded immediately. Avatar switching for the local player is possible at any point during runtime and can be triggered with UI, trigger colliders, or with any other programmatic callback.\
\
**Reference Code:**\
[PlayCanvas minimal reproduction project](https://playcanvas.com/project/1350550/overview/changeavatar-demo)\
[Live demo](https://create.viverse.com/DApMQ7h)

<figure><img src="/files/yhs1xXv6PeainWyPzgSZ" alt=""><figcaption></figcaption></figure>


# Custom Virtual Reality UX

Use \`XrService\` to interact with virtual reality devices and controllers.

***

WebXR experiences can run on desktop, mobile and virtual reality devices alike. Utilizing the `XrService` in the Create SDK, you can write custom code to manage VR controllers, locomotion settings, and XR session callbacks.

### Custom Locomotion Settings

By default, the VIVERSE character controller uses teleport for locomotion, since this is the more comfortable option, in general. However, smooth locomotion (where the player glides smoothly across the floor) can be enabled on either or both controllers by setting the [`LocomotionType`](https://viveportsoftware.github.io/pc-lib/enums/XrTypes.LocomotionTypes.html) of each.

Import the [`XrService`](https://viveportsoftware.github.io/pc-lib/classes/XrService.html) from the Create SDK into an .mjs script. Per the API docs, this gives you access to both [left and right controllers](https://viveportsoftware.github.io/pc-lib/classes/XrService.html#controllers), and their properties, to set on init:

```javascript
import { Script, Asset } from "playcanvas";
import { XrService, XrTypes } from "../@viverse/create-sdk.mjs";

export class ViverseXrManager extends Script {
  static scriptName = "viverseXrManager";
  
  initialize() {
    this.xrService = new XrService();
    this.xrService.controllers.right.locomotionType = XrTypes.LocomotionTypes.Teleport;
    this.xrService.controllers.left.locomotionType = XrTypes.LocomotionTypes.Smooth;  // Smooth
    // See enum definitions here:
    // https://viveportsoftware.github.io/pc-lib/enums/XrTypes.LocomotionTypes.html
  }
}
```

> **NOTE:** *this script asset must be placed in `/scripts` or another subfolder, since it assumes the VIVERSE SDK is one level up, located at: `"../@viverse/create-sdk.mjs"` - or you can alter this import path as needed. For more information on how .mjs scripts and imports work, see* [*Introduction to MJS*](/playcanvas-toolkit/building-with-the-playcanvas-toolkit-legacy-v3/custom-code/introduction-to-mjs)*.*

When switching to Smooth Locomotion, this also enables flight in VR in any World where flight is enabled in World Settings. Just "click" the Smooth Locomotion joystick to enter flight mode. Once flying, pressing forward on the joystick will fly forward along the VR camera's forward axis (i.e. wherever you're looking), and vice versa backwards.

### Custom Controller Models

Checking the `IXrController` interface further, we can [use the `setModelAsset()` function to set custom 3D models for our controllers](https://viveportsoftware.github.io/pc-lib/interfaces/IXrController.html#setModelAsset.setModelAsset-1), instead of the default VIVERSE models.

```javascript
import { Script, Asset } from "playcanvas";
import { XrService } from "../@viverse/create-sdk.mjs";

export class ViverseXrManager extends Script {
  static scriptName = "viverseXrManager";
  
  /**
  * @attribute
  * @type {Asset}
  */
  vrControllerAssetR = null;

  /**
  * @attribute
  * @type {Asset}
  */
  vrControllerAssetL = null;

  initialize() {
    this.xrService = new XrService();
    this.xrService.controllers.right.setModelAsset(this.vrControllerAssetR);
    this.xrService.controllers.left.setModelAsset(this.vrControllerAssetL);
  }
}
```

After defining the `vrControllerAssetL` and `vrControllerAssetR` attributes of type `Asset` in the above script, we then reference custom 3D controller assets in the editor, which our script instantiates at runtime in VR.

<figure><img src="/files/L5UNo6aX7eEvQczZIQh6" alt=""><figcaption></figcaption></figure>


# Creating From Templates

This document details the process of creating a world from a template.

***

Templates are starter projects that are pre-built in VIVERSE. They are available on the profile page of your VIVERSE account.

**Prerequisites**

* In order to create worlds you must first create a VIVERSE account.

{% stepper %}
{% step %}

### Navigate to [worlds.viverse.com/profile](https://worlds.viverse.com/profile)

<figure><img src="/files/NiTAdGjueWox8S4XnRlR" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Scroll down to "Worlds" and select the "Create World" button

<figure><img src="/files/u30Vett8S2IdSmVpIrUI" alt="" width="317"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Select from the list of premade templates

<figure><img src="/files/7FynZ09liyiSrwrwVIRD" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Updating the World Settings

Step 1. Select the **Details** page (A).

Step 2. Give the world a name in the **Name** field (B).

Step 3. Give the world a description in the **Description** field (C).

Step 4. Upload a thumbnail of the world in the **Thumbnail** (D) section.

Step 5. Assign at least genre to the world in the **Genre** (E) section.

Step 6. Set the compatible devices types for the world in the **Devices** (F) section.

<figure><img src="/files/rGiHFeFKqRRZhtpFAaeP" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/zeUu93RUJjo5ctnzujmq" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Adding gameplay trailer and screenshots

Step 1. Click on the **Media** (A) tab.

Step 2. Add a trailer video to the Trailer (B) section.

Step 3. Add images to the **Images** (C) section.

<figure><img src="/files/Gq50mzEaYO1xNwH4d6uw" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Adding a GitHub link

Step 1. Click on the **Code & Collab** (A) tab.

Step 2. Add a GitHub link to the **GitHub** (B) section.

<figure><img src="/files/kDKHxyN9E2cBycgHMV3v" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Managing World Settings after creating a world

Step 1. Select the **Worlds** tab (A).

Step 2. Select the 3 dots (B) on the world that needs to be updated.

Step 3. Click **World Settings** (C).

<figure><img src="/files/9GkPV4srCcmzACWhj3XO" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/fBDDH1E12sraPmWc0BRk" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Edit Mode

This page details the basics of Edit Mode for decorating your world.

***

### Getting Started with Edit Mode

Edit Mode is available on all worlds you own or where you have been added as a co-owner/moderator.  If you are a world owner or co-owner, you will have access to the "Edit Mode" button when you join the world.

<figure><img src="/files/5a4y8cDUaN28NoeCNIVv" alt="" width="234"><figcaption><p>The Edit Mode button</p></figcaption></figure>

When enabling Edit Mode, your controls and interface will be changed, and your camera will be detached from your avatar so that you can more easily navigate and edit your world. When in Edit Mode, your avatar will still be visible and your audio will be audible to others in the world.

<figure><img src="/files/aeBZYZpaQJCEj77hzmPb" alt="" width="188"><figcaption><p>The Edit Mode controls</p></figcaption></figure>

The Edit Mode interface has three sections, the performance report in the top-left corner, the media upload toolbar in the bottom of your screen, and the object list in the top right where you can access and control the settings of all media added to your world in Edit Mode.

<figure><img src="/files/Mb0zXO55VxNoz6Fppu2X" alt="" width="375"><figcaption><p>The Edit Mode interface.</p></figcaption></figure>

In order to save your customizations and have others see them, you will need to select the "Close edit mode" button and exit Edit Mode.

### Viewing the Performance Report

{% stepper %}
{% step %}

#### Open the Performance requirement link

Step 1. Click **Performance requirement** (A).

<figure><img src="/files/Va8nICTWZ35Su4de1gcZ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### View the world's VRAM consumption

Step 1. View the **Performance Manager** report (B).

<figure><img src="/files/7n8nX7sil8bcPmD6ajdM" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Adding and Handling Media

Media can be added from several different sources through the toolbar in the bottom of the Edit Mode interface: Marketplace assets, uploaded assets, and assets linked from external sources. For more information on the types of media you can add and their settings, see [this page](/publishing-from-templates/supported-media-and-settings).

<figure><img src="/files/XNG8VUflSMd6QC8H0tAg" alt="" width="338"><figcaption><p>The media upload toolbar</p></figcaption></figure>

When media are added to your world, you can access and update their properties through the object list on the right-hand side of the Edit Mode interface. Furthermore, you can quickly reposition your media by clicking and dragging them through your world.

<figure><img src="/files/GHMiYL0Q3gWNoegFlAER" alt="" width="375"><figcaption><p>The Edit Mode object list</p></figcaption></figure>

### Adding Collaborators

You can add five additional VIVERSE accounts as co-owners of your world. By promoting these users, you are giving them full control over Edit Mode, moderation tools, and world settings for your world. Co-owners can be removed and will not be able to delete the world on your behalf. Co-owners can be added outside of Edit Mode by going to the hamburger settings button in the top-right of your world, selecting the "Permissions" tab, and adding Co-Owners by their user id or display name.

<figure><img src="/files/o9SZFOofRnfsXYXKZa8t" alt="" width="375"><figcaption><p>Adding Co-owners through world permissions.</p></figcaption></figure>


# Supported Media & Settings

This page details the different kinds of media that can be uploaded to a world in Edit Mode and the settings you can control.

***

### Adding Media

Media are added to VIVERSE worlds in Edit Mode using the toolbar at the bottom of the screen. Detailed below are the categories of assets that correspond with the buttons in this toolbar.

<figure><img src="/files/XNG8VUflSMd6QC8H0tAg" alt=""><figcaption><p>The Edit Mode asset upload toolbar</p></figcaption></figure>

| Marketplace                                                                                                      | Upload                                            | Embed/Link                                                                      |
| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------- |
| Import assets associated with your account which you have uploaded to or purchased from the VIVERSE marketplace. | Upload files to your world from your computer.    | Embed assets hosted on servers external to VIVERSE.                             |
| GLB/glTF 2.0 files                                                                                               | JPG, PNG, GIF, PDF, MP4, MP3, GLB/glTF, VRM files | Websites, Images, Videos, Audio files, PDFs, .m3u8, YouTube videos/live streams |

### Media Settings

Once media have been uploaded, you are able to control several of its properties through the asset's details:

* **Asset Details** | Here you can add an expandable description and a clickable link to an external url.
* **Transform Properties** | Here you can control the asset's position, rotation, and scale.
* **Frame** | For 2D media, such as images or external website links, you can add a colored frame to give your media extra dimensionality.
* **Playback Settings** | For media with some form of playback, such as videos or streams, you can control audio and playback settings.

<figure><img src="/files/tPkwYbQOZPIPfD6tfuEy" alt="" width="333"><figcaption><p>The media properties editor</p></figcaption></figure>

#### Media Playback Settings

Configuring media playback settings for **MP4s** can be done via **Audio** (A) toggle.

<figure><img src="/files/AzZr5LDaLJkZAzAUzaLy" alt="" width="375"><figcaption></figcaption></figure>

Configuring media playback settings for **MP3s** can be done via **Audio** (A) toggle.

<figure><img src="/files/Tar5sDKbcRYxZYf7sS2n" alt="" width="375"><figcaption></figcaption></figure>

#### Adding Interactions to VRMs

To add interactions to VRMs, click **Add** button (A) under **Interactions**. Configure the interaction  in the panel (B).

<figure><img src="/files/SUAdkFl4FkafNDZCVF0N" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/MPxdJYbZ3f0BMaOdnuYk" alt="" width="375"><figcaption></figcaption></figure>


# Monetization on VIVERSE

This page introduces the methods which creators can use to earn on VIVERSE.

***

## Earning on VIVERSE

We are proud to support creators and teams of all sizes and artistic backgrounds on VIVERSE. In addition to investing in the development of new content on VIVERSE through our [Creator Grants](https://create.viverse.com/creator-program), we are excited to offer a number of ways for creators to earn on VIVERSE through our [Partner Program](#joining-the-viverse-partner-program).

We are currently working to release these tools to our developers. Here are some of the monetization methods that are/will be available to creators...

<table><thead><tr><th width="168.30303955078125">Content Type</th><th width="255.5811767578125">Description</th><th width="119.3590087890625">Status</th><th>Baseline Rev Share*</th></tr></thead><tbody><tr><td>Viewership Compensation</td><td>Creators earn based on the engagement their content receives both on VIVERSE and through embedding their VIVERSE-hosted content across the internet.</td><td>Open</td><td>Up to $0.011 USD monthly per player</td></tr><tr><td>Channel Subscriptions</td><td>Players pay monthly to access content listed by you under a channel subscription. This may be one or multiple experiences listed under one subscription.</td><td>Closed Beta</td><td>80% Developer / 20% Platform</td></tr><tr><td>One-Time Purchases</td><td>Players pay once to access an individual VIVERSE experiences.</td><td>Closed Beta</td><td>80% Developer / 20% Platform</td></tr><tr><td>Donations</td><td>Players may contribute to your favorite Creator as they develop more amazing content on VIVERSE.</td><td>Coming Soon...</td><td>TBD</td></tr><tr><td>In-App Purchases SDK</td><td>Players may purchase unlock-able content from with a your VIVERSE experience.</td><td>Coming Soon...</td><td>TBD</td></tr></tbody></table>

What else is on the roadmap? In addition to the methods listed above, we are also exploring ad revenue sharing and VIVERSE platform subscriptions. If you would like to see the VIVERSE monetization tools go in a specific direction, [join our Discord Server](https://discord.gg/viversecreators) and let us know your thoughts in #monetization. We want to hear how you think VIVERSE can become the most developer-friendly platform for 3D creations.

## Joining the VIVERSE Partner Program

The [Partner Program](https://create.viverse.com/partner) is the gateway for using our monetization tools! To join, [submit an application to the Partner Program](https://create.viverse.com/partner-application) and hold tight for us to get back to you.

<figure><img src="/files/yVwrMPWWZZhehHFhmFnG" alt=""><figcaption></figcaption></figure>

At this time, only creators 18 years and older from the following countries are eligible for the VIVERSE Partner Program:

Argentina, Algeria, Australia, Austria, Bahrain, Bangladesh, Belgium, Bolivia, Brazil, Brunei, Bulgaria, Cambodia, Cameroon, Canada, Chile, Colombia, Costa Rica, Croatia, Cyprus, Czech Republic, Denmark, Dominican Republic, Ecuador, Egypt, El Salvador, Estonia, Ethiopia, Finland, France, Germany, Ghana, Greece, Greenland, Guatemala, Haiti, Honduras, Hungary, Iceland, India, Indonesia, Ireland, Israel, Italy, Jamaica, Japan, Jordan, Kenya, Kuwait, Laos, Latvia, Lebanon, Lithuania, Luxembourg, Malaysia, Malta, Mexico, Mongolia, Morocco, Myanmar, Nepal, Netherlands, New Zealand, Nicaragua, Nigeria, Norway, Oman, Pakistan, Panama, Papua New Guinea, Paraguay, Peru, Philippines, Poland, Portugal, Puerto Rico, Qatar, Romania, Rwanda, Saudi Arabia, Senegal, Singapore, Slovakia, Slovenia, South Africa, South Korea, Spain, Sri Lanka, Sweden, Switzerland, Taiwan, Tanzania, Thailand, Trinidad and Tobago, Tunisia, Turkey, Uganda, Ukraine, United Arab Emirates, United Kingdom, United States, Uruguay, Vietnam.

## Getting Paid

At this time, payout takes place on an invoice basis. Partner Program participants will be onboarded to this system shortly after being invited to the program. We expect to automate this process with a payout system in the coming months.

***

\*All figures referenced are shares of **net revenue.** In addition to this baseline, VIVERSE may offer more generous rates on a case-by-case basis or through limited time promotions. For full details about revenue sharing, check out the VIVERSE Developer Platform Agreement: <https://www.viverse.com/terms-of-use>


# Viewership Compensation

This page details the process for creators to earn based on viewership.

***

## How We Pay for Viewership

Viewership compensation is available to all [VIVERSE Partner Program](https://create.viverse.com/partner) participants. We compensate creators based on the amount of 30-second play sessions their content receives on a monthly basis. Each month, our analytics allow us to determine the total number of 30-second play sessions that a piece of content has received, as well as the number of unique users who have contributed these play sessions.&#x20;

Partners can earn from play sessions that occur on viverse.com or from play sessions of VIVERSE content embedded across the internet ([learn more about sharing your content](/sharing-content)). If you have a large social-media following, a high-traffic webpage, or have a piece of content that gains amazing traffic, we will compensate you for sharing VIVERSE-hosted content through these avenues!

## Viewership Compensation Rates

The Partner Program compensates creators based on initial and repeat views on the following schedule:

* $0.002 USD for first-time plays
* $0.001 USD for repeat plays
* Up to 10 repeat plays per unique player per month

By this schedule, Partners can earn up to $0.012 USD per unique player per month, assuming that the player completes all 11 valid play sessions.

{% hint style="info" %}
The VIVERSE team does negotiate custom compensation rates on a case-by-case basis. [Email the VIVERSE team](mailto:michael_morran@htc.com?cc=brooke_chen@htc.com\&subject=Custom%20Viewership%20Rates\&body=Hello%20VIVERSE%20Team%2C%0A%0AI%20would%20like%20to%20request%20a%20custom%20rate%20for%20viewership%20compensation.%20I%20believe%20that%20my%20content%20%7Blink%20to%20VIVERSE%20hosted%20content%7D%20will%20bring%20in%20approximately%20%7Binsert%20number%20of%20estimated%20monthly%2030s%20play%20sessions%7D%20plays%20and%20would%20like%20to%20be%20paid%20%7Binsert%20rate%20per%20play%20you%20are%20requesting%7D.) to begin discussing custom rates.
{% endhint %}

## How to Get Started

After [joining the VIVERSE Partner Program](/monetization#joining-the-viverse-partner-program), your content will automatically become eligible for viewership based compensation. As you are registering for the Partner Program, you will connect with a member of the VIVERSE team who will be able to assist with any questions you have about the setup process, including getting your work embedded on additional webpages.&#x20;

{% hint style="danger" %}
Please note, participation in the VIVERSE Partner Program **does not guarantee that your content will be curated** on VIVERSE webpages, nor does it guarantee a certain amount of viewership by default. Ultimately, viewership depends on the merits of the content itself and is benefitted by each creator's active promotion of their content to the VIVERSE community, through social media, and on external curation surfaces and webpages.
{% endhint %}


# Premium Content & Channel Subscriptions

This page details the process for VIVERSE players to purchase content.

***

## Setting Up Content for Purchase

Premium content and channel subscriptions are **currently in beta** with a manual setup process, but we are working to automate this process in the near future. The first requirement for for setting up purchase-able content, is to join the [VIVERSE Partner Program](/monetization#joining-the-viverse-partner-program). Once you are officially a VIVERSE partner, [email the VIVERSE team](mailto:michael_morran@htc.com?cc=brooke_chen@htc.com\&subject=Request%20to%20set%20up%20a%20subscription%20%2F%20premium%20content\&body=Hello%20VIVERSE%20Team%2C%0A%0AI%20would%20like%20to%20request%20help%20setting%20up%20a%20channel%20subscription%20%2F%20premium%20content.%0A%0AHere%20is%20a%20link%20to%20my%20content%3A%20%7Blink%20to%20complete%2C%20viverse-hosted%20URL%7D.%0A%0AHere%20is%20the%20description%20I%20would%20like%20players%20to%20see%20when%20choosing%20whether%20to%20purchase%20my%20content%3A%20%7Bdescription%20of%20what%20your%20content%20offers%20users%7D.%0A%0AI%20would%20like%20to%20list%20my%20content%20for%20%7Binsert%20price%20in%20USD%20for%20content%7D.) with a request for setting up your premium content/subscription. You must include the name of your content / subscription, a description of what it offers users, and your desired cost in USD for the monthly subscription / one time purchase.

For the 1st year, VIVERSE will pay you 90% of net revenue (after processing fees & taxes) before shifting to 80% for the remainder of the time your content is listed.

## Support for Content Purchasers

The [VIVERSE Support Page](https://support.viverse.com/hc/en-us/articles/43360304923277-Purchasing-and-Subscribing-to-Content) is the main resource for players who need help with channel subscriptions or content purchases. However, it is important for creators to know about this process and to be able to help guide their players through the process...

## How Players Purchase Content

Follow these steps to discover and purchase content from your favorite creator...

{% stepper %}
{% step %}

### Go to Creator Profile

Navigate to the profile of the Creator whose content you would like to purchase

<figure><img src="/files/n1FPc5GxosGZAt4a8rhI" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Browse Premium Content

Scroll down to the "Premium Worlds" section of the creator's profile and select the content you would like to purchase

<figure><img src="/files/aEg8voIjJjIHGJ0Q94eL" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Select "Unlock"

When attempting to access premium content that you have not purchased yet, select "Unlock" to initiate a purchase.

<figure><img src="/files/DnXmMX6HgMamYgbMI00k" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Confirm Selection

You will automatically be redirected to the Creator's content options. Confirm that you are selecting the correct world or subscription, and select "Subscribe"/"Purchase" to continue.

<figure><img src="/files/aYShxmlv7lMfz4XmfbvX" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Checkout

You will automatically be redirected to the Creator's listing on marketplace.viverse.com. Select an existing card or add a new one, agree to the VIVERSE terms and conditions, and press "Place Order".

<figure><img src="/files/4ScEn7FaMEhs4uSdtRwX" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Purchase Confirmation

Depending on your payment method, you may need to verify your card's security code. Once successful, you will see the confirmation page and receive an email.

<figure><img src="/files/NF5pBx4KS4NjLwJ5rxZW" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Access Content

You can review and access the content your have purchased/subscribed-to on your VIVERSE profile: <https://worlds.viverse.com/profile>

<figure><img src="/files/qwXD5BCXZygOuApOY6ib" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

## Subscription Cancellation

To cancel a channel subscription, follow these steps...

{% stepper %}
{% step %}

### Go to Creator Profile

Go to the profile of the creator whose subscription you would like to cancel. Select the "Unlock" button and select "Manage Subscription".

<figure><img src="/files/rlEfuk3S6LfTrE4XUR90" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Manage Subscriptions

You will automatically be redirected to <https://market.viverse.com/subscriptions> where you can manage any subscriptions associated with your account, update payment methods, or cancel your subscriptions.

<figure><img src="/files/qDbspyDlvgCBKzmobvYa" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Confirm Cancellation

Confirm your cancellation by selecting "Cancel Subscription".&#x20;

<figure><img src="/files/Fu8Iwz4HGkHmOBZX5EKX" alt=""><figcaption></figcaption></figure>

After confirming, you will see the confirmation screen and receive a confirmation email. You will continue to access any subscribed content until the end of the billing period.

<figure><img src="/files/Xhi2M1d2r3kTdJl46GNM" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### (Optional) Resubscribe

After cancelling, you will see an option to resume a subscription at any time on <https://market.viverse.com/subscriptions>. Simply select "Resume" and confirm!

<figure><img src="/files/bdyiKMATMigmePaGzXH0" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/yilkRcTkbp9eqo86ZB0l" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}


# Introduction to Optimizing for the Web

An overview of why developers should build for the web, how optimizing experiences for the web is different from native, and what characteristics define an optimized experience.

***

## Why Build for the Web?

<h3 align="center"><span data-gb-custom-inline data-tag="emoji" data-code="1f30d">🌍</span> Reach millions of users instantly <span data-gb-custom-inline data-tag="emoji" data-code="1f30d">🌍</span></h3>

<p align="center">An estimated <strong>3 billion people</strong> have access to a 3D-capable web browser and high-speed internet connection<a href="https://datareportal.com/reports/digital-2025-global-overview-report"><sup>0</sup></a><sup>,</sup><a href="https://ngital.com/bangladesh-internet-penetration-2025-data-insights/"><sup>1</sup></a><sup>,</sup><a href="https://africa.businessinsider.com/local/lifestyle/african-countries-with-the-largest-internet-population-in-2025/871gpnf"><sup>2</sup></a><sup>,</sup><a href="https://www.itu.int/itu-d/reports/statistics/2024/11/10/ff24-internet-use/"><sup>3</sup></a><sup>,</sup><a href="https://datareportal.com/reports/digital-2025-sub-section-accelerated-access"><sup>4</sup></a>. Millions of users play video games in the browser each month, providing a wide reach for your 3D experience.</p>

<figure><img src="/files/HXp49H6j2KwiVy9RSujf" alt="" width="375"><figcaption></figcaption></figure>

<h3 align="center"><span data-gb-custom-inline data-tag="emoji" data-code="1f680">🚀</span> Launch on any device with a 3D-compatible web browser <span data-gb-custom-inline data-tag="emoji" data-code="1f680">🚀</span></h3>

<p align="center">3D web apps reach phones, desktop computers, laptop computers, and mixed reality (XR) headsets via the same URL. The vast majority of internet activity happens in 3D-capable browsers (Chrome, Edge, Firefox, and Safari)<a href="https://gs.statcounter.com/browser-market-share"><sup>5</sup></a> running on 3D-capable operating systems (Android, iOS, OSX, Windows)<a href="https://www.gsma.com/r/wp-content/uploads/2024/10/The-State-of-Mobile-Internet-Connectivity-Report-2024.pdf"><sup>6</sup></a>.</p>

{% columns %}
{% column %}

<figure><img src="/files/QPHQdHReLNh1Gv3IBD7V" alt=""><figcaption></figcaption></figure>
{% endcolumn %}

{% column %}

<figure><img src="/files/Cv1SVtTqrAjRRqO0DD1N" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

<p align="center">An estimated 99% of browsers in use support 3D experiences via the WebGL API<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API#api.webgl2renderingcontext"><sup>7</sup></a>.</p>

<figure><img src="/files/XFdJgl5oRxJPOajXqTgC" alt="" width="375"><figcaption></figcaption></figure>

<h3 align="center"><span data-gb-custom-inline data-tag="emoji" data-code="1f510">🔐</span> Secure by default <span data-gb-custom-inline data-tag="emoji" data-code="1f510">🔐</span></h3>

<p align="center">The <a href="https://developer.mozilla.org/en-US/docs/Web/Security">web’s</a> application sandbox, HTTPS, and permissions model let you consume powerful graphics, XR, and platform features with user consent and origin isolation.</p>

<h3 align="center"><span data-gb-custom-inline data-tag="emoji" data-code="1f91d">🤝</span> The same standards for everyone <span data-gb-custom-inline data-tag="emoji" data-code="1f91d">🤝</span></h3>

<p align="center">APIs and technologies like <a href="https://registry.khronos.org/webgl/specs/latest/2.0/">WebGL</a>, <a href="https://www.w3.org/TR/webgpu/">WebGPU</a>, <a href="https://www.w3.org/TR/webxr/">WebXR</a>, <a href="https://www.w3.org/groups/wg/wasm/">WebAssembly</a> (WASM), <a href="https://www.w3.org/Style/CSS/">CSS</a>, and JavaScript are stewarded by mature standards bodies and implemented across major web browsers. There is no store gatekeeping compared to native platforms.</p>

<h3 align="center"><span data-gb-custom-inline data-tag="emoji" data-code="1fab6">🪶</span> No download required <span data-gb-custom-inline data-tag="emoji" data-code="1fab6">🪶</span></h3>

<p align="center">Web experiences do not require users to download an application ahead of time from a store, making it simple for new users to try out your experience and share it with friends.</p>

***

## What makes an experience well-optimized?

{% hint style="info" %}
Developing for the web comes with many advantages, but developers must ensure that the experience performs well on many devices. What's more, there is a large variance of 3D performance within device classes; a mobile user could be on the latest iPhone or an older budget device. While developers instinctively know what an optimized experience feels like, it can be hard to describe. Here, we outline six characteristics that describe a well-optimized experience.
{% endhint %}

{% columns fullWidth="false" %}
{% column %}

### :zap: Fast-loading

Research shows that users are much more likely to return to applications that load quickly. In a well-optimized experience, users can expect to see a loading indicator in milliseconds and interact with the scene in seconds.

### :rock: Stable

A stable scene produces new images at a consistent rate, regardless of frequency. When there are random gaps between new images, the user's immersion is broken, just like when a video buffers.

### :saluting\_face: Responsive

In a responsive experience, the scene updates immediately in response to user inputs. Users expect the camera to track directly with inputs, and can even experience nausea if there is too much latency. Beyond the camera movement, users expect UI elements to respond to clicks and player animations to trigger on button presses. It is easy to break immersion when an experience is not responsive.
{% endcolumn %}

{% column %}

### :ocean:  Fluid

Fluidity, or smoothness, is the characteristic that users typically associate with performance. Fluidity is defined by how a scene looks in motion; in a fluid scene, the user should not be able to perceive the gaps in time between each image, instead perceiving a continuous stream of imagery.

### :mag: Legible

A scene may be smooth and stable, but this does not matter if individual elements in the scene cannot be interpreted. Users must be able to read all UI elements and identify models in motion. This characteristic is most related to screen and texture resolution. High [aliasing](https://en.wikipedia.org/wiki/Aliasing), or blurriness, also reduces legibility.

### :triangular\_ruler: Scalable

Regardless of whether an experience runs in a mobile browser or desktop browser, users expect performance comparable to native applications on that platform. A scalable experience tunes its performance to the device it's running on.
{% endcolumn %}
{% endcolumns %}

***

## Optimizing 3D experiences for the web vs native platforms

While the core principles of game development are universal, building on the web requires additional optimization to ensure that users have a great first impression and keep coming back.

### :bullettrain\_front: Reduce loading times

{% hint style="info" %}
**Users expect web experiences to load more quickly than native experiences.**&#x20;

Native applications are downloaded ahead of launch, making it much easier to achieve fast loading times. Web applications must achieve better launch times while downloading assets after launch.
{% endhint %}

* **Keep assets small** - users have to download assets over their network on each page load. Keeping assets small makes your experience accessible to users with slower internet speeds and speeds up the loading time. The average user has an internet speed of 50 megabits per second (mbps) and each extra second of waiting time increases [bounce rates](https://en.wikipedia.org/wiki/Bounce_rate) by [14%](https://www.tooltester.com/en/blog/website-loading-time-statistics/). Outside of bounce rates, major online retailers, like Amazon, have found that [even small latency increases significantly impact revenue generation](mailto:undefined).
* **Load in assets as you go** - one benefit of the browser is that assets can be continuously fetched over the network. Rather than loading everything in at once, only download it when you need it.
* **Show the user something as soon as you can** - users will be visiting your game from a web page, which typically load very quickly. To keep the experience seamless, make sure to render a loading bar while resources are downloading.
* **Cache assets for future sessions** - modern browsers expose features like IndexedDB and Service Workers that allow developers to store files on a user's device. This enables developers to cache assets on the first launch and load almost instantly on subsequent launches.
* **Reuse assets where possible** - assets can also be cached in memory and reused while the application is running. We can then clone and modify cached meshes at any time, removing the need to redownload a similar mesh. For instance, we may load a single monster mesh that we color and resize instead of downloading multiple meshes with different colors and sizes.

### :desktop: :computer: Prepare for cross-platform use :mobile\_phone::goggles:

{% hint style="info" %}
**The same build runs across mobile, desktop, and XR platforms**

Unlike native applications, where a separate build must be created for each platform, only one build is generated for the web. This simplifies deployment, but it makes it harder to ensure that apps perform well on all platforms. Developers should update performance settings while the app is running to ensure the best experience.
{% endhint %}

* **Set performance settings on launch** - web users expect that the default graphics settings will perform well on launch. Use lower graphics settings on mobile and XR than on desktop.
* **Scale performance as you go** - track performance metrics while the app is running, and scale graphics settings up or down accordingly.
* **Determine the correct input method** - make sure that the correct input format is selected for each platform you support: touch inputs for mobile; keyboard/mouse on desktop; and controllers on XR. Be prepared to detect gamepads and alternate input sources on each platform.
* **Scale resolution according to the device** - users expect UI elements and resolution to scale with the size of the screen. Mobile devices typically run at a lower resolution with larger UI elements, while desktops run at a higher resolution with proportionally smaller UI elements. Mobile devices also support both landscape and portrait mode; ensure that the experience renders well in portrait mode or notify the user that the game must run in landscape mode.

### :radio: Optimize scenes for the browser

{% hint style="info" %}
**Developers must tailor their experience towards running in a WebGL context**

Running applications in the browser adds some overhead for extra security. Developers should take this extra overhead into account when designing their scene.
{% endhint %}

* **Account for WebGL execution overhead** - the browser adds security measures to ensure that APIs like [WebGL](https://registry.khronos.org/webgl/specs/1.0/#ATTRIBS_AND_RANGE_CHECKING) can't be used to run malicious code. This adds some CPU overhead to 3D web applications compared to native applications, reducing the total number of WebGL commands we can execute in a frame. However, once the CPU passes the command to the GPU, rendering performance is largely the same [compared to an OpenGL application](https://docs.unity3d.com/6000.2/Documentation/Manual/webgl-performance.html).
* **Understand how** [**draw calls**](https://howik.com/understanding-draw-calls) **impact performance, and** [**how to reduce them**](/optimization/challenges-and-optimization-strategies#reducing-and-batching-draw-calls) - CPU performance scales with the number of draw calls that occur in each frame. A draw call is a command that the engine sends to the GPU telling it to a draw a series of triangles or pixels. This operation happens quickly on the GPU, but is slow on the CPU, making it advantageous to batch draw calls.
* **Reduce scene complexity** - You may need to reduce mesh counts, reduce [polygon counts](https://gamedev.stackexchange.com/questions/82538/why-are-huge-polygon-amounts-bad), reduce [overdraw](https://www.sceneri.com/sceneri-docs-glossar/overdraw/), remove expensive shaders (e.g. water, fire), remove particle effects, remove full-screen post-processing shaders, and/or remove transparency and reflections.

### :fire\_engine: Select the best engine for the experience

{% hint style="info" %}
**Different web engines are well-suited for different types of experiences**

There are numerous feature-packed 3D rendering engines for the web, like [PlayCanvas](https://playcanvas.com/), [Unity](https://docs.unity3d.com/6000.2/Documentation/Manual/webgl.html), [Three.js](https://threejs.org/), and [Babylon.js](https://www.babylonjs.com/). Each engine has different strengths and weaknesses, explored more in [Overview of 3D Web Rendering Engines](/optimization/overview-of-3d-web-rendering-engines).
{% endhint %}

* **Double-check the supported features** - Built-in engine features generally perform better than home-brewed solutions, since they can be tested and iterated on by a large developer base. If you need features like rigid-body physics or photorealistic lighting, it could be better to develop in an engine that supports them by default, like Unity.
* **Be careful with file size** - Without optimization, fully-featured C++ engines like Unity can have much larger application sizes than JavaScript engines like Babylon.js, Three.js, and PlayCanvas. If you don't need all of the features of Unity, using a javascript-based engine may make bundle size optimization easier. Users on mobile devices typically prefer smaller app sizes, as they may be on a cellular network.
* **Device support** - If you plan to support mobile devices, some engines, like Unity and PlayCanvas, support touch controls out of the box. For more minimal engines like Three.js, you will need to use a third-party solution or implement touch controls yourself. If you plan to support XR, make sure your chosen engine has WebXR support.
* **Developer experience matters** - Developers should use an engine that suits their strengths. If you're already comfortable with Unity, then continue with that engine. If you're looking to jump into JavaScript development, but still want an editor, PlayCanvas might be the right choice. For programming specialists, Three.js and Babylon.js provide more flexibility.

***

## Selected Optimized VIVERSE Experiences

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Horizon Chaser</td><td>Impressive graphics and smooth performance</td><td><a href="/files/L0cq9nk6QvLY6zPS8kJB">/files/L0cq9nk6QvLY6zPS8kJB</a></td><td><a href="https://worlds.viverse.com/TAJTZdc">https://worlds.viverse.com/TAJTZdc</a></td></tr><tr><td>Pet Rescue</td><td>A highly detailed world with intuitive controls on multiple devices.</td><td><a href="/files/0S9avlmP6IOBoY8D2IiU">/files/0S9avlmP6IOBoY8D2IiU</a></td><td><a href="https://worlds.viverse.com/MMuLWbE">https://worlds.viverse.com/MMuLWbE</a></td></tr><tr><td>To the Limbs</td><td>An interactive music video that loads a large and immersive world.</td><td><a href="/files/dI1hpAbceh30SP6Uk3Mq">/files/dI1hpAbceh30SP6Uk3Mq</a></td><td><a href="https://worlds.viverse.com/jtrJ9az">https://worlds.viverse.com/jtrJ9az</a></td></tr><tr><td>Alfi's Adventures</td><td>Fine-tuned performance across mobile and desktop platforms.</td><td><a href="/files/cuDhmuNwk4irBlxqTK9c">/files/cuDhmuNwk4irBlxqTK9c</a></td><td><a href="https://worlds.viverse.com/EcxNxwe">https://worlds.viverse.com/EcxNxwe</a></td></tr></tbody></table>


# Optimization Recommendations and Requirements

Learn about fundamental optimization metrics and the optimization requirements in VIVERSE.

***

## Quantifying the Characteristics of Good Optimization

{% hint style="info" %}
In [Introduction to Optimizing for the Web](/optimization), we discussed the qualitative characteristics of a well-optimized experience: **fast-loading**, **fluid**, **stable**, **responsive**, **legible**, and **scalable**. In this section, we present quantifiable application metrics and scene metrics, and show how these metrics correlate to a well-optimized scene.&#x20;
{% endhint %}

**Application metrics** are the benchmarks against which we measure optimization, and they are generated by browser and engine profiling tools. **Scene metrics** are the levers that developers use to improve the application metrics. These scene metrics correspond to quantifiable properties from the rendering pipeline described above. **Application metrics** tell us what to optimize, **scene metrics** tell us how to optimize. All of these metrics are moderated by device characteristics and other external factors, which affect the scalability and potential reach of your application.

### Application Metrics to Optimize

<table><thead><tr><th width="199.984375">Metric</th><th width="393.80078125">Description</th><th>Target Values</th></tr></thead><tbody><tr><td><a href="https://developer.android.com/topic/performance/vitals/launch-time#time-initial">Time to initial display</a> (TTID)</td><td>The amount of time (in seconds) that it takes to display the first frame of an application from a cold start. In 3D applications, it is important to show a loading bar as soon as possible. This lets the user know that the app is in a loading state and not frozen.</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: &#x3C; 1.5 s<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 1.5 - 4 s<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: > 4 s</td></tr><tr><td><a href="https://developer.android.com/topic/performance/vitals/launch-time#time-full">Time to full display</a> (TTFD)</td><td>The amount of time (in seconds) that it takes for the app to become interactive. For example, this would be the amount of time it takes for the user to gain control of the camera or for clickable UI elements to load. In 3D web applications, it is best to minimize TTFD, as it increases user retention.</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: &#x3C; 10 s<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 10 - 30 s<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: > 30 s</td></tr><tr><td><a href="https://web.dev/articles/inp">Interaction to next paint</a> (INP)</td><td>Measures the amount of time (in milliseconds) between a user interaction and the next frame. This tracks the direct scripting overhead of user interactions. This metric is most relevant to interactions that incur asset loading, shader compilation, or extensive data processing, like entering a new area of the world, as these interactions can cause the app to momentarily freeze.</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: &#x3C; 16 ms<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 16 - 75 ms<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: > 75 ms</td></tr><tr><td><a href="https://en.wikipedia.org/wiki/Frame_rate">Frame rate, or frames per second</a> (FPS)</td><td>The frequency at which frames are rendered to the screen. Applications with higher FPS appear more fluid, while applications with low FPS can look like a slideshow, making it difficult for the user to control their character.</td><td>Mobile &#x26; Desktop<br><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: > 60 fps<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 45 - 60 fps<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: &#x3C; 45 fps<br><br>XR: <br><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: > 72 fps<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 60 - 72 fps<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: &#x3C; 60 fps</td></tr><tr><td><a href="https://www.capframex.com/blog/post/Explanation%20of%20different%20performance%20metrics">5th percentile minimum frame rate</a> (5th %ile FPS)</td><td>High variance in frame time can result in a poor user experience, as this can be perceived as "choppiness" and break immersion. To get a sense of how much choppiness there is an application, we can take the 5th percentile value from <em>n</em> samples of the frame rate. In a stable application, this value will be around 80% of the median FPS.</td><td>Mobile &#x26; Desktop<br><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: > 45 fps<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 30 - 45 fps<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: &#x3C; 30 fps<br><br>XR: <br><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: > 60 fps<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 45 - 60 fps<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: &#x3C; 45 fps</td></tr><tr><td><a href="https://en.wikipedia.org/wiki/Image_quality">Image Quality</a></td><td>Indicates how well the real-time rendered scene matches an idealized version of the scene. A heuristic for this metric is the amount of resolution downscaling that is applied to the canvas relative to the screen. This is described more below. </td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: 100% scaling<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 85 - 100% scaling<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: &#x3C; 85% scaling</td></tr><tr><td>Memory Usage</td><td>Higher memory usage (in megabytes/MB) generally correlates to poorer performance on lower-end devices, which typically have less available system memory (<a href="https://en.wikipedia.org/wiki/Random-access_memory">RAM</a>) and smaller CPU caches. Higher memory usage also correlates to higher <a href="https://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29">garbage collection</a> overhead, which can cause the scene to momentarily freeze. If too much memory is used, the browser tab will crash.</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span>: &#x3C; 800 MB<br><span data-gb-custom-inline data-tag="emoji" data-code="1f7e1">🟡</span>: 800 - 1600 MB<br><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span>: > 1600 MB</td></tr></tbody></table>

### Scene Metrics to Optimize

<table><thead><tr><th width="200.12890625">Metric</th><th>Description</th></tr></thead><tbody><tr><td>Asset sizes</td><td>By decreasing the file size of static assets (models, textures, code), users can improve the loading time of the application (TTID, TTFD) and time spent loading new areas. Asset sizes can be improved by <a href="https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/decimate.html">decimating meshes</a>, <a href="https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images">reducing texture size</a>, and <a href="https://www.geeksforgeeks.org/javascript/how-to-minify-javascript/">minifying code</a>.</td></tr><tr><td><a href="https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html">Framebuffer resolution and scale</a></td><td><p>There are 3 types of resolution that impact image quality: <strong>framebuffer resolution</strong>, <strong>window resolution</strong>, and <strong>device resolution</strong>. The <strong>framebuffer resolution</strong> of an application is the number of pixels that are output into the framebuffer, represented as a (width, height) pair. This is distinct from the <strong>window resolution</strong>, which is the number of <a href="https://developer.mozilla.org/en-US/docs/Glossary/CSS_pixel">CSS pixels</a> displayed to the user. <strong>Framebuffer scale</strong> represents the ratio of framebuffer resolution divided by window resolution, where higher values represent a more legible image. Lowering framebuffer scale can increase FPS at the expense of image quality. </p><p></p><p><strong>Device resolution</strong> is the number of <a href="https://developer.mozilla.org/en-US/docs/Glossary/Device_pixel">physical pixels</a> in the screen of the device. On devices with high pixel density, multiple physical pixels will make up a single CSS pixel, resulting in a higher quality image. The ratio of phyiscal pixels to CSS pixels is measured by the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio">devicePixelRatio</a> API.</p></td></tr><tr><td>Texture resolution</td><td>The resolution of a texture dictates how legible corresponding models, text, and shader effects are in the application. Reducing texture resolution can increase FPS by decreasing image quality. This is distinct from the downloaded texture size, as high-resolution textures can be downsampled at runtime, providing an option for runtime scalability.</td></tr><tr><td><a href="https://toji.dev/webxr-scene-optimization/#reducing-draw-calls">Draw call count</a></td><td>In 3D applications, <a href="/pages/akqJMtAh1PsNpzC3s6WB#optimize-scenes-for-the-browser">draw calls</a> tend to be the primary bottleneck. <a href="https://howik.com/understanding-draw-calls">Draw calls are very CPU intensive</a>, so FPS inversely scales with the number of draw calls in the scene. To reduce draw calls, developers can reuse materials across meshes, merge static meshes, and use instancing to clone meshes.</td></tr><tr><td>Scripting overhead</td><td>The amount of time spent each frame running application logic (in milliseconds). In a 3D experience, some time is spent each frame transforming meshes, responding to user inputs, and updating animations. Developers must optimize this per-frame scripting time to be as small as possible to ensure higher FPS. Intermittent scripting times increases are also a primary contributor to low 5th percentile minimums, as lots of application logic may need to be run when entering a new area or processing a large amount of incoming data.</td></tr><tr><td><a href="https://docs.unity3d.com/6000.0/Documentation/Manual/SL-ShaderPerformance.html">Shader overhead</a></td><td>Time spent in shaders is difficult to measure directly, but it can be inferred from the total amount of time spent on the GPU. To reduce shader time, developers can remove expensive functions (pow, log, sin), use built-in functions, use lower precision variables, and avoid repeated calculations.</td></tr></tbody></table>

### External Factors Affecting Scalability

Application metrics are affected by factors outside of a developer's control. In particular, a developer cannot control the hardware specifications (CPU + GPU + RAM) or internet connection that the experience will run on, making it difficult to optimize the experience for all users. Developers should aim to provide a good experience on the median hardware specs for each platform (mobile, desktop, XR) that their experience supports and adjust asset sizes according to median network bandwidth. A good approximation for this is to target a flagship device that is a few years old (an iPhone 12 or Galaxy s20 in 2025), and to expect around 50 mbps network bandwidth.

{% tabs %}
{% tab title="Device Class" %}
3D capable devices are typically split into 3 categories: Mobile (Android and iOS tablets and phones), Desktop (laptop and desktop computers), and recently XR (augmented and virtual reality headsets). Desktops are  the most powerful, and can render experiences at higher fidelity than mobile devices and XR headsets. If you plan to support multiple platforms, expect to optimize code and assets for each platform.
{% endtab %}

{% tab title="CPU" %}
A [CPU's](https://en.wikipedia.org/wiki/Central_processing_unit) speed is determined by three main factors: **clock speed**, core **count**, and **cache size**. Clock speed determines how fast an individual function can complete on a CPU, higher clock speeds corresponding to faster execution. Higher cache sizes allow the CPU to access data very quickly, avoiding relatively expensive RAM operations. However, leveraging cache size often relies on careful optimization. Core count determines how much parallelism an application can leverage. A program can theoretically cut scripting time proportionally to the number of cores, but this requires additional optimization with Web Workers or WebAssembly. Increasing CPU speed allows for more complex scripting and higher draw call counts. Loading times also tend to increase, as assets are processed more quickly once they are downloaded.
{% endtab %}

{% tab title="GPU" %}
A [GPU's](https://en.wikipedia.org/wiki/Graphics_processing_unit) speed is determined by memory size (**VRAM**), **GPU clock speed**, and **core count**. For GPUs, core count and memory size are more important than clock speed, as GPUs have to perform many operations in parallel. Having more cores and more memory allow GPUs to process larger textures and more vertices with higher precision. Increased clock speeds typically decrease the amount of time spent in shaders, though this is typically only a bottleneck with very expensive shaders.
{% endtab %}

{% tab title="RAM" %}
The amount of system memory, or [RAM](https://en.wikipedia.org/wiki/Random-access_memory), that a user's device has will limit the types of experience that they are able to run. RAM is used to keep active program data readily available, so more complex experiences will require more RAM. Typical mobile devices will have 1GB - 8GB of RAM, which is enough to run smaller experiences with good performance. Desktop and laptop computers typically have 8GB - 32GB of RAM, which is enough to run medium experiences. The highest-quality experiences require 16+ GB of available RAM.
{% endtab %}

{% tab title="Network" %}
A user's network speed is out of a developer's control. If a user does has less than 1 MB/s internet speed, they will have trouble accessing 3D experiences in a reasonable time. Nevertheless, developers should tune asset sizes such that users with a *wide* range of internet speeds can access their experience. Developers can also take use of progressive loading to download assets throughout the experience runtime.
{% endtab %}
{% endtabs %}

### Correlating Qualitative Characteristics to Metrics

<table><thead><tr><th width="176.875" valign="top">Characteristic</th><th valign="top">Application Metrics</th><th valign="top">Scene Metrics</th><th valign="top">External Factors</th></tr></thead><tbody><tr><td valign="top">Fast Loading</td><td valign="top"><ul><li>TTID</li><li>TTFD</li><li>INP</li></ul></td><td valign="top"><ul><li>Asset Sizes</li></ul></td><td valign="top"><ul><li>Network Speed</li><li>CPU Clock Speed</li></ul></td></tr><tr><td valign="top">Fluid</td><td valign="top"><ul><li>FPS</li></ul></td><td valign="top"><ul><li>Draw Call Count</li><li>Framebuffer Resolution</li><li>Scripting Overhead</li><li>Shader Overhead</li><li>Texture Resolution</li></ul></td><td valign="top"><ul><li>CPU Clock Speed</li><li>GPU VRAM</li><li>GPU Clock Speed</li></ul></td></tr><tr><td valign="top">Stable</td><td valign="top"><ul><li>5th Percentile Frame Rate</li><li>Memory Usage</li></ul></td><td valign="top"><ul><li>95th percentile shader overhead</li><li>95th percentile scripting overhead</li><li>95th percentile draw call count</li></ul></td><td valign="top"><ul><li>RAM</li><li>CPU Clock Speed</li><li>GPU VRAM</li><li>GPU Clock Speed</li></ul></td></tr><tr><td valign="top">Responsive</td><td valign="top"><ul><li>Interaction to Next Paint (INP)</li></ul></td><td valign="top"><ul><li>Maximum scripting overhead</li><li>Maximum shader overhead</li><li>Maximum draw call count</li></ul></td><td valign="top"><ul><li>CPU Clock Speed</li></ul></td></tr><tr><td valign="top">Legible</td><td valign="top"><ul><li>Image Quality</li></ul></td><td valign="top"><ul><li>Texture resolution</li><li>Framebuffer scaling</li></ul></td><td valign="top"><ul><li>Screen Resolution</li><li>Screen Size</li></ul></td></tr><tr><td valign="top">Scalable</td><td valign="top"><ul><li>For scalability, validate all application metrics on each device class you plan to support (mobile, XR, desktop)</li></ul></td><td valign="top"><ul><li>Texture resolution may need to be tuned for each device</li><li>Draw call count may need to be tuned for each device</li></ul></td><td valign="top"><ul><li>Device Classes Supported</li></ul></td></tr></tbody></table>

***

## Optimization Requirements for VIVERSE

Coming soon...


# The Basics of 3D in the Browser

Learn the fundamentals of browser rendering.

***

## Introduction

3D rendering engines are an abstraction around rendering APIs that simplify the process of drawing meshes on the screen and simulating the lighting of the meshes. In the web, rendering engines are composed of:

* A [rendering pipeline](https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview) built on top of a browser rendering API, which updates every frame.
* A developer-facing API for initializing and updating meshes, materials, cameras, and lights, written in a browser-supported programming language.
* Abstractions for connecting the output of the rendering pipeline to the browser window.
* Abstractions for updating the scene in response to user inputs received from browser APIs.

<figure><img src="/files/QKSJ2RCZiw9rTHXSFviP" alt="Rendering Image Design: User Input -> State Updates -> Rendering -> Output" width="563"><figcaption></figcaption></figure>

## :art: Core Browser Technologies: WebGL and HTMLCanvas

{% hint style="info" %}
Modern web browsers, such as Safari, Chrome, Firefox, and Edge, implement a set of standardized Graphics APIs and HTML components that collectively support rendering 3D applications in a web page.&#x20;
{% endhint %}

* The primary browser graphics API is [**WebGL**](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API), which is based on the native OpenGL graphics API. Over the past two years, engines browsers have also begun to support [**WebGPU**](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API), a more modern graphics API that integrates tightly with powerful native APIs like [Vulkan](https://www.vulkan.org/), [Metal](https://developer.apple.com/metal/), and [DirectX 12](https://learn.microsoft.com/en-us/windows/win32/directx). The purpose of a graphics API is to provide an interface between the CPU, which constructs an abstract representation of a scene, and the GPU, which renders the abstract scene representation to an image.
* The GPU outputs the rendered image as a list of color values into a special memory allocation known as the [**framebuffer**](https://en.wikipedia.org/wiki/Framebuffer). In order to display the image to the user, the browser provides an HTML component called the [**canvas**](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/canvas). This canvas component occupies a rectangular area of a webpage, and displays the output from the GPU. In practice, the developer constructs a canvas, and then accesses the appropriate graphics API (WebGL or WebGPU) through a reference to the canvas.

## :desktop: Programming Language Support in Web Rendering Engines

{% hint style="info" %}
The only officially supported browser language is [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript). To execute non-JavaScript code directly in the browser, it must be compiled to a binary format known as [WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly), where it can be directly executed by a virtual machine in the browser.
{% endhint %}

There are two main approaches to building rendering engines for the web:

* Compiling an existing engine written in a low-level languages (e.g. C, C++, Rust) to [WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly). This approach is taken by engines like [Unity](https://docs.unity3d.com/2020.3/Documentation/Manual/webgl.html), [Godot](https://docs.godotengine.org/en/stable/about/list_of_features.html#platforms), and [Bevy](https://bevy.org/examples/).
* Writing the engine entirely in JavaScript. This approach is used by engines like [PlayCanvas](https://playcanvas.com/), [Three.js](https://threejs.org/), and [Babylon.js](https://www.babylonjs.com/).

Each approach has certain tradeoffs:

<table><thead><tr><th width="142.6875">Tradeoff</th><th>WebAssembly</th><th>JavaScript</th></tr></thead><tbody><tr><td>Browser Compatibility</td><td><a href="https://webassembly.org/docs/web/">Browser APIs are generally not available to the WebAssembly runtime</a>; to interface with the browser, WebAssembly code must call a JavaScript function that then calls the corresponding browser API. Sending commands and data over this reflection layer can be expensive, negating some of the benefits of the theoretically faster low-level WebAssembly code. </td><td>JavaScript can directly call all browser APIs.</td></tr><tr><td>Performance Ceiling</td><td>WebAssembly runs at near-native speeds, enabling a very high performance ceiling.</td><td>While modern JavaScript engines like <a href="https://v8.dev/">V8</a> and <a href="https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html">JavaScriptCore</a> are capable of running graphics applications, low-level languages like C++ and Rust have an edge in pure performance. In particular, JavaScript is single-threaded, uses automatic memory management, and cannot leverage <a href="https://en.wikipedia.org/wiki/Single_instruction%2C_multiple_data">Single-Instruction Multiple Data</a> parallelization in the browser, putting a theoretical ceiling on performance. Some multi-threading capability is available in the browser via the <a href="#reducing-scripting-overhead-with-multi-threading">web worker API</a>.</td></tr><tr><td>Debugging</td><td>WebAssembly debugging support is lacking across major browsers. Developers will often need to rely on debugging tools provided by the engine, making it hard to fix web-specific bugs.</td><td>Profiling and debugging tools are built into the browser, making it relatively easy to identify the root cause of performance problems and bugs.</td></tr><tr><td>Deployment</td><td>WebAssembly requires an additional compilation step in the deployment process. The entire game engine must be compiled with the build.</td><td>JavaScript applications can be deployed directly. Developers can also reduce bundle sizes by stripping unused code and <a href="https://github.com/mishoo/UglifyJS">minifying</a> the JavaScript code.</td></tr></tbody></table>

The choice of engine is not as clear-cut as performance vs. usability. Given the complex nature of rendering engines, it is very difficult to build comprehensive performance benchmarks proving one engine is faster than another. It is more important to choose an engine based on the type of experience you want to build and your strengths as a developer.

## :construction\_site: Building a Rendering Pipeline with WebGL

Graphics APIs like WebGL and WebGPU are used to construct a [**rendering pipeline**](https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview), a program that defines the steps that the underlying native Graphics API ([OpenGL](https://www.opengl.org/), [Vulkan](https://www.vulkan.org/), [Metal](https://developer.apple.com/metal/), [DirectX](https://learn.microsoft.com/en-us/windows/win32/directx)) must take to render an object to the framebuffer.&#x20;

Here is a code sample of a rendering pipeline implemented in WebGL that draws a single triangle to the screen.  Below, we describe how WebGL and the canvas interact with the GPU to draw the triangle onto the screen.

{% embed url="<https://codepen.io/Henry-Allen/embed/PwPezyg>" %}

{% hint style="info" %}
Toggle the "JS" and "HTML" tabs to view the usage of the WebGL and the canvas APIs, respectively.
{% endhint %}

{% stepper %}
{% step %}

#### **Creating the Canvas**

The output of the rendering pipeline is rendered directly into the browser via the Canvas API:

```html
<canvas id="myCanvas" width="400" height="400">
    The Rendering Canvas (Alt Text)
</canvas>
```

{% endstep %}

{% step %}

#### Setting Up WebGL State In JavaScript

Before anything happens on the GPU, the developer must set up the pipeline in JavaScript. This consists of: setting up **shaders**, small functions that run on the GPU in parallel to process each triangle and pixel we want to render; allocating **vertex buffers**, which contain data for each vertex, a point in 3D space that makes up a mesh; and describing the layout of vertex buffers, as a vertex buffer can contain position data, color data, etc., laid out in any fashion. After this state is set up, the code submits the frame to the GPU, where the rendering pipeline begins.
{% endstep %}

{% step %}

#### Vertex Processing

3D scenes consist of many vertices in 3D space, which make up lines and triangles, which in turn compose into more complex shapes. In the first step of the rendering pipeline, each vertex in an array of vertices are processed by a "vertex shader", a small program that determines the position and color of a particular vertex given some input properties.
{% endstep %}

{% step %}

#### Primitive Assembly and Clipping

Each vertex may be part of one or more primitives (point, line, or triangle) in the scene. At this stage, the pipeline generates an array of primitives from the vertices and clips all primitives that extend outside of the camera view.
{% endstep %}

{% step %}

#### Rasterization

The processed primitives are then *rasterized*, or converted into a sequence of fragments. Whereas primitives represent a shape in 3D space, fragments represent the projection of 3D shapes into 2D space. Imagine taking a picture on a digital camera; the 3D scene you are capturing is recorded by a series of sensors, which record light information from 3D space. A fragment loosely corresponds to one of these sensors; it consists of 2D position data for a point and data interpolated from the vertices that contribute to that point. This fragment data is output to the next stage.
{% endstep %}

{% step %}

#### Fragment processing

Each fragment is processed by a fragment shader, which determines the final color of each value in the framebuffer.
{% endstep %}

{% step %}

#### Canvas Output

Finally, the framebuffer is drawn onto the canvas in the browser window. View a complete example of the rendering pipeline in this [codepen](https://codepen.io/Henry-Allen/pen/PwPezyg). You can look at the exact JavaScript and HTML used to render a triangle to the screen.
{% endstep %}
{% endstepper %}

## :camera\_with\_flash: Engine Abstractions: Meshes, Materials, Cameras, and Lights

Rendering API code is often regarded as verbose and unapproachable, so some abstractions are built on top to make things easier:

* At the highest level of abstraction is the **scene**, a tree-like data structure that defines the hierarchy of elements that are rendered to the screen.&#x20;
* The scene is composed of **meshes**, collections of triangles that from a shape, and have some position, scale, and rotation in the world.&#x20;
* Each mesh has a **material**, which defines how the mesh responds to **lights** in the scene. Materials may render just a solid color, can render a **texture**, or can imitate real-life material properties, like wood or skin.&#x20;
* A **camera** renders the scene from a particular perspective, which is then output to the browser window via the Canvas API.&#x20;

We might construct a scene like this:

```javascript
// Example of initializing a scene and starting the render loop
function runApp() {
  const scene = engine.createScene();

  const camera = scene.createCamera();
  camera.position = { x: 0, y: 4, z: -15 };
  camera.lookAt({ x: 0, y: 0, z: 0 });

  const light = scene.createAreaLight();
  light.color = { r: 1, g: 1, b: 1 };

  const basicMaterial = scene.createStandardMaterial();
  basicMaterial.color = { r: 1, g: 0, b: 0 };

  const box = scene.createBox();
  box.material = basicMaterial;

  const boxInstance1 = box.createInstance();
  boxInstance1.position = { x: 1, y: 1, z: 0 };
  boxInstance1.scale = { x: 2, y: 1, z: 0 };

  const boxInstance2 = box.createInstance();
  boxInstance2.position = { x: -1, y: -1, z: 0 };

  // RequestAnimationFrame is a browser API that fires a callback on every browser fram
  requestAnimationFrame(function() {
    // Re-render the scene every frame
    render(scene);
  })
}
```

In an engine like Babylon.js, we can create a similar scene in a [playground](https://playground.babylonjs.com/#2KET78#1) and produce the following result:

<figure><img src="/files/Z1OmWNIIVQEp7RvLAbIQ" alt="" width="375"><figcaption></figcaption></figure>

Behind the scenes, the engine will compose a rendering pipeline from these instances, lights, and camera in the `render` function. In most engines, the `render` function will look something [like this](https://toji.dev/webgpu-gltf-case-study/#rendering-with-webgl):

```js
// Heavily-abstracted render loop. This runs every frame.
function render(scene) {
  for (let material of scene.materials) {
    // Initialize Shaders using gl.createShader(), gl.compileShader(), etc.
    InitializeShadersAndTextures(material, scene.camera, scene.lights);

    for (let mesh of scene.meshes.filter(
      (mesh) => mesh.material === material
    )) {
      // Create a WebGL buffer for the mesh vertices using gl.createBuffer()
      InitializeMeshBuffers(mesh);

      for (let instance of mesh.instances.values()) {
        // For each instance of the mesh, set the appropriate color and position
        SetInstanceValues(instance);

        // Render the instance to the screen using gl.drawArrays()
        Draw();
      }
    }
  }
}
```

## :woman\_technologist: Updating the Scene in Response to User Inputs in the Browser

Generally, users need to interact with the program in some way. The browser exposes a few APIs to allow controlled access to the mouse, keyboard, touch, and gamepad events:

<table><thead><tr><th width="200.3359375">Input Method</th><th>API</th><th>Platform Support</th></tr></thead><tbody><tr><td><strong>Touch</strong></td><td><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Touch_events">Touch Events API</a>: Responds to swipe, tap, and multitap interactions</li></ul></td><td><ul><li>Mobile</li><li>Chrome, Edge Desktop</li></ul></td></tr><tr><td><strong>Keyboard/Mouse</strong> </td><td><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent">KeyboardEvent API</a>: Responds to key strokes </li><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent">MouseEvent API</a>: Responds to mouse clicks and mouse movements</li></ul></td><td><ul><li>Mobile</li><li>Desktop</li></ul></td></tr><tr><td><strong>Gamepad</strong> </td><td><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API">Gamepad API</a>: Detects when controllers are connected, and determines the controller layout</li></ul></td><td><ul><li>Mobile</li><li>Desktop</li></ul></td></tr><tr><td><strong>XR Headset and Inputs</strong></td><td><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API">WebXR API</a>: Provides abstractions for handling headset movement and hand/gamepad inputs.</li></ul></td><td><ul><li>XR</li></ul></td></tr><tr><td>Pointer</td><td><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events">Pointer Events API</a>: Treats mouse, touch, and gamepad events as generic "pointer" events. This simplifies responding to basic click and hover events.</li></ul></td><td><ul><li>Mobile, Desktop, XR</li></ul></td></tr></tbody></table>

## :books: More Learning Resources

General WebGL and WebGPU tutorials:

1. [Canvas Tutorial](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial)&#x20;
2. [Learning WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/By_example)&#x20;
3. [WebGL Fundamentals](https://webglfundamentals.org/):&#x20;
4. [WebGPU Fundamentals](https://webgpufundamentals.org/)


# Challenges and Optimization Strategies

Learn about the fundamental difficulties of optimizing in the browser and how to address them.

***

## The Challenges of Optimizing for the Browser

The browser makes certain tradeoffs that make it difficult to achieve the target application metrics laid out in the previous section. In particular, the browser prioritizes security over performance, making it harder to achieve FPS targets on mobile and XR platforms. Additionally, loading time is dependent on network speed, since applications are re-downloaded on every launch. This section seeks to identify common problems developers must overcome when developing for browsers.

### :scales: Performance Differences between WebGL and Native Rendering APIs

By default, rendering engines like Three.js, Babylon.js, Unity, and PlayCanvas use the [WebGL 2 API](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API#webgl_2) to take advantage of hardware-accelerated graphics in the browser. WebGL 2 largely conforms to the Open GL ES 2.0 standard and dispatches commands to the GPU, meaning that the performance of a WebGL API call is very close to the corresponding native OpenGL call. However, there are some limitations to WebGL 2 in comparison to native graphics APIs:

* **Dispatching Overhead**: There is [overhead when dispatching WebGL calls on the CPU](https://docs.unity3d.com/6000.3/Documentation/Manual/webgl-performance.html), as the WebGL API call must be translated into the correct native graphics API call. Further, the browser implements more security checks than native code to prevent things like [Out of Range Memory Accesses](https://www.khronos.org/webgl/wiki/Main%20Page/cms/security). This slower dispatching limits the number of draw calls that can be performed in the browser.
* **Missing Modern Features**: Because WebGL is based on OpenGL ES 2.0, it lacks many features that modern graphics APIs like DirectX 12, Metal, and [Vulkan can provide](https://github.com/KhronosGroup/Vulkan-Samples/blob/main/samples/vulkan_basics.adoc), limiting the theoretical performance of an experience. The WebGPU browser API does support many of these features, but WebGPU support is experimental in Safari and Firefox. Major engines like [Three.js](https://github.com/mrdoob/three.js/issues/28968), [Babylon.js](https://doc.babylonjs.com/setup/support/webGPU/webGPUStatus/#features-not-working-because-not-implemented-yet), [PlayCanvas](https://blog.playcanvas.com/initial-webgpu-support-lands-in-playcanvas-engine-1-62/), and [Unity](https://docs.unity3d.com/Manual/WebGPU.html) provide WebGPU support, though support for the [WebXR API in WebGPU](https://github.com/immersive-web/WebXR-WebGPU-Binding/blob/main/explainer.md) is still being standardized.

### :brain: Memory Management in 3D Web Applications

3D applications running in the browser can be very sensitive to JavaScript Garbage Collection (GC) pauses. [Garbage Collection](https://en.wikipedia.org/wiki/Garbage_collection_\(computer_science\)) is an automatic memory management technique used in many programming languages, including JavaScript. This technique helps avoid issues like memory leaks and dangling pointers, but has some performance overhead. When memory usage is high in a particular frame, the resulting garbage collection step will freeze the main frame until it completes. In Web applications, this freeze can result in dropped frames, affecting user experience.&#x20;

Although engines like Unity use garbage collection in [C# scripting contexts](https://docs.unity3d.com/6000.1/Documentation/Manual/performance-garbage-collector.html), the underlying engine is written in C++, which can take advantage of manual memory management in native contexts. Engines written in JavaScript, like Three.js, do not have this advantage, and developers must be very careful about allocating memory and reusing resources like Vectors and Arrays.

#### Why is Garbage Collection Expensive?

Modern browsers use the "mark-and-sweep" algorithm to implement garbage collection. This algorithm is synchronous and scales linearly with the amount of memory in use. As a result, the app will momentarily freeze while cleaning up a large amount of unused memory. The mark-and-sweep algorithm consists of two phases: a depth-first search of memory usage that marks all reachable objects and a sweep pass that releases all unreachable memory. The mark-and-sweep algorithm works like this:&#x20;

```javascript
// A global tree structure that contains links to reachable objects
let root = {} 

// A global data structure containing a reference to all allocated objects
let jsHeap = []

function mark(node) {
    if (!node.marked) {
        node.marked = true
        
        for (const referencedNode of node.references) {
            mark(referencedNode)
        }
    }
}

function sweep() {
    for (const node of jsHeap) {
        if (node.marked) {
            node.marked = false
        } else {
            jsHeap.release(node)
        }
    }
}

function garbageCollect() {
    mark(root)
    sweep()
}
```

This approach prevents memory leaks and dangling references, but has overhead in comparison to manual memory management techniques. In manual memory management, the allocated objects would be freed when they are no longer used, removing the need for a separate pass over all of the objects in the tree.

### :zap: App Startup Time

Startup time is limited by network bandwidth in the browser. This contrasts with native apps, where assets and source code are typically downloaded on the initial install, or in explicit updates to the app.&#x20;

Research show that bounce rates [increase by 123% if an application takes more than 10 seconds to load](https://www.tooltester.com/en/blog/website-loading-time-statistics/), and 53% of users will leave a webpage if it takes more than 3 seconds to load.  Given that worldwide network bandwidth is conservatively [50 mbps](https://www.speedtest.net/global-index#mobile), developers should aim for JavaScript bundle sizes under 5 MB to achieve a 1.5 second TTID. To achieve a TTFD under 10 seconds, developers should aim for 40 MB or less of total assets (code, textures, audio, and models).

Refer to [Optimizing for the Web](broken://pages/Oy7laS7LpHitbo51HRhg) for more details on optimizing assets for the web.&#x20;

### :mobile\_phone: Optimizing for Mobile Devices

Web development is distinct from native development because the same build runs on all devices, meaning a single build must support mobile and desktop platforms. This presents the following challenges to developers:

* **Mobile devices have less powerful hardware than desktop computers**: This means that experiences that run at 60 FPS on desktop computers may require additional optimization to run on mobile phones at 60 FPS.
* **Builds must scale performance automatically**: This means that your application may need to provide multiple scene qualities that it can fall back to, and multiple asset configurations that can be selected from at runtime.
* **Mobile devices can have a wide range of screen sizes**: This makes choosing texture size and font size more challenging, as a user on a tablet expects a different experience from a user on a phone. Developers must detect the window size and device pixel ratio at runtime to ensure that the correct texture sizes and font sizes are used.

### :goggles: Optimizing for XR Devices

The browser enables XR applications through the [WebXR API](https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Fundamentals), meaning that a mobile or desktop experience can run on an XR headset with some additional work. Developing XR experiences is rewarding, as it allows users to experience a level of immersion not afforded by flat displays. However, it may take a lot of work to properly optimize a 3D web build for WebXR. WebXR development places the following unique constraints on developers:

* **Higher FPS Thresholds**: A good performance target is a [stable 72 frames per second](https://developers.meta.com/horizon/resources/vrc-quest-performance-1) (fps), with minimums of 60 fps. This contrasts with development for PC or Consoles, where 60+ fps is preferred, but users can comfortably play games at 30 fps.
* **Higher Stability Requirements**: It is important to limit screen tearing and dropped frames, as desynchronization in head tracking or dropped frames can cause nausea.
* **Binocular Rendering**: XR experiences are more expensive to render than flat 3D experiences. This is because the scene needs to be [rendered twice](https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Rendering#the_optics_of_3d): once from the left eye and once from the right eye. While much of the rendering work can be shared (see: [multiview](https://developer.mozilla.org/en-US/docs/Web/API/OVR_multiview2)), there is unavoidable overhead associated with rendering for both eyes.
* **Spatial Tracking Overhead**: In an immersive experience, some portion of each frame is dedicated to [updating the real-world position of the headset](https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Spatial_tracking) and the control inputs. This is more expensive than reading inputs in a flat 3D experience, as the headset must multiplex signals from accelerometers, cameras, and other sensors to determine where the user is in the world. This real-world position must then be mapped to a position in the simulated world.
* **Scene Understanding Overhead**: Some WebXR experiences allow interactions between objects in the simulated world and objects in the real world. For example, a virtual tennis ball could bounce off your actual floor. Performing this simulation is expensive, as the device must estimate a 3D collision geometry for the floor, again processing large amounts of sensor data in real time.
* **Mobile-class Hardware**: Most XR headsets run on mobile chipsets like the [Qualcomm Snapdragon XR2 Gen 2](https://www.qualcomm.com/products/mobile/snapdragon/xr-vr-ar/snapdragon-xr2-gen-2-platform), which are typically less powerful than those found in desktops, consoles, and laptops (though there is a wide variance in all of these devices). This means that an experience that runs at a stable 60 fps on a mid-range laptop may run at a much lower framerate on an XR headset. It is recommended to [throttle your CPU](https://developer.chrome.com/docs/devtools/settings/throttling/) in your web browser while profiling performance.

***

## Optimization Methods for the Web

We recommend the following approach to optimizing an application for the web:

{% stepper %}
{% step %}

#### Profile the Application

Before doing any optimization work, be sure to profile your application in the browser on each of the devices you support. If you do not have access to a particular device, most desktop browsers allow you to emulate other devices. Once you've generated application metrics, use built-in engine tools to profile your scene. Together, these metrics should give you the appropriate direction for what to optimize, whether it be scripting performance, draw calls, or shader performance. In [Profiling in the Browser](/optimization/profiling-in-the-browser), we provide resources for profiling specific browsers, engines, and devices.
{% endstep %}

{% step %}

#### Perform Engine-Specific Optimizations

Many optimization techniques from traditional game development still apply to Web engines; for instance, [object pooling](https://en.wikipedia.org/wiki/Object_pool_pattern), [shader optimization](https://docs.unity3d.com/6000.1/Documentation/Manual/SL-ShaderPerformance.html), and instancing are still valid techniques. However, explicit techniques typically vary for each game engine; for example, object pooling may be more effective in some engines than others. Refer to [Overview of 3D Web Rendering Engines](/optimization/overview-of-3d-web-rendering-engines) for specific optimization techniques for major web engines.
{% endstep %}

{% step %}

#### Optimize the Scene for the Browser

A developer may need to tailor their experience specifically for mobile chipsets, using lower polygon counts, reducing draw calls, enabling GPU instancing, removing unnecessary shadows on dynamic lights, adding lightmaps, and reducing the number of dynamic lights in a scene. Assets should be optimized, as they need to be downloaded over the network on application start.
{% endstep %}

{% step %}

#### Leverage Browser Technologies to Improve Performance

Web development optimization requires the use of unique browser APIs, such as WebGL, WebWorkers, and WebAssembly. See the section below for leveraging browser APIs in 3D experiences.
{% endstep %}
{% endstepper %}

### :city\_dusk: Optimizing Scenes for Browsers

In addition to leveraging browser APIs to improve performance, developers must also tailor their experiences towards the browser and the devices that web applications typically run on. In the next pages we will cover engine-specific techniques for implementing these optimizations.

#### Reducing and Batching Draw Calls

The first technique to try before lowering the quality of a scene is reducing the number of draw calls per frame. In general, draw calls can be reduced with the following techniques:

* Reusing a single material across different meshes by using a [texture atlas](https://en.wikipedia.org/wiki/Texture_atlas) or [array textures](https://www.khronos.org/opengl/wiki/Array_Texture). Implementation details typically vary by engine.
* Merging static meshes that use the same material into a single material. This [blog post](https://toji.dev/webxr-scene-optimization/#merging-by-material) provides a detailed example of how this can be done in asset creation tools like [Blender](https://www.blender.org/download/).
* Using [hardware instancing](https://webglfundamentals.org/webgl/lessons/webgl-instanced-drawing.html) to draw meshes with the same geometry in a single draw call.
* Leveraging [level of detail](https://en.wikipedia.org/wiki/Level_of_detail_\(computer_graphics\)) (LOD) systems.
* Culling meshes that are not visible with [occlusion queries](https://www.khronos.org/opengl/wiki/Query_Object#Occlusion_queries).

#### Reusing Objects to Reduce Memory Usage

In experiences with high memory usage, it is important to reuse allocated memory objects and hardware objects wherever possible. As an example, a developer may want to avoid reallocating vector objects in a loop, as this can drastically increase the chance of a GC pause.

:x: Without Reuse: 1 million vector objects are created, all of which must be cleaned up by the garbage collector

```javascript
const positionBuffer = mesh.getVertexBuffer('position');
for (let x = 0; x < 1000; x++) {
    for (let y = 0; y < 1000; y++) {
        const position = vec3(x, y, 0);
        position.copyTo(positionBuffer, 3 * (x * 1000 + y);
    }
}
```

:white\_check\_mark: With Reuse: A single temporary vector is created, minimizing garbage collector overhead

```javascript
const positionBuffer = mesh.getVertexBuffer('position');
const tempPosition = vec3(0, 0, 0);
for (let x = 0; x < 1000; x++) {
    for (let y = 0; y < 1000; y++) {
        tempPosition.set(x, y, 0);
        tempPosition.copyTo(positionBuffer, 3 * (x * 1000 + y);
    }
}
```

#### Reducing Scene Complexity

If draw calls can not be batched further and performance does not match expectations, the developer should consider reducing the complexity of the scene. This can include:

* Reducing the number of meshes in the scene.
* Removing transparency from meshes.
* Removing reflections from the scene.
* Using static [lightmaps](https://en.wikipedia.org/wiki/Lightmap) instead of dynamic lights where possible.

#### Reducing Visual Fidelity

If the application is GPU bound, i.e. the application spends a significant portion of time on the GPU, the developer should reduce the visual fidelity of the app. This can include:

* Optimizing meshes with tools like [meshoptimizer](https://meshoptimizer.org/).
* Reducing the size of textures.
* Reducing the number of dynamic lights in the scene.
* Removing shadows from dynamic lights that do not need them.
* Simplifying mesh materials by removing expensive [physically-based rendering](https://en.wikipedia.org/wiki/Physically_based_rendering) effects.
* Removing complex post-processing shaders like fog.

### :person\_lifting\_weights: Improving Performance By Leveraging Browser APIs

#### Improving Load Times By Caching Assets

Although a user will always need to download assets on the first launch of a 3D web experience, the browser exposes two key APIs for caching source code and assets, making subsequent load times nearly instantaneous:

* The [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) API is useful for caching game assets. It acts as a local proxy server between the application and asset CDN, intercepting potentially expensive asset download requests and returning a cached response. This can enable near-native loading performance and can reduce network bandwidth usage for users that may have service-provider imposed data caps.
* [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) is useful for storing large amounts of serializable text data across sessions. This can be used to store game save files and configuration files locally, rather than replicating them to a server: Rendering engines like [Babylon](https://doc.babylonjs.com/features/featuresDeepDive/scene/optimizeCached) and [Unity](https://docs.unity3d.com/6000.1/Documentation/Manual/webgl-caching.html) also allow caching assets in IndexedDB for faster loading times.

#### Reducing Scripting Overhead with Multi-Threading

The [Web Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) allows for complex, non-rendering work to be run on a background thread, enabling basic multi-threading. This is particularly useful for computationally expensive tasks like AI pathfinding. If an app spends too much time running application logic, this is a useful browser API to leverage.

WebGL Rendering can also be performed in a worker thread using the [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas) API. This is particularly useful if the main thread has scripting overhead resulting from user interactions and/or animations that cannot be moved to a background thread.

#### Reducing Scripting Overhead with WebAssembly

[WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly) (or Wasm) is a special binary instruction set that can be executed in all major browsers. Non-browser compatible languages like C++, Rust, and C# can be compiled to this binary format and then run in the browser, leading to dramatic performance improvements when compared to JavaScript in specific tasks. Real-time 3D engines written in C++ or Rust like Unity and Bevy make use of Wasm to run games in the browser at ["near-native speed"](https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Concepts#what_is_webassembly).

JavaScript-based engines like Three.js, Babylon.js, and PlayCanvas can leverage Wasm for computationally expensive features like physics simulation, texture decompression, and mesh optimization, reducing scripting overhead in application logic:

* [MeshOptimizer](https://www.npmjs.com/package/meshoptimizer)
* [Basis Universal](https://github.com/BinomialLLC/basis_universal/blob/master/webgl/encoder/README.md) texture compression
* [Rapier](https://rapier.rs/docs/user_guides/javascript/getting_started_js) physics engine
* First-party native (C, C++, Rust) code that is not well-suited to JavaScript can be compiled to Wasm using tools like [Emscripten](https://emscripten.org/).

There are some limitations to Wasm in comparison to native code:

* **Startup Time**: Like all web app source code, Wasm bytecode must be downloaded by the browser before it can begin running. This can result in worse startup time in comparison to native apps, where source is downloaded ahead of time.
* **Garbage Collection**: In Unity WebGL, garbage collection only runs at the end of each frame. This means that allocating many temporary values in a single frame can lead to ["temporary quadratic memory growth pressure for the garbage collector"](https://docs.unity3d.com/2022.3/Documentation/Manual/webgl-memory.html).
* **Multi-threading**: Threading support in Wasm is constantly evolving. Although the Wasm supports multi-threading and SIMD instructions, engines must explicitly support these Wasm features. For instance, [Unity does not support C# multithreading](https://docs.unity3d.com/Manual/webgl-technical-overview.html).
* WebAssembly does not always guarantee the [best performance](https://ianjk.com/webassembly-vs-javascript/). It may be more cost-effective to optimize existing JavaScript code rather than introducing Wasm into your project.

#### Reducing Scripting Overhead with WebGPU Compute Shaders

WebGPU is gaining adoption as a potential substitute for WebGL in the browser, providing a direct abstraction for modern rendering APIs like Vulkan, Metal, and DirectX 12. While [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API) can be used as the primary rendering API in some engines, it can also be leveraged in WebGL-based applications to run [compute shaders](https://webgpufundamentals.org/webgpu/lessons/webgpu-compute-shaders.html) allowing non-rendering work to be done on the GPU. This enables highly parallelizable computations like AI pathfinding and animations to be performed asynchronously [on the GPU](https://surma.dev/things/webgpu/). Parallelizing these computations can reduce scripting overhead and improve frame rates.


# Profiling in the Browser

Learn about how to find and analyze key metrics in the browser tools.

***

## Profiling Web Applications in the Browser

In [Optimization Recommendations and Requirements](/optimization/optimization-recommendations-and-requirements), we stressed the importance of profiling web applications before beginning any optimization. Here, we give an overview of how browser developer tools can be used to profile application metrics&#x20;

In this section, we use screenshots from Chrome, as it is has the largest user base. However, developer tooling functions similarly across browsers. For instance, Edge and Chrome use the exact same developer tools, with some minor UI differences. Safari has a slightly different collection of tools, but it can be used to achieve similar results. In the next section, we provide platform-specific resources for profiling web applications.

### :toolbox: Accessing Developer Tools

<figure><img src="/files/TRa4pc7DOxcz4z79K5PV" alt=""><figcaption><p>Developer Tools in Chrome</p></figcaption></figure>

Most profiling utilities are accessible in the "Developer Tools" menu (Web Inspector in Safari). This menu is accessed in a different way on each browser:

| Browser | Command                                                  |
| ------- | -------------------------------------------------------- |
| Chrome  | <p>Ctrl-Shift-J (Windows)<br>Command-Option-K (Mac)</p>  |
| Edge    | F12                                                      |
| Safari  | Command-Option-C                                         |
| Firefox | <p>Ctrl-Shift-K. (Windows)<br>Command-Option-K (Mac)</p> |

### :stopwatch: Profiling Application Metrics

The Developer Tools browser menu can be used to access profilers for the core application metrics described in [Optimization Recommendations and Requirements](/optimization/optimization-recommendations-and-requirements). Here, we give an overview of generating profiles with these tools and interpreting the results.

{% tabs %}
{% tab title="FPS" %}

#### Overview

We recommend enabling the built-in FPS overlay on desktop devices while developing an application. This allows developers to quickly detect if a change has impacted performance, even when not they are not doing optimization work. This helps prevent performance regressions from accidentally getting out to users.&#x20;

#### Guide: Enabling the FPS Overlay

Focus the Developer Tools Menu and enter `ctrl/cmd + shift + P` . This opens a search menu:&#x20;

<figure><img src="/files/mOzpLKAlpDYLtWb6utdE" alt=""><figcaption></figcaption></figure>

Type "show FPS" into the search menu and hit `Enter`\
\
\ <br>

<figure><img src="/files/UlE8YRqiA4HQKgoC2KYt" alt=""><figcaption></figcaption></figure>

\
Observe the FPS counter in the top left corner of the application.

<figure><img src="/files/MUQrOy8wFBWaadAxPe8x" alt=""><figcaption></figcaption></figure>

#### Interpreting Results

* Average FPS is displayed at the top of the overlay (69.6 FPS in the picture above)
* Variance in FPS can be determined from the colored bar below the average FPS. Yellow segments represent samples above 60 fps, while red segments represent samples below 60 fps. In the image above, FPS is hovering around 60 fps, so short red segments are interspersed with short yellow segments.&#x20;
* In a well-optimized application, all segments will be yellow:&#x20;

  <figure><img src="/files/IUsNp6Wu84X3ByxWMvbN" alt=""><figcaption></figcaption></figure>
* In a poorly-optimized application, many segments will be red:&#x20;

  <figure><img src="/files/dMmOOBgdfUJgYdLJ9rvV" alt=""><figcaption></figcaption></figure>
* GPU memory usage is displayed at the bottom of the overlay. GPU performance typically scales with memory usage. In the image above, there is very little GPU overhead.&#x20;
  {% endtab %}

{% tab title="General Performance" %}

#### Overview

Most optimization work in the browser revolves around generating profiles in the "Performance" tab (Timelines in Safari). In this tab, we can break down how much time is spent each frame on scripting, shaders, and garbage collection (GC). We can also view outlier frames, i.e. frames that last much longer than the average frame, contributing to instability.

#### Guide: Generating a Performance Profile

Select the Performance tab (Timelines in Safari) and click the "record" button.&#x20;

<figure><img src="/files/pEywtdU207JsUV4d13Dw" alt=""><figcaption></figcaption></figure>

If your browser supports memory profiling, be sure to toggle the option. This allows you to analyze garbage collection pauses with more detail.

<figure><img src="/files/RBo5Lgjnyyyn9nNbGUrv" alt=""><figcaption></figcaption></figure>

Developers can enable CPU throttling to approximate performance on less-powerful mobile devices. We recommend selecting "mid-tier mobile", as Chrome normalizes these values across devices.

<figure><img src="/files/kfnlnhHM6Sw8dRbLqloo" alt=""><figcaption></figcaption></figure>

The performance profiler generates a [flame graph](https://queue.acm.org/detail.cfm?id=2927301), which shows how much time is spent in individual functions in each frame. The functions stack downwards, so the core render function will be near the top, and native calls, JavaScript APIs, and WebGL calls will be at the bottom.

<figure><img src="/files/FhN3MEoAIMztc8r8NCzG" alt=""><figcaption></figcaption></figure>

We also see the amount of memory being used during the profile. This typically has a "sawtooth" pattern, as garbage collection will automatically trigger once memory usage reaches a certain level.

<figure><img src="/files/MsBPD9zvNf71E7Tnkxfz" alt=""><figcaption></figcaption></figure>

Garbage collection appears in the flame graph as either a "Minor GC" or "Major GC" event, corresponding to a partial garbage collection or a full garbage collection, respectively. The differences between minor and major GC are explored in more depth on the [v8 blog](https://v8.dev/blog/trash-talk).

<figure><img src="/files/SMc5KFaNbO0kwseePaFC" alt=""><figcaption></figcaption></figure>

GPU overhead is recorded at the bottom of the profile, but there is relatively little detail provided.

<figure><img src="/files/uaPtp91ZF4xG9jHrlMUz" alt=""><figcaption></figcaption></figure>

#### Interpreting results

* In a well-optimized application, the flamegraph will have a small width relative to the size of the length of the frame. In the screenshot below with mid-tier mobile throttling selected, we spend about 5ms per frame in scripting, leaving plenty of room for GPU overhead. This application can be expected to run at above 60 FPS on most devices:&#x20;

  <figure><img src="/files/yFNf7PPYX1ZKlqwEUZ8v" alt="" width="375"><figcaption></figcaption></figure>
* In a poorly-optimized application with the same throttling, we see much more time spent in scripting, over 20 ms in this case. This application will run well below 60 fps, as this does not include time spent on the GPU. Here, we can see a lot of time spent in the "render" function. This suggests that the developer should reduce the number of draw calls in the application.&#x20;

<figure><img src="/files/DZIgEagnLDWYv8l4Rtnp" alt="" width="323"><figcaption></figcaption></figure>

* In a well-optimized application, there should be a long time between Minor GC events. To achieve this, developers should reduce temporary allocations, as described in [Challenges and Optimization Strategies](/optimization/challenges-and-optimization-strategies).&#x20;
* A "Major GC" event will correspond to a noticeable freeze in the application. These should be minimized as much as possible. Major garbage collection events can also be avoided by using temporary variables, but often correlate to a major scripting event, such as hundreds of meshes being removed from the scene at once. In these circumstances, it may be better to split up the work across multiple frames with [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout), allowing for multiple small GC pauses that are less noticeable to the user.
  {% endtab %}

{% tab title="Memory Usage" %}

#### Overview

It can be challenging to locate the memory allocations that contribute to long garbage collection pauses or application crashes. In many cases, these allocations are split across many different functions and objects, each of which needs to be optimized. To identify these functions and objects, developers can take a "Heap Snapshot", which captures all of the application's allocated memory objects at a given time point and where they are still being used.

#### Generating a Memory Snapshot

Select the Memory tab in Developer Tools

<figure><img src="/files/V0HGjLsbsK8QpcVPTPYm" alt=""><figcaption></figcaption></figure>

Click the record button to take a heap snapshot

<figure><img src="/files/XIDcqsushw52rXQYGmWX" alt=""><figcaption></figcaption></figure>

This generates an overview of how much memory is consumed by your application, and where the majority of memory is allocated. It is important to minimize the amount of temporary "object" and "array" memory used by your application, as this memory contributes the most to garbage collection pauses.

<figure><img src="/files/DqcQ6T5zwKh1WnIgkGAa" alt=""><figcaption></figcaption></figure>

#### Interpreting Results

**Minimizing large objects**

Select an object that is utilizing lots of memory. Typically, these will be ArrayBuffers, Meshes, Vectors, other game engine objects, or plain JavaScript arrays like the ArrayBuffer below:

<figure><img src="/files/RxC9oPJWgnBc4cEaD32W" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/HGEV8TkTiy3ajgcM87xc" alt=""><figcaption></figcaption></figure>

We can then see that the ArrayBuffer is being allocated as matrix transform data for instances of a mesh. Given the large amount of memory being allocated for this matrix, it may make sense to reduce the number of instances rendered by the corresponding mesh, as this could cause a GC pause when it is disposed.

<figure><img src="/files/pEPUwdhNae81f4CeugHc" alt=""><figcaption></figcaption></figure>

**Minimizing large quantities of small objects**

In addition to the large ArrayBuffer allocation, we also see a large number of Vector3 objects being allocated (43,363)

<figure><img src="/files/F2uSYrCigU0Grqhtg64x" alt=""><figcaption></figcaption></figure>

Most of these Vector3 objects are in use by the engine, but some of them correspond to temporary variables created in a loop in our experience logic. These temporary variables should ideally be reused, as described in [Broken mention](broken://pages/NpscNyh56zBBMYfQktEy#reusing-objects-to-reduce-memory-usage).
{% endtab %}

{% tab title="Loading Time" %}

#### Overview

There are three main tools for profiling loading time: the performance profiler, Lighthouse (only available in Chrome and Edge), and the network tab. Lighthouse generates a report on how long the application takes to load

#### Guide: Profiling Loading Performance

To profile scripting performance while loading, reload the page while recording performance. This generates a performance profile that is identical to the one described in [#general-performance](#general-performance "mention").

<figure><img src="/files/0yfzbIf01TY9yxLaHX0n" alt=""><figcaption></figcaption></figure>

In the generated profile, we can see lots of time being spent decoding textures. This could be reduced by using smaller texture sizes, which take less time to decode, or by merging textures into a single texture atlas.

<figure><img src="/files/UYj6JGj9n4oBAMplqwKh" alt="" width="353"><figcaption></figcaption></figure>

#### Guide: Generating Lighthouse Reports

Chrome has built-in tools for analyzing loading time performance in the "Lighthouse" tab.&#x20;

<figure><img src="/files/QIP5kiRDXTvoYN5mBX5r" alt=""><figcaption></figcaption></figure>

This gives information on asset download sizes, how long the initial load takes, and how long it takes for the application to come fully interactive. This is useful in determining TTID. TTID roughly corresponds to the "First Contentful Paint" metric. There is not an exact analogue for TTFD, as Lighthouse cannot determine whether or not the canvas is interactive.

<figure><img src="/files/WFf9ecYl7DoPeQpRQ7aP" alt=""><figcaption></figcaption></figure>

In a poorly optimized application, Lighthouse will give information on specific assets to optimize, such as textures, meshes, and code.

<figure><img src="/files/jV5j4OAkD7whqwqM86WT" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/AqTgZlobT0UcpTeAqyIe" alt=""><figcaption></figcaption></figure>

#### Guide: Inspecting Network Downloads

The network tab can be used to inspect the size and timing of network fetches. To profile network activity, open the network tab, and reload the page. Select "Disable Cache" to ensure that everything is downloaded over the network.

<figure><img src="/files/e1WqvSCsNB9fgk4LmeSP" alt=""><figcaption></figcaption></figure>

Look for assets with large sizes. Here, we may choose to compress the skybox texture even more, as the combined assets are about 6 MB.

<figure><img src="/files/iQbtAItBK8Yb1BQuiNth" alt=""><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}

### :mobile\_phone: Profiling Mobile Devices

There are two main ways to capture mobile performance metrics:&#x20;

* directly profiling a connected mobile device
* emulating a mobile device in the browser

These techniques should be used in combination. Profiling mobile hardware gives more representative data, while emulation can be used to provide a wider range of data points. Crucially, GPU performance does not scale in browser emulators, so frame rate may be higher in the emulator. Emulators should primarily be used to approximate CPU performance across a range of devices and to validate image quality across a range of resolutions.

{% tabs %}
{% tab title="Android" %}

#### Prerequisites

1. Access to an Android Device
2. Install Android Debug Bridge ([ADB](https://developer.android.com/tools/adb)) to connect to the android device.
3. Install Chrome or Edge on your desktop/laptop computer. In Edge, replace `chrome://` with `edge://` for the following steps.

#### Connecting to an Android Device via USB

1. Connect an android device via USB
2. In a terminal, run `adb devices` to start the debugging server
3. Navigate to chrome://inspect/devices and click "inspect" the connected device
4. This brings up the Developer Tools menu for the connected device. From here, profiling is the same as on a desktop

#### Generating Native Performance Profiles

1. In chrome://inspect/devices, select the "trace" option.
2. If debugging an Android XR device, select `Edit categories` > check `xr.debug`
3. Click record to begin generating a native trace.
   {% endtab %}

{% tab title="iOS" %}

#### Prerequisites

1. Access to an iOS device
2. [Enable inpsecting safari on the iOS Device](https://developer.apple.com/documentation/safari-developer-tools/inspecting-ios#Enabling-inspecting-your-device-from-a-connected-Mac)

#### Connecting to an iOS device

1. [Connect to the device over WiFi or USB](https://developer.apple.com/documentation/safari-developer-tools/inspecting-ios#Enabling-inspecting-your-device-from-a-connected-Mac)
2. [Open Safari > Develop > Your Device > Your Application](https://developer.apple.com/documentation/safari-developer-tools/inspecting-ios#Inspecting-a-webpage)
3. Profile the application using safari dev tools.
   {% endtab %}

{% tab title="Mobile Device Emulation" %}

#### Emulating a Mobile Device

Open Developer Tools and select the icon in the top left corner that looks like a laptop computer. This enables device emulation, allowing a developer to select from a range of devices to emulate.

<figure><img src="/files/Fy2GXsOj8W2zPlphssAV" alt=""><figcaption></figcaption></figure>

Developers can then select a device to emulate in the main browser window, and apply throttling to the CPU. Below, we selected an iPhone 12 Pro with Mid-tier Mobile throttling:

<figure><img src="/files/VhG2QZpy0vQRewWAv5wr" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="WebXR Emulation" %}

#### Emulating a WebXR Device

For WebXR Emulation, install the [Meta Immersive Web Emulator](https://developers.meta.com/horizon/blog/webxr-development-immersive-web-emulator/). It is supported on Chrome and Edge. This allows developers to profile immersive experiences more accurately, and gives tools for manipulating the XR headset and controllers:<br>

<figure><img src="/files/aUmJeuxT47VnIhNSYjTZ" alt=""><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}

***

## Profiling Resources for Web Browsers and Devices

Beyond the overview we have provided, each browser provides detailed guides for debugging and profiling applications. Here we provide lists of resources for each major browser, as well as resources for connecting to mobile devices from Chrome and Safari.

{% tabs %}
{% tab title="Chrome" %}

#### Platform Support

:desktop: Desktop & :mobile\_phone: Mobile

#### Core Developer Tools

* [Performance Profiling](https://developer.chrome.com/docs/devtools/performance/overview)
* [Debugger](https://developer.chrome.com/docs/devtools/javascript)
* [Network Profiling](https://developer.chrome.com/docs/devtools/network/overview)
* [Memory Profiling](https://developer.chrome.com/docs/devtools/memory)

#### Additional Tooling

* [Tracing Tool](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/): Provides more introspection into the native side of chrome, showing JavaScript engine traces and GPU tracing.
* [Lighthouse](https://developer.chrome.com/docs/devtools/lighthouse): Tools for optimizing app start time and file size.
* [Immersive Web Emulator Plugin](https://chromewebstore.google.com/detail/immersive-web-emulator/cgffilbpcibhmcfbgggfhfolhkfbhmik?pli=1): emulate XR devices in Chrome Developer Tools
* Display [frame rate](https://devtoolstips.org/tips/en/display-current-framerate/)
* [WebAssembly debugger](https://developer.chrome.com/docs/devtools/wasm)

#### Mobile Tooling

* [Remote Debugging](https://developer.chrome.com/docs/devtools/remote-debugging/local-server)
* [Device Emulator](https://developer.chrome.com/docs/devtools/device-mode/)
  {% endtab %}

{% tab title="Edge" %}

#### Platform Support

&#x20;:desktop: Desktop

#### Core Developer Tools

* [Performance Profiling](https://learn.microsoft.com/en-us/microsoft-edge/devtools/performance/overview)
* [Debugger](https://learn.microsoft.com/en-us/microsoft-edge/devtools/javascript/)
* [Network Profiling](https://learn.microsoft.com/en-us/microsoft-edge/devtools/network/)
* [Memory Profiling](https://learn.microsoft.com/en-us/microsoft-edge/devtools/memory-problems/)

#### Additional Tooling

* [Immersive Web Emulator Plugin](https://microsoftedge.microsoft.com/addons/detail/immersive-web-emulator/hhlkbhldhffpeibcfggfndbkfohndamj): emulate XR devices in Edge Developer Tools

#### Mobile Tooling

* [Device Emulator](https://learn.microsoft.com/en-us/microsoft-edge/devtools/device-mode/)
  {% endtab %}

{% tab title="Safari" %}

#### Platform Support

Desktop & :mobile\_phone: Mobile

#### Core Developer Tools

* [Tools Overview](https://developer.apple.com/documentation/safari-developer-tools/web-inspector)
* [Timelines](https://webkit.org/web-inspector/timelines-tab/) (Scripting and Memory profiler)
* [Debugger](https://webkit.org/web-inspector/sources-tab/)
* [Network Profiler](https://webkit.org/web-inspector/network-tab/)

#### Mobile Tooling

* [Inspecting iOS devices](https://developer.apple.com/documentation/safari-developer-tools/inspecting-ios)
  {% endtab %}
  {% endtabs %}


# Overview of 3D Web Rendering Engines

Learn about popular 3D rendering engines and resources for profiling with them.

***

## Introduction

In this section, we discuss the features, strengths, and weaknesses of popular 3D rendering engines for the web. Each of these engines is capable of creating high-quality 3D experiences, but developers may prefer one engine over another based on the type of experience they are developing and their development background.

## <img src="/files/0M3b3yDR3d9fgilDAW7c" alt="" data-size="line"> Unity

Unity is a feature-packed native game engine that has been used to make some of the most popular indie games of all time. If you have experience developing with other native engines like Unreal Engine and Godot, Unity's development flow will feel natural to you. However, optimizing file size is a challenge for Unity experiences, as Unity supports both native and web platforms.

#### Overview

{% columns %}
{% column width="50%" %}

#### :star: Engine Features

2D, 3D, Particles, Photorealistic Rendering, Animations, User Interface, Physics

#### :mobile\_phone:Platform Compatibility

* Built-in support for mobile touch controls
* Integrates well with WebXR

#### :earth\_africa: Developer Community

* Huge community, though the web-specific community is relatively small
* Extensive documentation
* Numerous third-party plugins, though native plug-ins must be manually compiled to Wasm
  {% endcolumn %}

{% column width="50%" %}

#### :technologist: Developer Experience

* First-Class 3D Editor
* Built-In Version Control
* Extensive tooling for profiling experiences
* Integrates with browser Developer Tools

#### :scroll: Licensing

* Closed source engine
* Free for hobbyists and indie teams
* Paid for teams with over $200k annual revenue.
  {% endcolumn %}
  {% endcolumns %}

#### Strengths and Weaknesses

| Strengths                                                                                                                                                                                      | Neutrals                                                                                              | Weaknesses                                                                                                |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| <ul><li>High performance ceiling</li><li>First-class feature set</li><li>First-class editor</li><li>Experiences scale well on mobile devices</li><li>Large, high quality asset store</li></ul> | <ul><li>Native game engine</li><li>Write in C#</li><li>Opinionated game engine architecture</li></ul> | <ul><li>WebGL rendering is not the focus of the engine</li><li>Produces large builds by default</li></ul> |

#### Resources

<table><thead><tr><th valign="top">Documentation</th><th valign="top">Optimization Guides</th><th valign="top">Profiling Tools and Guides</th></tr></thead><tbody><tr><td valign="top"><ul><li><a href="https://docs.unity3d.com/Manual/index.html">Core Documentation</a></li><li><a href="https://docs.unity3d.com/Manual/webgl.html">WebGL Documentation</a></li><li><a href="https://docs.unity3d.com/Manual/webgl-gettingstarted.html">Publishing Guide</a></li><li><a href="https://docs.unity3d.com/Manual/webgl-building-distribution.html">Creating Unity Web Builds</a></li></ul></td><td valign="top"><p>General Performance</p><ul><li><a href="https://docs.unity3d.com/Manual/analysis.html">General Unity Optimization</a></li><li><a href="https://docs.unity3d.com/Manual/optimizing-draw-calls.html">Optimizing Draw Calls</a></li><li><a href="https://docs.unity3d.com/Manual/SL-ShaderPerformance.html">Optimizing Shaders</a></li><li><a href="https://docs.unity3d.com/Manual/webgl-technical-overview.html">Technical Limitations</a></li><li><a href="https://docs.unity3d.com/Manual/webgl-memory.html">Web Memory Optimization</a></li><li><a href="https://docs.unity3d.com/Manual/web-graphics-apis-intro.html">Web Graphics Recommendations</a></li><li><a href="https://docs.unity3d.com/Manual/webgl-texture-compression.html">Texture Compression</a></li><li><a href="https://docs.unity3d.com/Manual/wasm-2023-features.html">WebAssembly Optimization</a></li></ul><p>Loading Time</p><ul><li><a href="https://docs.unity3d.com/Manual/web-optimization.html">Optimizing Web Builds</a></li><li><a href="https://docs.unity3d.com/Manual/web-optimization-mobile.html">Optimizing Web Builds for Mobile</a></li></ul></td><td valign="top"><ul><li><a href="https://docs.unity3d.com/Manual/Profiler.html">Unity Profiler</a></li><li><a href="https://docs.unity3d.com/Manual/performance-profiling-tools.html">Other Profiling Tools</a></li><li><a href="https://docs.unity3d.com/Manual/graphics-performance-profiling.html">Graphics Performance Profiling</a></li><li><a href="https://docs.unity3d.com/6000.3/Documentation/Manual/webgl-debugging.html">Debug Web Builds</a></li><li><a href="https://unity.com/how-to/profile-optimize-web-build#the-importance-of-profiling">How to profile Web Builds</a></li></ul></td></tr></tbody></table>

## <img src="/files/ihaBwE7NZfZK3rhmr15b" alt="" data-size="line"> Three.js

Three.js is the most popular 3D rendering library written in JavaScript. It is powerful, lightweight, and highly extensible, making it a good choice for developers who want to keep build sizes small and have more control over the rendering performance of their experience. However, it has few features by default, and must be extended with open source libraries to implement physics and user interfaces.

#### Overview

{% columns %}
{% column width="50%" %}

#### :star: Engine Features

3D, Photorealistic Rendering, Animations

#### :mobile\_phone:Platform Compatibility

* No built-in support for mobile touch controls
* Integrates well with WebXR

#### :earth\_africa: Developer Community

* Large community
* Good documentation, numerous examples and tutorials available
* Huge selection of third-party libraries that enable features like physics and user interfaces
* Frameworks like [A-frame](https://aframe.io/) turn Three.js into a true game engine
* Integrates very well with React
  {% endcolumn %}

{% column width="50%" %}

#### :technologist: Developer Experience

* Extremely rudimentary 3D editor
* No built-in version control
* Integrates well with browser Developer Tools

#### :scroll: Licensing

* Open source engine
* Free to use
  {% endcolumn %}
  {% endcolumns %}

#### Strengths and Weaknesses

| Strengths                                                   | Neutrals                                                                                                                      | Weaknesses                                                                                                                                                                                   |
| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <ul><li>Very small builds</li><li>Large community</li></ul> | <ul><li>JavaScript engine</li><li>Flexible architecture</li><li>Requires lots of expertise to get great performance</li></ul> | <ul><li>Only provides rendering logic. Features like UI and physics require third-party libraries </li><li>No asset store. Users must use 3rd party sites to download GLTF assets.</li></ul> |

#### Resources

<table><thead><tr><th valign="top">Documentation</th><th valign="top">Optimization Guides</th><th valign="top">Profiling Tools and Guides</th></tr></thead><tbody><tr><td valign="top"><ul><li><a href="https://threejs.org/manual/#en/creating-a-scene">Documentation</a></li><li><a href="https://discourse.threejs.org/">Forum</a></li><li><a href="https://threejsresources.com/">Learning Resources</a></li></ul></td><td valign="top"><p>General Performance</p><ul><li><a href="https://threejs.org/manual/#en/optimize-lots-of-objects">Rendering Many Objects</a></li><li><a href="https://threejs.org/manual/#en/optimize-lots-of-objects-animated">Rendering Many Animated Objects</a></li><li><a href="https://threejs.org/manual/#en/offscreencanvas">Leveraging Offscreen Canvas</a></li><li><a href="https://threejs.org/manual/#en/cleanup">Memory Management</a></li><li><a href="https://threejs.org/docs/?q=inst#api/en/objects/InstancedMesh">InstancedMesh</a></li><li><a href="https://tympanus.net/codrops/2025/02/11/building-efficient-three-js-scenes-optimize-performance-while-maintaining-quality/">Building Efficient Three.js Scenes</a></li></ul></td><td valign="top"><ul><li><a href="https://threejs.org/manual/#en/debugging-javascript">JavaScript Debugging</a></li><li><a href="https://chromewebstore.google.com/detail/threejs-devtools/jechbjkglifdaldbdbigibihfaclnkbo">DevTools Plugin</a></li><li><a href="https://threejs.org/manual/#en/debugging-glsl">GLSL Debugging</a></li><li><a href="https://github.com/mrdoob/stats.js/">Stats.js</a>: JavaScript Performance Monitor</li></ul></td></tr></tbody></table>

## <img src="/files/5i0myozamw5ug1sdGV1R" alt="" data-size="line"> PlayCanvas

PlayCanvas is a fully-featured JavaScript engine that has a great developer experience and enables high performance web applications. The engine is less flexible than other JavaScript engines, using a more opinionated Entity-Component System architecture to ensure consistent performance.

#### Overview

{% columns %}
{% column width="50%" %}

#### :star: Engine Features

3D, Particles, Animations, Photorealistic Rendering, Physics, User Interface

#### :mobile\_phone:Platform Compatibility

* Built-in support for mobile touch controls
* Integrates with WebXR

#### :earth\_africa: Developer Community

* Medium-size community
* Extensive documentation
* Integrates well with React
* Relatively few third-party libraries
  {% endcolumn %}

{% column width="50%" %}

#### :technologist: Developer Experience

* First-Class 3D Editor
* Built-In Version Control
* Extensive tooling for profiling experiences
* Integrates with browser Developer Tools

#### :scroll: Licensing

* Open source engine
* Free to use source code
* Some editor features are available as a paid subscription
  {% endcolumn %}
  {% endcolumns %}

#### Strengths and Weaknesses

| Strengths                                                                                        | Neutrals                                                                                                                                                                          | Weaknesses                                                                              |
| ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| <ul><li>High performance ceiling</li><li>Feature-rich editor</li><li>Small build sizes</li></ul> | <ul><li>JavaScript engine</li><li>Opinionated ECS Architecture</li><li>The PlayCanvas Editor is accessible through the browser, rather than as a standalone application</li></ul> | <ul><li>Some editor features are paid</li><li>Comparatively small asset store</li></ul> |

#### Resources

<table><thead><tr><th valign="top">Documentation</th><th valign="top">Optimization Guides</th><th valign="top">Profiling Tools and Guides</th></tr></thead><tbody><tr><td valign="top"><ul><li><a href="https://developer.playcanvas.com/">Documentation</a></li><li><a href="https://developer.playcanvas.com/tutorials/">Tutorials</a></li><li><a href="https://playcanvas.com/products/editor">Editor</a></li><li><a href="https://forum.playcanvas.com/">Forums</a></li></ul></td><td valign="top"><p>General Performance</p><ul><li><a href="https://developer.playcanvas.com/user-manual/optimization/">Optimization</a></li><li><a href="https://developer.playcanvas.com/user-manual/optimization/texture-compression/">Texture Compression</a></li><li><a href="https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/batching/">Batching</a></li><li><a href="https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/hardware-instancing/">Hardware Instancing</a></li><li><a href="https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/indirect-drawing/">Indirect Drawing</a></li><li><a href="https://developer.playcanvas.com/user-manual/xr/optimizing-webxr/">Optimizing WebXR</a></li></ul><p>Loading Time</p><ul><li><a href="https://developer.playcanvas.com/user-manual/optimization/load-time/">Optimizing Load Time</a></li></ul></td><td valign="top"><ul><li><a href="https://developer.playcanvas.com/user-manual/scripting/debugging/browser-dev-tools/">Browser Dev Tools</a></li><li><a href="https://developer.playcanvas.com/user-manual/optimization/profiler/">PlayCanvas Profiler</a></li><li><a href="https://developer.playcanvas.com/user-manual/optimization/gpu-profiling/">GPU Profiling</a></li><li><a href="https://threejs.org/manual/#en/debugging-javascript">JavaScript Debugging</a></li></ul><p></p></td></tr></tbody></table>

## <img src="/files/7sX9er6PkWMYHyngdiSX" alt="" data-size="line">Babylon.js

Babylon.js is a fully-featured JavaScript engine that enables great performance, is easy to learn, and has a solid developer tooling ecosystem. Project sizes tend to be larger than PlayCanvas or Three.js, but smaller than Unity. For developers that do not need an editor but want all the features of a modern web rendering engine, Babylon is a great choice.

#### Overview

{% columns %}
{% column width="50%" %}

#### :star: Engine Features

3D, Particles, Animations, Photorealistic Rendering, Physics, User Interface

#### :mobile\_phone:Platform Compatibility

* No built-in touch controls
* Integrates well with WebXR

#### :earth\_africa: Developer Community

* Small, active community
* Extensive documentation
* Integrates with JavaScript libraries like React, Angular, and Vue
  {% endcolumn %}

{% column width="50%" %}

#### :technologist: Developer Experience

* Rudimentary 3D editor
* Rudimentary playground system for prototyping in the browser
* Node-based material editor
* No built-in version control
* The Babylon Inspector and Spector libraries enable extensive profiling in the browser
* Integrates with browser Developer Tools

#### :scroll: Licensing

* Open source engine
* Free to use
  {% endcolumn %}
  {% endcolumns %}

#### Strengths and Weaknesses

| Strengths                                                                                          | Neutrals                                                                                         | Weaknesses                                                                                     |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| <ul><li>High performance ceiling</li><li>First class WebXR</li><li>Extensive feature set</li></ul> | <ul><li>JavaScript engine</li><li>Flexible architecture</li><li>Supported by Microsoft</li></ul> | <ul><li>Larger file sizes for a JavaScript Engine</li><li>Relatively small community</li></ul> |

#### Resources

<table><thead><tr><th valign="top">Documentation</th><th valign="top">Optimization Guides</th><th valign="top">Profiling Tools and Guides</th></tr></thead><tbody><tr><td valign="top"><ul><li><a href="https://doc.babylonjs.com/">Documentation</a></li><li><a href="https://playground.babylonjs.com/">Playground</a></li><li><a href="https://forum.babylonjs.com/">Forums</a></li></ul></td><td valign="top"><p>General Performance</p><ul><li><a href="https://doc.babylonjs.com/features/featuresDeepDive/scene/optimize_your_scene">Optimization Guide</a></li><li><a href="https://doc.babylonjs.com/features/featuresDeepDive/scene/optimizeOctrees/">Octree Optimization</a></li><li><a href="https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer/">SceneOptimizer</a></li><li><a href="https://doc.babylonjs.com/features/featuresDeepDive/scene/offscreenCanvas/">Offscreen Canvas</a></li><li><a href="https://doc.babylonjs.com/features/featuresDeepDive/scene/customLoadingScreen/">Creating Loading Screens</a></li><li><a href="https://doc.babylonjs.com/features/featuresDeepDive/mesh/copies/">Instancing</a></li></ul><p>Loading Time</p><ul><li><a href="https://doc.babylonjs.com/setup/frameworkPackages/es6Support/">Optimizing build sizes</a></li><li><a href="https://doc.babylonjs.com/features/featuresDeepDive/scene/optimizeCached/">Resource Caching</a></li></ul></td><td valign="top"><ul><li><a href="https://github.com/BabylonJS/Spector.js">Spector</a>: a tool for analyzing draw calls</li><li><a href="https://doc.babylonjs.com/toolsAndResources/inspector/">Babylon Inspector</a></li><li>No asset store. Users must use 3rd party sites to download GLTF assets.</li></ul></td></tr></tbody></table>


# Introduction & Benefits

This page introduces you to the goals and technology that make up VIVERSE Polygon Streaming.

***

VIVERSE Polygon Streaming is an intelligent visualization core technology that overcomes modern-day 3D visualization challenges. It unleashes the potential to stream spatial content from the cloud and seamlessly shares high-fidelity models and worlds on any device.

Polygon Streaming aims to provide creators, developers, designers, and enterprises with tools to build immersive, high-quality 3D content that is widely compatible with the most popular game engines and can be experienced across different platforms.

VIVERSE Polygon Streaming focuses on the next shift in computing and the future of the internet. It breaks down barriers and enables accessibility from any device (mobile, tablet, PC, and even XR headsets), allowing people to create, share experiences, and collaborate like never before.

Allow end-users to consume big 3D models rapidly by only downloading the geometry that has relevant visual impact for them at their specific position and bandwidth. With Polygon Streaming, you can stream large 3D models into your platform or projects without taking up local storage space, leveraging the number of polygons that can be shown in a cross-platform and multi-device way.

### Benefits

* Only transfer geometry is needed in the user’s current position and field of view in the scene over time.&#x20;
* Automatic LOD generation and texture compression. Texture compression method optimized for lightweight decompression.
* Minimal back-end server requirements, only requiring HTTP requests.&#x20;
* Utilize the end-user's GPU for full rendering. No Cloud GPU is necessary at any step.

{% hint style="info" %}
For license & agreement, please click on this [link](/polygon-streaming/viverse-sdk-license-agreement).
{% endhint %}


# Supported Formats & Standards

This page details the file types and formats supported in VIVERSE Polygon Streaming.

***

### Supported 3D Formats:

Polygon Streaming supports the following formats:

* **.GLB**
* **.glTF** (zipped)
* **.OBJ** (zipped)

It’s **highly recommended** to use a single **.GLB** file to convert your 3D models. This will ensure that the file contains all the necessary data, and its following the correct standards used during conversion. **3D models must be a triangulated mesh with PBR materials following metallic-roughness standards.**

* In case you use a **.glTF** model, all the necessary files need to be zipped into a single **.zip** file. It should at least contain a **.gltf** file, a **.bin** file and all the texture images in the formats **.jpg** or **.png**.
* In case you use a **.OBJ** model, all the necessary files need to be zipped into a single **.zip** file. It should contain a **.obj** file, a **.mtl** file, and all the texture images in the formats **.jpg** or **.png**, making sure they are properly set and connected. You can also upload a single **.obj** file in case there are no textures or .mtl file needed.&#x20;

{% hint style="info" %}
Note that in case the textures are not properly connected in your file, they will appear as plain white after conversion. A single .obj file will always look plain white since it doesn’t contain material or texture information.
{% endhint %}

### Lightmap Support

Image-based lighting (or "lightmapping") can save GPU processing time by baking real-time lighting information into static images that are then overlaid on models as texture maps. Currently, a single lightmap is supported via a separate file. When uploading your model to <https://stream.viverse.com/>, package it in a `.zip` file containing your `.glb` or `.obj` and a file named either `lightmap.hdr` or `lightmap.png`. This lightmap will be automatically applied to the Streaming Model inside PlayCanvas when running the VIVERSE Create SDK, and should be using the first UV set, often called `UV0`. There is no size limit to this lightmap, but most browser will error on image files larger than 8k. *To reiterate, only one (1) lightmap file is supported at this time.*

### Supported Image Formats

* JPG
* PNG
* BMP
* KTX

### Supported PBR Standard Texture Maps:

* Diffuse/Albedo
* Metallic/Roughness
* Transparency/Opacity
* Normals
* Ambient Occlusion
* Emissive

### Supported glTF Extensions:

* KHR\_draco\_mesh\_compression
* KHR\_materials\_unlit
* KHR\_texture\_basisu
* KHR\_materials\_specular
* KHR\_materials\_pbrSpecularGlossiness
* KHR\_texture\_transform
* KHR\_materials\_ior
* KHR\_materials\_transmission
* KHR\_materials\_volume
* KHR\_materials\_clearcoat


# Asset Preparation & Best Practices

In this guide, you will find information on how to prepare you 3D models to achieve the best outcomes when converting and streaming your models within the supported platforms and devices.

***

In this guide you will find information on how to prepare your 3D models to achieve the best outcomes when converting to Polygon Streaming solutions and streaming your models within the supported platforms and devices.

### PBR Standard for Material and Textures

**Physically based rendering (PBR)** has recently become the standard in many 3D applications, such as [Unity](https://blogs.unity3d.com/2014/10/29/physically-based-shading-in-unity-5-a-primer/), [Unreal](https://docs.unrealengine.com/latest/INT/Engine/Rendering/Materials/PhysicallyBased/), [3D Studio Max](http://area.autodesk.com/blogs/the-3ds-max-blog/what039s-new-for-rendering-in-3ds-max-2017) and 3D Web platforms. Polygon Streaming is also fully based on this standard, for a more performant and realistic material and textures representations.

This approach differs from older approaches in that instead of using approximations for the way in which light interacts with a surface, a physically correct model is used. The idea is that, instead of tweaking materials to look good under specific lighting, a material can be created that will react correctly under all lighting scenarios.

In case your models are created using legacy standards such as Lambert, Blinn-Phong or Specular-Roughness, they should be converted to a PBR Physically Based Rendering standard before conversion, in order to achieve a correct representation of the materials and textures from the original model.

Models using legacy or custom standards can be converted, however, it’s not possible to guarantee a correct representation of the materials and textures. Using custom standard or custom shader elements may also give failed conversion, since the system won’t be able to proper translate custom elements.<br>

<figure><img src="/files/1a6ptk6NgwO1bTaEKslV" alt="A 3D mesh material node following PBR Standards, which will render correct results."><figcaption><p><strong>A 3D mesh material node following PBR Standards, which will render correct results.</strong></p></figcaption></figure>

<figure><img src="/files/9vxlLIlGas1LM1QDDZUr" alt="A 3D mesh material node following custom standard, which may generate incorrect results."><figcaption><p><strong>A 3D mesh material node following custom standard, which may generate incorrect results.</strong></p></figcaption></figure>

### Anchor Point Origin, General Transform and Scale

Polygon Streaming maintains the origin point, position, and scale of all 3D objects. When converting entire environments with multiple objects, each object will automatically be positioned at its origin point. You need to make sure you have your objects placed at the correct origin point. If a model is far from the 0,0,0 origin point it will be incorrect for visualization needs, and the wrong origin point will still be translated after conversion. The same goes for the general scale of the model, the scale set for the model will be translated after conversion. Polygon Streaming follows meter units by default, the same as Blender, Unity or Unreal Engine. If the model scale is following another unit system or is set very small on the original model, The model will appear very small after conversion.

<figure><img src="/files/3Gj6oIUQ0fUlKICmVXy1" alt="A model of a 3D cube positioned at the origin point."><figcaption><p><strong>A model of a 3D cube positioned at the origin point.</strong><br></p></figcaption></figure>

<figure><img src="/files/pdbgIGbpJ6EIkJucu23C" alt="A model of a 3D cube positioned far from the origin point."><figcaption><p><strong>A model of a 3D cube positioned far from the origin point.</strong></p></figcaption></figure>

### Maintain Face Orientation / Flipped Normals

Make sure to have your model’s faces facing the correct orientation. In case a face is being oriented in the wrong direction (also known as flipped normals), it will appear transparent depending on the user view-point direction. Face orientation is a design choice and therefore subjective, not possible for an automated system to know which face should be oriented at which direction. The system will follow the original model’s orientation and direction. In case it’s facing the wrong direction on the original model, it will also face wrong after conversion.

{% hint style="info" %}
Note that our plugins and integrations always provide a double-sided material option, which can be used to avoid flipped normals issues since it renders two sides.
{% endhint %}

<figure><img src="/files/mjYiksVoXVtzyWPyES8O" alt="A 3D model with faces facing the wrong direction and rendering with issues."><figcaption><p><strong>A 3D model with faces facing the wrong direction and rendering with issues.</strong></p></figcaption></figure>

### Avoid Z-Fighting

Z-Fighting is a common issue in real-time rendering, it happens when two meshes are overlapping each other, and the GPU is unable to understand which one to render first. It’s very common to happen when displaying NURBS based models, like CAD or BIM models, in a real-time rendering scenario. In order to have a proper real-time display of your streaming model, make sure to avoid z-fighting by avoiding creating overlapping meshes.

<figure><img src="/files/6ACPauxvd93IkanQYEjN" alt="Z-Fighting from two overlapping planes."><figcaption><p><strong>Z-Fighting from two overlapping planes.</strong></p></figcaption></figure>

### Remove Hidden Data

Sometimes a 3D model may have data that is being hidden or enclosed by another object, which will never be seen by the final user. Although there is no issue in converting the model with hidden data, and the hidden data won’t be render, since our player plugins make use of Occlusion Culling. It’s a good practice to remove any data that will be hidden, in order to make the overall model smaller, more concise, faster to convert, and overall lighter to stream.

<figure><img src="/files/kXrRxBnG53PT9cNgAcmn" alt="A 3D model of a car showing a large amount of data underneath the bonnet that won’t be seen by the user."><figcaption><p><strong>A 3D model of a car showing a large amount of data underneath the bonnet that won’t be seen by the user.</strong></p></figcaption></figure>

### Maintain a Proper Mesh Topology

It’s a good practice to maintain a proper mesh topology. There is no issue in converting a model with a bad mesh topology. However, any 3D visualization issues that comes from a bad mesh topology, will be translated for the streaming model as well. Therefore, in order to obtain the best looking model, with faster and lighter stream. A concise, sensible and well done mesh topology will always give much better results than a bad mesh topology, that may contain weird looking patches, holes, weird looking triangles and faces. The best the 3D model is modeled and maintain a sensible mesh topology, the best it will be streamed.

<figure><img src="/files/HxEVk0oueA6Hrih6vYm4" alt="Comparison between a mesh with bad topology on the left, and proper mesh topology on the right."><figcaption><p><strong>Comparison between a mesh with bad topology on the left, and proper mesh topology on the right.</strong><br></p></figcaption></figure>


# Upload and Convert a 3D Asset

This page guides you through the steps of uploading an asset to the Polygon Streaming platform.

***

{% stepper %}
{% step %}
Start by logging into the [Polygon Streaming CMS](https://www.viverse.com/polygon-streaming). Then you will be able to upload a 3D asset into the platform by simply dropping a file in the upload field and clicking the upload button.

<figure><img src="/files/A8ms8bYNJ3jnhlti367y" alt="Upload 3D Model"><figcaption><p>Upload 3D Model</p></figcaption></figure>
{% endstep %}

{% step %}
After selecting your file you will be able to click Convert to convert the 3D asset into a streaming model. You can also choose different options before conversion:

<figure><img src="/files/VDep286hEy7ucz6qijhO" alt="Resolution Options Before Conversion"><figcaption><p>Resolution Options</p></figcaption></figure>
{% endstep %}

{% step %}
After selecting your file you will be able to click Convert to convert the 3D asset into a streaming model. You can also choose different options before conversion:

* **Default Resolution** will use default resolution for the textures of your models, a middle term between low and high resolution.&#x20;
* **Low Resolution** will lower down the resolution of textures, making the file to stream lighter in exchange of a visual quality drop.&#x20;
* **High Resolution** will ensure the resolution of textures is as high as possible, which will ensure the highest visual quality. However, depending on the model, especially very large models like full environments, it could make the file heavy to stream and display.
  {% endstep %}

{% step %}
Once you choose the desired options you can press **Convert** and the conversion of the model will start. Once the conversion is done, the converted model will appear in the **Models** panel.

<figure><img src="/files/xULbjJwdiI51RdsOf3tk" alt="Model Panel"><figcaption><p>Model Panel</p></figcaption></figure>

In the Model panel you can click on the icons next to your model to either preview the model in a new tab on the web-browser, or copy the converted URL address to use inside your projects using the Polygon Streaming integrations and plugins.

<div align="center" data-full-width="false"><figure><img src="/files/nF7fyeDoaSeQxnZh96io" alt="Open the converted model in a new tab on the web-browser."><figcaption><p>Open the converted model in a new tab on the web-browser.</p></figcaption></figure></div>

<figure><img src="/files/Khic78mkGg4cdt7exZUg" alt="Copy the converted model URL"><figcaption><p>Copy the converted model URL.</p></figcaption></figure>

<figure><img src="/files/u24uDpRxtjEPGCpzMQOk" alt="Delete the converted model from the list"><figcaption><p>Delete the converted model from list.</p></figcaption></figure>
{% endstep %}
{% endstepper %}


# Avoiding Common Mistakes: Preview Mode vs. Play Mode

This page details important for avoiding errors when working with VIVERSE Polygon Streaming.

***

When working with high-fidelity 3D content using Polygon Streaming, it's essential to differentiate between *Preview Mode* and *Play Mode* (also known as Runtime, Live Mode, or Launch Mode depending on the game engine). For simplicity, we will use "Play Mode" as a shared term throughout this article, although each engine may refer to it differently. Understanding these modes is crucial to ensure that developers are testing their models under realistic conditions. Below, we'll explore what to expect when using Polygon Streaming in PlayCanvas, Unity, and Unreal Engine, and provide clear instructions on how to test models effectively in each environment.

### What Are Preview Mode and Play Mode?

* **Preview Mode**: This mode provides a quick visualization within the editor, allowing developers to see a basic representation of their models and adjust placements in the scene. It offers a rough approximation and may show visual artifacts. This mode does not fully utilize Polygon Streaming capabilities and should not be used for assessing the final quality or behavior of the models.
* **Play Mode**: Known by different names such as Runtime or Launch Mode in various engines, Play Mode simulates real-world conditions by running the application as it would be experienced by end-users. Polygon Streaming is fully active in Play Mode, ensuring high-quality rendering and optimal performance. Testing in Play Mode is essential for final model evaluation.

<figure><img src="/files/OSildb2fIPXRAGwo5i2T" alt="Preview Mode"><figcaption><p>Preview Mode</p></figcaption></figure>

<figure><img src="/files/z8tksCBqTj2FGU0rfGtq" alt="Play Mode Activated"><figcaption><p>Play Mode</p></figcaption></figure>

### PlayCanvas

In PlayCanvas, models utilizing Polygon Streaming are treated differently between modes:

* **Preview Mode**: In PlayCanvas, Polygon Streaming models are not visible in Preview Mode. This mode only shows basic placeholders, making it unsuitable for evaluating the model’s quality or performance. Developers can use this mode for simple adjustments and placements, but it doesn't reflect the actual capabilities of Polygon Streaming.
* **Play Mode**: Known as "Launch Mode" in PlayCanvas, this mode is activated by clicking the "Launch" button. Play Mode is where Polygon Streaming becomes fully operational, streaming the model from the cloud and rendering it in high fidelity. Testing models in Play Mode is crucial for an accurate assessment of both performance and quality.

<figure><img src="/files/mO5rAq80uHwWLhaBwaUS" alt="Launch Mode in PlayCanvas"><figcaption><p>Launch Mode in PlayCanvas</p></figcaption></figure>

### Unity

In Unity, the terminology and functionality align with industry standards, using Edit Mode and Play Mode:

* **Edit Mode**: Similar to Preview Mode, Edit Mode allows developers to place and adjust models within the Unity editor. While models can be viewed in this mode, the representation might include artifacts, and streaming optimizations are not fully applied. Edit Mode should not be relied upon for evaluating final model quality or performance.
* **Play Mode**: Activated by pressing the "Play" button in the Unity interface, this mode initiates the full capabilities of Polygon Streaming. The models are streamed in real-time from the cloud, showcasing their high-quality rendering and optimized performance. Play Mode is essential for realistic testing and accurate visualization.

<figure><img src="/files/A3dXmhkQdUiRHkI6674u" alt="Play Mode in Unity"><figcaption><p>Play Mode in Unity</p></figcaption></figure>

### Unreal

Unreal Engine follows a similar structure, with Editor Mode and Play Mode being key concepts:

* **Editor Mode**: Referred to as Preview Mode in other contexts, this mode in Unreal Engine allows for basic visualization and placement of models. However, it does not utilize the full power of Polygon Streaming, and visual artifacts may be present. Editor Mode is useful for setup but not for final evaluation.
* **Play Mode**: By clicking the "Play" button in Unreal Engine, developers activate Play Mode, where the game is simulated under real-world conditions. Polygon Streaming is fully engaged, streaming high-quality models from the cloud and ensuring that the performance and appearance meet expectations. Play Mode is crucial for validating the final output.

<figure><img src="/files/kVk9ckAU5zsZF1UDrqY4" alt="Play Mode in Unreal Engine"><figcaption><p>Play Mode in Unreal Engine</p></figcaption></figure>

### Understanding Model Preview in the Polygon Streaming Console

Apart from the in-editor modes like Preview and Play Modes, it's also important to understand how the "Model Preview" feature works within the console. When you upload a model to the Polygon Streaming platform, you can access a "Model Preview" option. This preview opens in a new browser window and provides a basic visualization of your model, giving you a quick way to see the model's appearance and structure.

<figure><img src="/files/FmH9ejC59pt1ttZIk5vC" alt="Model Preview visualization of assets"><figcaption><p>Model Preview</p></figcaption></figure>

However, like Preview Mode in development environments, the "Model Preview" is not indicative of the final streamed quality. It serves more as a preliminary check for the model's appearance rather than a comprehensive evaluation of its streaming performance or interactive capabilities. Developers should always perform thorough testing using Play Mode in their chosen game engine to fully understand how their models will behave in a real-world application.

<figure><img src="/files/tdDezqUO6rnC8ejsE3A4" alt="Model of a coffee cup asset before Preview is selected"><figcaption><p>Model of a Coffee Cup</p></figcaption></figure>

<figure><img src="/files/AiduLrz3HhnM7mfskRkZ" alt="Model Preview of the coffee cup asset"><figcaption><p>Model Preview of a Coffee Cup</p></figcaption></figure>


# Convertor Release Notes

This page details important release notes on convertor releases, version changelogs, bug and feature hotfixes, and compatibility dependencies.

***

**Current version: 3.1.2**

<table><thead><tr><th width="139">Release Date</th><th width="82">Version</th><th>Release Note / Changelogs</th></tr></thead><tbody><tr><td>11/20/2025</td><td>3.1.2</td><td><p><strong>Updates</strong><br></p><ul><li>Mini Textures: Mini textures are now implemented for faster initial loading, loading first a lower resolution texture for the 3D models, which improves over time.</li><li>Updated Presets: Updated presets for conversion, the presets are now presented as Performance, Balanced, Quality and Ultra.</li><li>Ultra Quality Preset: Ultra quality preset is now implemented, providing conversion using PNG textures instead of using KTX image compression, for ultra quality. This is recommended only for single model display, since PNG can display high visual quality, however with a serious hit on performance.</li></ul></td></tr><tr><td>08/06/2025</td><td>3.0.2</td><td><p><strong>Improvements (vs 2.12.10) for converted models</strong></p><ul><li>Up to 12% increase in total mesh count</li></ul><p><strong>Updates</strong></p><ul><li><strong>Animated Models Support</strong>: Animated .GLB format models with animation using rigging, bones, weights, skinned and transforms animations are now supported.</li><li>Note: Vertex animation or morph targets are not currently supported</li><li><strong>Texture and Material Improvements:</strong> Texture baking now picks the best sample type automatically. Texture wrapping and texture sampler properties are now supported.</li><li><strong>Export &#x26; Compression Enhancements:</strong> Improved node hierarchy structure and internal storage structure for more efficient data handling</li><li>Bug Fixes</li></ul><p><strong>Compatibility Dependencies</strong><br>Requires PlayCanvas Plugin version 2.6.2 or higher<br>Requires Unity Plugin version 3.0.0 or higher</p></td></tr><tr><td>06/12/2025</td><td>2.12.10</td><td><p><strong>Major Improvements (vs 2.12.6) for converted models</strong></p><ul><li>Up to 9% smaller converted file sizes</li><li>Up to 29% less draw calls</li><li>Up to 38% less total textures</li><li>Up to 46% less total materials</li><li>Up to 13% less average memory used</li></ul><p><strong>Updates</strong></p><ul><li><strong>[Fixed]</strong> Normal map issue on some mobile and desktop devices</li><li>Overall performance improvements </li></ul><p><strong>Compatibility Dependencies</strong><br>Requires PlayCanvas Plugin version 2.5.2 or higher<br>Requires Unity Plugin version 2.7.4 or higher</p></td></tr><tr><td>02/24/2025</td><td>2.12.6</td><td><p><strong>Major Improvements (vs 2.11.1) for converted models</strong></p><ul><li>Up to 60% less draw calls</li><li>Up to 33% less total textures</li><li>Up to 32% less total materials</li><li>Up to 12% less average memory used</li></ul><p><strong>Updates</strong></p><ul><li><strong>Super Texture Atlas Merging:</strong> Texture data can now be merged into super texture atlases, minimizing draw calls coming from several separated small textures in a 3D model enables the data to be more efficient for streaming.</li><li><strong>Lightmap Support:</strong> Lightmaps are now being supported via a separated file. You can now upload a ZIP file containing your .GLB model and a file called “lightmap.hdr” or “lightmap.png” - this lightmap will be automatically applied to the Streaming Model inside PlayCanvas Engine running with VIVERSE Create SDK. Currently only one lightmap is supported.</li><li><strong>Improved Update Logic:</strong> With the use of Virtual Bounding Boxes, the update logic is now improved to calculate distances more accurately according to the actual visible mesh geometry in the scene. The camera frustum is also improved with a perspective bias update logic, making what the user is actually seeing more important and prioritized during streaming.  </li><li><strong>Performance Improvements:</strong> Several improvements and modifications regarding performance, such as Material Merging and Super Texture Atlas. Large environment scenes will see bigger improvements with minimized draw calls usage and optimized VRAM usage.</li><li>Bug fixes</li></ul><p><strong>Compatibility Dependencies</strong><br>Requires PlayCanvas Plugin version 2.5.1 or higher<br>Requires Unity Plugin version 2.7.3 or higher</p></td></tr><tr><td>01/13/2025</td><td>2.11.1</td><td><p><strong>Major Improvements (vs 2.9.8) for converted models</strong></p><ul><li>Up to 30% less draw calls</li><li>Up to 30% less memory for textures used</li><li>Up to 49% smaller converted file sizes</li><li>Up to 67% less total memory used</li></ul><p><strong>Updates</strong></p><ul><li><strong>Improved Decimation Algorithm:</strong>  Able to automatically configure the best possible algorithm when creating LODs with as little visual quality loss as possible. </li><li><strong>Shared Materials Feature:</strong> The system is now able to look-up materials being used, combine compatible texture-less materials and remove duplicates from a streaming model. This  optimizes the usage of texture-less materials and minimizes the amount of draw calls created by duplicates or the use of several similar materials in a model. </li><li><strong>Remove the need for Baked Textures:</strong>  Drastic reduction on memory needed for textures, bringing an important gain on overall performance.</li><li>Bug fixes</li></ul><p><strong>Compatibility Dependencies</strong><br>Requires PlayCanvas Plugin version 2.5.0 or higher<br>Requires Unity Plugin version 2.7.1 or higher</p></td></tr><tr><td>10/28/2024</td><td>2.9.8</td><td><p><strong>Updates</strong></p><ul><li>Bug fixes and minor performance updates</li></ul><p><strong>Compatibility Dependencies</strong><br>Requires PlayCanvas Plugin version 2.5.0 or higher<br>Requires Unity Plugin version 2.5.0 or higher<br>Requires Unreal Engine Plugin version 2.0.2 or higher</p></td></tr></tbody></table>


# Polygon Streaming iFrame Integration

This guide provides instructions for setting up an iFrame with a Polygon Streaming object to implement into your webpage, e-commerce product page, or any other type of website.

***

{% stepper %}
{% step %}
Copy the iFrame snippet below.

```
<iframe src="https://yourmodellink/" style="width: 100%; height: 100%;"></iframe>
```

{% endstep %}

{% step %}
Paste it right into your webpage editor. We are using Shopify as our example e-commerce platform.

<figure><img src="/files/HPhHu2uzxcwlWZGEiYZL" alt="iFrame code rendered in a webpage editor"><figcaption><p>iFrame code added and rendered in a webpage editor</p></figcaption></figure>

{% endstep %}

{% step %}
Adjust the width and height to fit your web design.

<figure><img src="/files/sKVCn155aJplm9rSdoOZ" alt="Height and width have been adjusted in the iFrame code"><figcaption><p>Height and width have been adjusted in the iFrame code</p></figcaption></figure>

{% endstep %}

{% step %}
Publish your website to preview.

<div><figure><img src="/files/UxmKgQEyhZJjRt8w8mCi" alt="Visual with viewing angles and skybox options on"><figcaption><p>Visual with viewing angles and skybox options on</p></figcaption></figure> <figure><img src="/files/3SYhkFbjrjLb6zZUgwlS" alt="Visual with viewing angles and skybox options off"><figcaption><p>Visual with viewing angles and skybox options off</p></figcaption></figure></div>
{% endstep %}
{% endstepper %}

{% hint style="info" %}
You can add "*?more=false*" to your Polygon Streaming URL to disable the viewing angles & skybox options.
{% endhint %}


# Polygon Streaming PlayCanvas SDK

This document guides you through the three options available for integrating VIVERSE Polygon Streaming into PlayCanvas projects.

***

### PlayCanvas Engine Plugins

Polygon Streaming models can be added to PlayCanvas projects with the use of our PlayCanvas plugins. These plugins will help to stream high polygon models from the cloud into your projects and build for the web. The table below breaks down the plugins and their usage.

<table><thead><tr><th width="393">Polygon Streaming Integration Option</th><th>Description</th></tr></thead><tbody><tr><td>Option 1: Chrome Browser Extension Plugin</td><td>Used for projects targeting publication to VIVERSE. Dependent on the PlayCanvas Chrome Browser extension being installed.</td></tr><tr><td>Option 2: Standalone Plugin</td><td>Used for projects <strong>NOT</strong> targeting publication to VIVERSE. Dependent on creation of PlayCanvas entities in editor.</td></tr><tr><td>Option 3: HTML Scripting</td><td>Used for projects <strong>NOT</strong> targeting publication to VIVERSE. Dependent on manually adding HTML code to PlayCanvas project.</td></tr></tbody></table>


# Plugin Usage

This document guides you through the three options available for integrating VIVERSE Polygon Streaming with PlayCanvas.

***

### Plugin Installation

{% hint style="info" %}
The PlayCanvas Chrome Browser Extension includes the **Polygon Streaming Plugin**. Instructions for downloading and installing the browser extension can be found [here](https://docs.viverse.com/playcanvas-sdk/playcanvas-extension-setup).
{% endhint %}

<table><thead><tr><th width="294">Polygon Streaming Integration Option</th><th width="215">Installation</th><th>Setup Instructions</th></tr></thead><tbody><tr><td>Option 1: Chrome Browser Extension Plugin</td><td>PlayCanvas Chrome Browser Extension includes the <strong>Polygon Streaming Plugin</strong>. </td><td>Instructions for using the Polygon Streaming Plugin can be found <a href="https://docs.viverse.com/polygon-streaming/integrations-and-plugins/polygon-streaming-playcanvas-sdk/plugin-usage">here</a>.</td></tr><tr><td>Option 2: Standalone Plugin</td><td>Add polygon-streaming.js to PlayCanvas project.</td><td>Instructions for using the Standalone plugin can be found <a href="https://docs.viverse.com/polygon-streaming/integrations-and-plugins/polygon-streaming-playcanvas-sdk/plugin-usage/option-2-standalone-plugin">here</a>.</td></tr><tr><td>Option 3: HTML Scripting</td><td>Download build of PlayCanvas project to access HTML.</td><td>Instructions for using HTML Scripting can be found <a href="https://docs.viverse.com/polygon-streaming/integrations-and-plugins/polygon-streaming-playcanvas-sdk/plugin-usage/option-3-html-scripting">here</a>.</td></tr></tbody></table>


# Option 1: Browser Extension

This document provides a guide for using the Chrome Browser Extension to integrate Polygon Streaming into a PlayCanvas project that targets publication to VIVERSE.

***

### Plugin Usage with the VIVERSE Extension

{% stepper %}
{% step %}
Start by opening your PlayCanvas project and setup using the **VIVERSE Extension**. Once the extension is setup and you’re logged in, you are ready to add special components. To add a **Polygon Streaming** component to the scene, add an **Empty** component to the scene and give a name like "**Streaming Model".**

<figure><img src="/files/nfMEUi6ojLGqtgTXoH0e" alt="Adding an Empty component to the scene"><figcaption><p>Adding an Empty component to the scene</p></figcaption></figure>
{% endstep %}

{% step %}
With the new component selected, click on **EDIT VIVERSE EXTENSION**. Choose a **Media** type of plugin, select the module **PolygonStreaming** and click the plus **+** symbol. Now you just added a Polygon Streaming component to your scene.

<figure><img src="/files/Nwj09QPnaByrSOnHFoZt" alt="Add a PolygonStreaming component to the scene"><figcaption><p>Add a PolygonStreaming component to the scene</p></figcaption></figure>
{% endstep %}

{% step %}
Paste the **Asset ID** of your streaming model into the **Polygon Streaming URL** field. Once you added the URL in the field, the object will preview inside your scene. This is just a preview; once **Published**, the streaming model will start actively streaming from the cloud.

<figure><img src="/files/fa8RUKjljsduBkaid8H9" alt="Previewing the object inside your scene"><figcaption><p>Previewing the object inside your scene</p></figcaption></figure>
{% endstep %}

{% step %}
That’s all you need to stream your models inside your VIVERSE Scene. You can also modify the **Polygon Streaming Parameters** going to **Viverse Scene Settings / Polygon Streaming** and change the parameters according to your preferences. You can check the **Supported Parameters** list below in this documentation.

<figure><img src="/files/2Nke3d32R7Ake0BsmZUA" alt=""><figcaption><p>Viverse scene settings</p></figcaption></figure>
{% endstep %}

{% step %}
After you published your VIVERSE World scene, the **Streaming Model** will automatically start to stream inside the scene.

<figure><img src="/files/SNgGm8L25Zk5ns8ser02" alt="The Streaming Model displays inside the scene"><figcaption><p>The Streaming Model inside the scene</p></figcaption></figure>
{% endstep %}
{% endstepper %}

### Video Overview

{% embed url="<https://www.youtube.com/watch?v=2wM07D-aEcg>" %}


# Polygon Streaming Project Settings

This page details how to control the global settings of VIVERSE Polygon Streaming within your PlayCanas project when using the VIVERSE PlayCanvas extension.

***

{% hint style="warning" %}
Polygon Streaming project settings are only available when using the VIVERSE [extension](/playcanvas-toolkit/toolkit-setup) of the PlayCanvas editor.
{% endhint %}

### Stream Controller Component

The **Stream Controller** or **Polygon Streaming Settings** (VIVERSE SDK) manages the streaming of models and streaming parameters inside your project. It can be found under **Viverse Scene Settings.**

<figure><img src="/files/W2LJDjAeuCyon23TmVOS" alt=""><figcaption><p>Polygon Streaming Settings (VIVERSE SDK)</p></figcaption></figure>

When **Occlusion Culling** is enabled objects that are hidden by other objects will not be rendered. It depends on the model how much  occlusion culling will improve performance.

**Occlusion Geometry** allows you to specify what is used to check if a mesh is occluded. **Bounding Box** option is faster but less accurate while the **Mesh** option is slower but more accurate. Bounding Box should be sufficient in most cases.

**Occlusion Query Frequency** determines how many times a second meshes are checked if they are occluded. A lower value will increase performance but meshes will take longer to reappear.

The **Triangle Budget** is a limit on the amount of triangles that will be drawn per frame. Increasing this will lead to better visual quality, but of course also higher processing and memory utilization. It's recommended to keep Triangle Budget to at least 30% of the full amount of polygons that you are going to stream. For example, if you are going to stream a 3D model of 10 million polygons, it's recommended to use a Triangle Budget of at least 3 million.

The **Mobile Triangle Budget** is the same as Triangle Budget, however it will be applied in case the system identifies the user is visiting the experience via a mobile device.

With a **Distance Factor** above 1 nearby objects are given preference when allocating triangles. A value below 1 gives preference to further away objects and a value of 1 is neutral.

It will stop improving geometry when it reaches the **Maximum Quality.** This can be used to stop far away objects from showing more detail than is necessary. Setting a value of 0 means there is no limit on quality apart from the triangle budget.

Details of parameters are also set on the Supported Parameters section.

### Supported Parameters

<table data-full-width="true"><thead><tr><th>Name</th><th width="216.18182373046875">Description</th><th>Min Value</th><th width="126.8182373046875">Default Value</th><th>Max Value</th><th width="138.727294921875">Low-End PC (Mobile)</th><th>Mid Range PC</th><th>High-End PC</th></tr></thead><tbody><tr><td><strong>Occlusion Culling</strong></td><td>Enable Dynamic Occlusion Culling.</td><td>FALSE</td><td>FALSE</td><td>TRUE</td><td>FALSE</td><td>FALSE</td><td>FALSE</td></tr><tr><td><strong>Occlusion Geometry</strong></td><td>Mesh: Use the mesh to check if it's occluded<br><br>Bounding Box: Use the bounding box of the mesh to check if it's occluded.</td><td>Bounding Box</td><td>Bounding Box</td><td>Bounding Box</td><td>Bounding Box</td><td>Bounding Box</td><td>Bounding Box</td></tr><tr><td><strong>Occlusion Query Frequency</strong></td><td>Value is in times per second. A value of 0 means will it run on every frame.</td><td>0</td><td>8</td><td>60</td><td>5</td><td>8</td><td>12</td></tr><tr><td><strong>Triangle Budget</strong></td><td>The maximum amount of triangles that you want to be in the scene at any single point.</td><td>0</td><td>5000000</td><td>Depends on device</td><td>Mobile won't use this parameter</td><td>5000000</td><td>5000000</td></tr><tr><td><strong>Mobile Triangle Budget</strong></td><td>The triangle budget used on a mobile device. If it is set to 0 it will use the non-mobile triangle budget.</td><td>0</td><td>3000000</td><td>Depends on device</td><td>3000000</td><td>PC won't use this parameter</td><td>PC won't use this parameter</td></tr><tr><td><strong>Distance Factor</strong></td><td>Preference for nearby objects over objects further away. Values above one mean a preference for nearby objects. Values below one mean a preference for objects further away. One is neutral.</td><td>0</td><td>1.1</td><td>10</td><td>1.1</td><td>1.1</td><td>1.1</td></tr><tr><td><strong>Maximum Quality</strong></td><td>Stops improving geometry that exceeds the maximum quality. This can be used to stop far away objects from showing more detail which can be wasteful. Leaving this at 0 means there is no maximum quality.</td><td>0</td><td>15000</td><td>300000</td><td>15000</td><td>15000</td><td>0</td></tr></tbody></table>


# Streamable Model Component

This page details how to control the settings of individual VIVERSE Polygon Streaming models within your PlayCanvas project when using the VIVERSE PlayCanvas extension.

***

{% hint style="warning" %}
Polygon Streaming component settings are only available when using the VIVERSE [extension](/playcanvas-toolkit/toolkit-setup) of the PlayCanvas editor.
{% endhint %}

### Streamable Model Component

The **Streamable Model Component** or **Polygon Streaming** (VIVERSE SDK) component represents the model to be streamed inside your project. It will ask for a Polygon Streaming URL, which is the address of your newly converted model. Once you paste the address in that field, it's ready to be streamed inside your scene

<figure><img src="/files/Yde0jSgqwc4KroFMsTVp" alt=""><figcaption></figcaption></figure>

**Initial Triangle Percentage** is the percentage of triangles to be first loaded in the scene. You should enter a number between 0 and 1. The lower the number the quicker the model will appear but at a lower quality though will start to update to a higher quality straight away.  &#x20;

**Cast Shadow**s and **Receive Shadows** should be checked in order for the model to cast and receive shadows.

**Force Double Sided Materials** should be checked if parts of the model are not showing up. This usually resolves problems with incorrect normals on the model. If you are not encountering problems with the model you should leave this setting turned off or you will double the amount of triangles that need to be rendered.

**Use Alpha** should usually be turned on but can be turned off to increase performance but transparent materials will render opaque.

**Play Animation Automatically** determines whether to play the embedded animation when the scene is loaded.&#x20;

**Animation To Play** is the embedded animation to play when the scene loads. This can either be the name of the animation or it's index i.e. 0 would be the first animation. If it's left blank it will play the first animation. You can view the names of the animations in a GLTF file in the [PlayCanvas Model Viewer](https://playcanvas.com/model-viewer). If you have supplied an animation state graph the initial animation will be the animation that is set in the default state. The default state is the one that is connected to START in the state graph editor.

To play a different animation you can fire the `streamable-model:play-animation` event on the entity that has the streamable model.

```javascript
this.entity.fire('streamable-model:play-animation', 1, 0.2);
```

The second argument is the name or index of the animation you want to play - 1 will play the second animation. The third argument is the transition duration in seconds. It's optional and defaults to one second.

**Animation State Graph** allows you to control the transition between different animations when triggered from code. [You can create the asset in the PlayCanvas editor](https://developer.playcanvas.com/user-manual/animation/anim-state-graph-assets/).&#x20;

**Animation States** map states in the animation state graph to the embedded animations. To the left of Array Size enter the number of state/animation mappings you want. State should be the state name in the state graph. Animation is the name or index of the embedded animation and layer is the layer in the state graph. If it's left blank it will use the base layer.

Scripting Polygon Streaming animations works in the same way as standard PlayCanvas animations but you shouldn't try to access the anim component in the initialize method of a script component as it's only added once the model has loaded.

Below is an example of triggering an animation using a state graph. The state graph has a running parameter and when it's set to true will transition to the Running state. You will need to setup the parameters, states and transitions in the state graph.

```javascript
this.entity.anim.setBoolean('running', true);
```

In the **VRM Animations** section you can add VRMA files to animate an XRG that has been created from a VRM file. To the left of Array Size enter the number of animations you want. The default animation is the animation that plays after a non-looping animation has finished. This will usually be an idle animation. To play a VRM animation you fire the `streamable-model:play-animation` event on the entity that has the streamable model.

```javascript
this.entity.fire('streamable-model:play-animation', 'idle', 0.2);
```

The second argument is the name you provided in the VRM animations configuration. The third argument is the duration of the transition in seconds. It's optional and defaults to one second.

To animate a VRM expression you would do the following:

```javascript
this.entity.fire('vrm-expression:start-emotion', 'expressionName', { 
  times: [0, 0.1, 0.2],  
  values: [0, 1, 0]
});
```

Do note that animations won't play in the editor extension. You will have to publish your scene to see your model animating.

**Priority Level** and **Quality Priority** affect the same setting but the first provides presets while the second allows you to enter a number. This setting affects how triangles are allocated to each model and is a ratio. For example if you have one model set to 1 and another set to 2 and both models are the same distance from the camera, the second model will receive twice as many triangles as the first.

<table data-full-width="false"><thead><tr><th>Priority</th><th>Value</th></tr></thead><tbody><tr><td>Default</td><td>1</td></tr><tr><td>Higher</td><td>1.5</td></tr><tr><td>Highest</td><td>2</td></tr><tr><td>Custom</td><td>Enter a number in Quality Priority</td></tr></tbody></table>

**Environment Asset** allows to set a custom environment map to use for the streaming model. It expects a cubemap which [you can create in the PlayCanvas editor](https://developer.playcanvas.com/user-manual/assets/types/cubemap/). This is useful if you have not set an environment asset in the scene but still want reflections to show on the model.


# Option 2: Standalone Plugin

This document provides a guide for using the Standalone Plugin to integrate Polygon Streaming into a PlayCanvas project that does not target publication to VIVERSE.

***

### PlayCanvas Standalone Plugin

For PlayCanvas projects that are not targeting publication to VIVERSE, this PlayCanvas standalone plugin is the first option for integrating Polygon Streaming into those projects. Because these projects will not be published to VIVERSE, we've created a standalone version of the plugin that does not utilize the PlayCanvas VIVERSE Chrome browser extension.

{% hint style="info" %}
***For using Polygon Streaming Standalone in PlayCanvas, the Basis Library needs to be imported inside your PlayCanvas project. To import the library go to: Settings -> Rendering -> and click the IMPORT BASIS button.***
{% endhint %}

{% stepper %}
{% step %}

#### Install the plugin

Download the latest version of the PlayCanvas standalone plugin [here](https://stream.viverse.com/assets/streamablemodel/2.9.1/playcanvas/PolygonStreaming-PlayCanvas-2.9.1.zip). Import the **polygon-streaming.js** script into the **Assets** window of the PlayCanvas project.

<figure><img src="/files/rBOWAwzpTWTU8Fk5l0ru" alt="" width="282"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create the StreamController

A. Create a new entity and name it **StreamController**. This entity will control the streaming models inside the project.&#x20;

B. Add the **StreamController** script to the StreamController entity. You can check detailed information on these parameters [here](https://docs.viverse.com/polygon-streaming/integrations-and-plugins/polygon-streaming-playcanvas-sdk/polygon-streaming-project-settings).

C. Add the **Camera** entity from the project to the **Camera** field.

<figure><img src="/files/8Sabt3sVESqZXjnzDa80" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Create the StreamableModel&#x20;

A. Create a new entity and name it **StreamableModel**.

B. Add the **StreamableModel** script to the StreamableModel entity. This will create the component to stream the model inside the scene.

C. Paste the **URL** address of your streaming model into the **Path or url to model** field.&#x20;

<figure><img src="/files/lH0wPMX5zTiZAtD7GjfW" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Test the model

Launch the project in the PlayCanvas editor to test the streaming model.

<figure><img src="/files/bO1aZ5ohOo2axgBj3IUZ" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Component Attributes

See [here](/polygon-streaming/integrations-and-plugins/polygon-streaming-playcanvas-sdk/plugin-usage/component-attributes) for a description of the attributes used in the Stream Controller and Streamable Model components.


# Option 3: HTML Scripting

This document provides a guide for using HTML Scripting to integrate Polygon Streaming into a PlayCanvas project that does not target publication to VIVERSE.

***

### PlayCanvas Direct HTML Plugin

For PlayCanvas projects that are not targeting publication to VIVERSE, this PlayCanvas Direct HTML Plugin is the second  path for integrating Polygon Streaming into those projects. Because these projects will not be published to VIVERSE, we've created a direct HTML version of the plugin that does not utilize the PlayCanvas VIVERSE Chrome browser extension.

{% stepper %}
{% step %}

#### Installation

<pre class="language-html"><code class="lang-html"><strong>// add Polygon Streaming SDK to your HTML with the correct version number
</strong><strong>
</strong><strong>&#x3C;script src="https://stream-stage.viverse.com/assets/streamablemodel/{LATEST_STABLE_VERSION}/PolygonStreaming.js">&#x3C;/script>
</strong></code></pre>

{% endstep %}

{% step %}

#### Plugin Usage

The script in PlayCanvas should be added to your HTML file after initializing of pc.Application, but before you attach the script component to the Entity and run Application.start()

```javascript
// add a camera entity for Polygon Streaming SDK to use

const camera = new pc.Entity("camera");
camera.addComponent("camera", {
    farClip: 1000,
    nearClip: 0.1
});
app.root.addChild(camera);
```

{% endstep %}

{% step %}

#### Define an Entity

After then you need to define a new Entity, add it to the root and define the attributes ([described here](/polygon-streaming/integrations-and-plugins/polygon-streaming-playcanvas-sdk/plugin-usage/component-attributes)). In the streamController only the camera attribute is required and in streamableModel only the path attribute is required but all attributes are shown below for informational purposes.

```javascript
// add a Polygon Streaming controller
const streamController = new pc.Entity();
streamController.addComponent("script");
streamController.script.create("streamController", {
	attributes: {
		camera: camera,
		cameraType: "nonPlayer",
		occlusionCulling: true,
		occlusionGeometry: "boundingBox",
		occlusionQueryFrequency: 8,
		triangleBudget: 3000000
		mobileTriangleBudget: 1000000,
		minimumDistance: 0.01,
		distanceFactor: 1.1,
		distanceType: "boundingBoxCenter",
		maximumQuality: 15000,
		closeUpDistance: 3,
		closeUpDistanceFactor: 5,
		iosMemoryLimit: 0
	}
});

// add a Polygon Streaming model
let streamableModel = new pc.Entity();
streamableModel.addComponent("script");
streamableModel.script.create("streamableModel", {
	attributes: {
		path: "/model.xrg",
		qualityPriority: 1,
		useAlpha: true,
		castShadows: true,
		receiveShadows: true,
		doubleSidedMaterials: false,
		initialTrianglePercent: 0.1
		playAnimationAutomatically: true,
  	animation: 0
	}
});

// add model to stream controller
streamController.addChild(streamableModel);

// add stream controller to the scene
app.root.addChild(streamController);
```

{% endstep %}
{% endstepper %}


# Component Attributes

These component attributes apply to the standalone plugin and the one used in HTML scripting.

***

If using these components in code the attribute name would be a camel case version of the attributes below unless specified in brackets.

### Stream Controller

| Attribute                          | Description                                                                                                                                                                                                                              | Default Value     |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| <p>Camera<br><em>Required</em></p> | This is the camera used in the scene. This attribute is required.                                                                                                                                                                        | None              |
| Camera Type                        | <p><strong>Non-player camera</strong> ('nonPlayer'): A camera that is not attached to a player e.g. a camera that orbits an object. <br><strong>Player camera</strong> ('player'): A camera that is attached to a player</p>             | Non-player camera |
| Occlusion Culling                  | Whether to enable occlusion culling. When occlusion culling is enabled objects that are hidden by other objects will not be rendered. It depends on the model whether this setting will improve performance.                             | False             |
| Occlusion Geometry                 | <p><strong>Bounding Box</strong>: Use the bounding box of each mesh to check if it's occluded. This is less accurate but faster.<br><strong>Mesh</strong>: Use the mesh to check if it's occluded. This is more accurate but slower.</p> | Bounding Box      |
| Occlusion Query Frequency          | How many times per second to check for occlusion. A lower number will improve performance but geometry will take longer to reappear.                                                                                                     | 8 times/second    |
| Triangle Budget                    | The maximum amount of triangles that you want to be in the scene.                                                                                                                                                                        | 5000000           |
| Mobile Triangle Budget             | The triangle budget used on a mobile device. If it is set to 0 it will use the non-mobile triangle budget.                                                                                                                               | 3000000           |
| Minimum Distance                   | The smallest possible distance to the camera.                                                                                                                                                                                            | 0.01              |
| Distance Factor                    | Preference for nearby objects over objects further away. Values above one mean a preference for nearby objects. Values below one mean a preference for objects further away. One is neutral.                                             | 1.1               |
| Maximum Quality                    | Stops improving geometry that exceeds the maximum quality. This can be used to stop far away objects from showing more detail than is necessary. Setting it to 0 means there is no maximum quality.                                      | 15000             |
| Close Up Distance Factor           | The distance factor used when close-up to an object. Should be higher than the standard distance factor.                                                                                                                                 | 5                 |
| Close Up Distance                  | The distance where it starts using close-up distance factor. Set it to 0 to not use close-up distance factor.                                                                                                                            | 3                 |
| iOS Memory Limit                   | The maximum amount of memory in MB that meshes and textures can consume on iOS devices to avoid the page crashing. Use -1 for no limit or 0 to let Polygon Streaming determine a device specific limit.                                  | 0                 |

### Streamable Model

| Attribute                                    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Default Value |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| <p>Model URL (path)<br><em>Required</em></p> | The URL of the XRG file. This attribute is required.                                                                                                                                                                                                                                                                                                                                                                                                                      | None          |
| Quality Priority                             | How much to prioritize the quality of this model relative to the quality of other models in the scene. It is a ratio.                                                                                                                                                                                                                                                                                                                                                     | 1             |
| Initial Triangle Percent                     | Percentage of triangle budget to initialize the model with. It should be a number between 0 and 1                                                                                                                                                                                                                                                                                                                                                                         | 0.1           |
| Cast Shadows                                 | Whether the model should cast shadows.                                                                                                                                                                                                                                                                                                                                                                                                                                    | True          |
| Receive Shadows                              | Whether the model should receive shadows.                                                                                                                                                                                                                                                                                                                                                                                                                                 | True          |
| Force Double Sided                           | Render the model double sided regardless of the setting in the model file.                                                                                                                                                                                                                                                                                                                                                                                                | False         |
| Use Alpha                                    | Whether to render semi-transparency in materials.                                                                                                                                                                                                                                                                                                                                                                                                                         | True          |
| Use Embedded Collider                        | Determines whether the embedded collider should be used in physics.                                                                                                                                                                                                                                                                                                                                                                                                       | True          |
| Play Animation Automatically                 | Whether to play the embedded animation automatically                                                                                                                                                                                                                                                                                                                                                                                                                      | True          |
| Animation To Play                            | The name or index of the embedded animation to play if an animation state graph is not provided. An index value of 0 is the first animation. If no value is supplied it will play the first animation.                                                                                                                                                                                                                                                                    | None          |
| Animation State Graph                        | Create an Anim State Graph in the Playcanvas editor and drag it onto this attribute. If no asset is provided a default state graph will be created.                                                                                                                                                                                                                                                                                                                       | None          |
| Animation States (animationStateMappings)    | <p>Assigns animations to states in the animation state graph. Nested attributes include:<br><strong>state</strong>: The state in the state graph<br><strong>animation</strong>: The embedded animation name or index.<br><strong>layer</strong>: The layer in the state graph. If left blank will use the base layer.</p>                                                                                                                                                 | None          |
| VRM Animations (vrmAnimations)               | <p>VRM animations to animate an XRG that has been created from a VRM file.<br>Nest attributes include:<br><strong>name</strong>: The name you want to assign to this animation.<br><strong>asset</strong>: The VRMA animation asset.<br><strong>loop</strong>: Whether the animation loops. Default: true<br><strong>default</strong>: Whether it's the default animation. After a non-looping animation it will revert back to the default animation. Default: false</p> | None          |
| Environment Asset                            | Use a cubemap asset if you want to provide an environment map otherwise it will use the scene's environment map.                                                                                                                                                                                                                                                                                                                                                          | None          |
| Hash Code                                    | Hash code to validate streaming model.                                                                                                                                                                                                                                                                                                                                                                                                                                    | ''            |


# Polygon Streaming Unity SDK

This page details basic information about using VIVERSE Polygon Streaming with Unity.

***

### Overview

Polygon Streaming Unity SDK lets you stream high-fidelity 3D models from the cloud into Unity projects. Models are converted to the optimized XRG format and streamed progressively with automatic LOD selection, texture compression, and occlusion culling. Build for desktop, mobile, WebGL, and VR headsets.

**Package name**: `com.viverse.polygon-streaming` **Unity version**: 2021+ **Render pipelines**: Built-in (BRP), URP, HDRP

#### Supported Model Types

| Source Format | Animation                              | VRM Features                                     |
| ------------- | -------------------------------------- | ------------------------------------------------ |
| GLB / glTF    | Embedded clips, Mecanim humanoid       | -                                                |
| FBX           | Embedded clips, Mecanim humanoid       | -                                                |
| VRM 1.0       | VRMA, Mecanim humanoid, embedded clips | Expressions, look-at, spring bones, first-person |

***

### Download

[**Download the latest version (v3.4.0)**](https://stream.viverse.com/assets/downloads/StreamingUnityPlugin/3-4-0/PolygonStreaming-Unity-3-4-0.tgz)

***

### Installation

1. Open **Window > Package Manager** in Unity.
2. Click the **+** button and choose **Add package from tarball...**.
3. Select the downloaded `.tgz` file.

The package installs its dependencies automatically. Do **not** copy the package folder into your project manually.

#### Verify Installation

After installation, the package appears under **Packages** in the Project panel as `Polygon Streaming`.

#### Optional: UniVRM (for VRMA animation)

Core VRM features work without UniVRM. VRMA animation playback requires it. Install via **Add package from git URL**:

```
https://github.com/vrm-c/UniVRM.git?path=/Assets/UniGLTF#v0.130.1
https://github.com/vrm-c/UniVRM.git?path=/Assets/VRM10#v0.130.1
```

***

### Documentation Pages

| Page                                                                                                                             | Description                                           |
| -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| [Quick Start](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/quick-start)                               | Get a model streaming in under 5 minutes              |
| [Stream Controller Settings](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/stream-controller-settings) | Scene-level streaming configuration                   |
| [Streaming Model Component](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/streaming-model-component)   | Per-model settings and inspector fields               |
| [Static Model Streaming](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/static-model-streaming)         | Code examples for static (non-animated) models        |
| [Animated GLB Models](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/animated-glb-models)               | Animation system, custom Animator Controllers         |
| [VRM Avatar Streaming](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/vrm-avatar-streaming)             | VRM expressions, spring bones, look-at, VRMA, Mecanim |

***

### Dependencies

| Package                 | Version | Purpose                  |
| ----------------------- | ------- | ------------------------ |
| `com.unity.cloud.ktx`   | 3.3.0   | Texture compression      |
| `com.unity.burst`       | 1.8.4   | Performance optimization |
| `com.unity.mathematics` | 1.2.6   | Math utilities           |
| `com.unity.collections` | 1.5.2   | Native collections       |

***

### Change Log

| Date        | Version | Release Notes                                                                                                                                |
| ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Coming Soon | 3.4.0   | VRM support, network optimization                                                                                                            |
| 03/10/2026  | 3.3.0   | Shader optimization, WebGL memory optimization, mesh memory optimization, fix preview issue, gizmo control, custom Animator support          |
| 01/09/2026  | 3.2.9   | Optimize occlusion culling in WebGL                                                                                                          |
| 12/10/2025  | 3.2.8   | Disable model normal option, occlusion culling optimization, built-in pipeline shader optimization                                           |
| 11/27/2025  | 3.2.7   | Vertex color in URP, fix alpha cutoff in URP, fix older version model rendering                                                              |
| 11/19/2025  | 3.2.6   | Fix preview bug, fix URP support                                                                                                             |
| 11/10/2025  | 3.2.5   | Fix mini-texture support bug                                                                                                                 |
| 11/07/2025  | 3.2.4   | WebGL support                                                                                                                                |
| 10/30/2025  | 3.2.3   | Optimize occlusion culling                                                                                                                   |
| 10/29/2025  | 3.2.2   | Mini texture support, fix optimal tree                                                                                                       |
| 10/22/2025  | 3.2.1   | Enable instancing on unlit URP materials                                                                                                     |
| 10/16/2025  | 3.2.0   | GPU instancing support                                                                                                                       |
| 10/08/2025  | 3.1.4   | PNG texture support                                                                                                                          |
| 09/22/2025  | 3.1.3   | Remove hidden node mesh for improved resource management                                                                                     |
| 09/04/2025  | 3.1.2   | Fix camera update issue                                                                                                                      |
| 08/15/2025  | 3.1.1   | Fix offscreen model initialization, resolve obfuscation errors                                                                               |
| 08/12/2025  | 3.1.0   | Xrgc 6 support, hidden node performance optimization, fix memory leak, bounding box only partition                                           |
| 06/18/2025  | 3.0.0   | Animated model support, GltFast bundled into assembly, xrgc version 7 (bone weights/indices), occlusion culling disabled for animated models |
| 05/07/2025  | 2.7.5   | SWIZZLEGGGR normal map support                                                                                                               |
| 04/15/2025  | 2.7.4   | Fix shader rendering on Android devices                                                                                                      |
| 02/26/2025  | 2.7.3   | Change triangle budget default to 3M                                                                                                         |
| 02/25/2025  | 2.7.2   | Minimal support for info.json v0.0.5                                                                                                         |
| 12/11/2024  | 2.7.1   | Hotfix model not updating                                                                                                                    |
| 12/03/2024  | 2.7.0   | Xrgc version 6 support, performance optimization                                                                                             |
| 11/07/2024  | 2.6.0   | Web service optimization, shader updates, culling mask default                                                                               |
| 10/07/2024  | 2.5.0   | Vertex color, multi UV set, KHR\_materials\_emissive\_strength (BRP), occlusion culling performance                                          |
| 08/30/2024  | 2.4.2   | Fix shader on Android devices                                                                                                                |
| 08/26/2024  | 2.4.1   | Fix preview, KHR\_texture\_transform, specular glossiness color                                                                              |
| 07/22/2024  | 2.4.0   | KHR\_materials\_pbrSpecularGlossiness support, fix alpha mode                                                                                |
| 07/09/2024  | 2.3.9   | Fix shader not auto-building                                                                                                                 |
| 07/05/2024  | 2.3.8   | Fix texture transform issue                                                                                                                  |
| 07/03/2024  | 2.3.7   | Fix double-sided material transparency, add signature check                                                                                  |
| 06/12/2024  | 2.3.6   | Xrgc version 4 (materials from info.json), fix XrgcMaterial defaults                                                                         |
| 05/26/2024  | 2.3.5   | Fix memory leaks, double-sided material in BRP/URP                                                                                           |
| 04/26/2024  | 2.3.4   | Hotfix light probe usage                                                                                                                     |
| 04/15/2024  | 2.3.3   | Graceful destroy/disable handling, free resources on hide/disable                                                                            |
| 03/23/2024  | 2.3.2   | Light probe usage in StreamingModel                                                                                                          |
| 02/06/2024  | 2.3.1   | Fix triangle budget bug, deduplicate metallic roughness/occlusion texture                                                                    |
| 01/02/2024  | 2.3.0   | Occlusion map (URP), double-sided shader (Android)                                                                                           |
| 11/22/2023  | 2.2.9   | Normal map support                                                                                                                           |
| 11/16/2023  | 2.2.8   | Better sRGB color support                                                                                                                    |
| 11/15/2023  | 2.2.7   | Occlusion map support                                                                                                                        |
| 11/07/2023  | 2.2.6   | Unity 2021 compatibility                                                                                                                     |
| 11/07/2023  | 2.2.5   | BRP-Unlit material, fix Android streaming assets                                                                                             |
| 11/01/2023  | 2.2.4   | Close-up distance and close-up distance factor                                                                                               |
| 10/17/2023  | 2.2.3   | Fix metallic material                                                                                                                        |
| 08/30/2023  | 2.2.1   | Fix preview rendering                                                                                                                        |
| 08/28/2023  | 2.2.0   | KTX texture encoding, turbo-jpeg loading, transparent material occlusion fix                                                                 |
| 08/14/2023  | 2.1.0   | Burst-accelerated occlusion raycasts, improved LOD algorithm                                                                                 |
| 07/19/2023  | 2.0.0   | Dynamic occlusion culling, simplified shaders, triangle budget algorithm                                                                     |


# Quick Start

Get a model streaming in your Unity scene in under 5 minutes.

### Option A: Use Prefabs (Recommended)

The SDK includes ready-to-use prefabs. Import them first:

1. Open **Window > Package Manager**.
2. Select **Polygon Streaming** from the package list.
3. Expand the **Samples** section and click **Import**.

Then in your scene:

1. Drag the **Stream Controller** prefab into your scene. Assign your scene camera to the **Viewer Camera** field.
2. Drag the **Streaming Model** prefab into your scene. Assign the `StreamController` to the **Stream Controller** field, and paste your XRG model URL into **Source Url**.
3. Press Play.

***

### Option B: Manual Setup

If you prefer to set up from scratch:

1. Create an empty GameObject, add the `StreamController` component, and assign your scene camera to **Viewer Camera**.
2. Create another empty GameObject where you want the model to appear, add the `StreamingModel` component, assign the `StreamController`, and paste your XRG URL into **Source Url**.
3. Press Play.

> If Viewer Camera is left empty, the SDK falls back to `Camera.main` at runtime and logs a warning.

The model begins streaming automatically on the first frame. No code required for basic usage.

***

### Scene Preview (Edit Mode)

The `StreamingModelPreview` component (added automatically) renders a lightweight preview of each model directly in the Scene View while in Edit Mode. Toggle it on or off in the Inspector.

***

### Required Layer (Occlusion Culling)

If occlusion culling is enabled (default), add a layer named exactly **PolygonStreaming** in **Edit > Project Settings > Tags and Layers**. The SDK auto-creates this layer in the Editor via `AutoAddLayerOnLoad`, but verify it exists if you encounter occlusion issues.

***

### Cache Settings

On first use, the SDK creates `Assets/Resources/SosCacheSettings.asset` automatically. This ScriptableObject controls memory and disk cache sizes. You do not need to create it manually.

***

### Next Steps

* [Stream Controller Settings](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/stream-controller-settings) — Configure LOD budgets, occlusion culling, and distance factors.
* [Streaming Model Component ](/polygon-streaming/integrations-and-plugins/polygon-streaming-playcanvas-sdk/plugin-usage/option-1-browser-extension/streamable-model-component)— Per-model options: materials, animation, VRM settings.
* [Static Model Streaming](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/static-model-streaming) — Code examples for runtime control of static models.
* [Animated GLB Models](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/animated-glb-models) — Play embedded animations with custom Animator Controllers.
* [VRM Avatar Streaming](/polygon-streaming/integrations-and-plugins/polygon-streaming-unity-sdk/vrm-avatar-streaming) — Expressions, spring bones, look-at, VRMA, and Mecanim.


# Stream Controller Settings

The ***`StreamController`*** is the scene-level manager for all streaming models. It drives LOD selection, manages triangle budgets, and runs occlusion culling. There should be one ***`StreamController`*** per scene.

### Key Inspector Fields

| Field                        | Default       | Description                                                                                                            |
| ---------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Viewer Camera**            | —             | Camera used for LOD and occlusion decisions. Falls back to `Camera.main` if unset.                                     |
| **Triangle Budget**          | 3,000,000     | Maximum triangles in the scene. Recommended: at least 30% of total polygons you plan to stream.                        |
| **VRAM Budget**              | 0 (unlimited) | Maximum GPU memory in MB. 0 = no limit.                                                                                |
| **Distance Factor**          | 1.1           | Preference for nearby objects. Above 1 = favor nearby; below 1 = favor distant; 1 = neutral.                           |
| **Close Up Distance**        | 3             | Distance (meters) at which Close Up Distance Factor kicks in. 0 = disabled.                                            |
| **Close Up Distance Factor** | 5             | Distance factor when camera is closer than Close Up Distance.                                                          |
| **Distance Type**            | Bounding Box  | `BoundingBox` (nearest edge) or `BoundingBoxCentre` (center point). Use `BoundingBoxCentre` for walkable environments. |
| **Max Download Size MB**     | 16            | Concurrent download throttle.                                                                                          |
| **Maximum Quality**          | 0             | Cap on LOD improvement. 0 = no cap.                                                                                    |

### Occlusion Culling

| Field                 | Default | Description                                                                         |
| --------------------- | ------- | ----------------------------------------------------------------------------------- |
| **Occlusion Culling** | `true`  | Raycast-based dynamic occlusion culling. Requires a layer named `PolygonStreaming`. |
| **Time Slicing**      | 120     | Frames to accumulate before hiding an occluded object.                              |
| **Rays Per Frame**    | 256     | Occlusion rays cast per frame.                                                      |

> Occlusion culling requires a layer named exactly **PolygonStreaming** in **Edit > Project Settings > Tags and Layers**. The SDK auto-creates this layer in the Editor.


# Streaming Model Component

The ***`StreamingModel`*** component represents a single model streamed into your scene. It requires a reference to a ***`StreamController`*** and a source URL. Loading starts automatically when the component is enabled.

### Key Inspector Fields

| Field                 | Default | Description                                                            |
| --------------------- | ------- | ---------------------------------------------------------------------- |
| **Stream Controller** | —       | Reference to the scene's `StreamController`. Required.                 |
| **Source Url**        | —       | URL of the XRG model to stream.                                        |
| **Light Probe Usage** | `false` | Enable Unity light probe sampling.                                     |
| **Custom Materials**  | `false` | Override default materials with your own opaque/transparent materials. |
| **Quality Priority**  | 1       | Prioritize this model's quality relative to others in the scene.       |

### Animation Fields

| Field                          | Default | Description                                                                       |
| ------------------------------ | ------- | --------------------------------------------------------------------------------- |
| **Animation Mode**             | `Auto`  | Which animation system to use. See Animated GLB Models and VRM Avatar Streaming.  |
| **Custom Animator Controller** | —       | `RuntimeAnimatorController` for Mecanim animation.                                |
| **Auto Play Loop**             | `true`  | Auto-play embedded clips. Disabled automatically when a custom controller is set. |

### VRM Settings

Only take effect on VRM avatars:

| Field                   | Default | Description                                  |
| ----------------------- | ------- | -------------------------------------------- |
| **Enable Spring Bones** | `false` | Enable spring bone physics (hair, clothing). |
| **Enable Look At**      | `false` | Enable eye tracking.                         |
| **Look At Target**      | —       | Transform the avatar's eyes follow.          |

> For full VRM settings (physics multipliers, override options), see `USAGE_VRM_STREAMING.md` in the SDK package.

### Callbacks

```csharp
streamingModel.SetLoadedCallback(() => Debug.Log("Ready!"));
streamingModel.SetLoadFailedCallback(ex => Debug.LogError(ex.Message));
```

### Scene Preview

The `StreamingModelPreview` component renders a lightweight preview in the Scene View while in Edit Mode. Toggle it on/off in the Inspector.


# Static Model Streaming

Stream and display static (non-animated) 3D models. Supported source formats: GLB, FBX, and other geometry formats converted to XRG.

### Minimal Code Example

```csharp
using UnityEngine;

public class StaticModelExample : MonoBehaviour
{
    public StreamingModel streamingModel;

    void Start()
    {
        streamingModel.SetLoadedCallback(() => Debug.Log("Model is ready!"));
        streamingModel.SetLoadFailedCallback(ex => Debug.LogError(ex.Message));
    }
}
```

Loading starts automatically — no explicit `Load()` call needed.

***

### Common Operations

**Swap URL at runtime:**

```csharp
streamingModel.enabled = false;
streamingModel.sourceUrl = "https://example.com/new_model.xrg";
streamingModel.enabled = true;
```

**Custom materials:**

```csharp
streamingModel.customMaterials = true;
streamingModel.material = myOpaqueMaterial;
streamingModel.transparentMaterial = myTransparentMaterial;
```

**Physics collider:**

```csharp
Mesh mesh = await StreamingModel.FetchColliderMesh("https://example.com/model.xrg");
gameObject.AddComponent<MeshCollider>().sharedMesh = mesh;
```

***

> For the full API reference (camera swapping, events, troubleshooting), see `USAGE_STATIC_MODELS.md` shipped with the SDK package.


# Animated GLB Models

Play animations from streamed GLB/XRG models using the SDK's hybrid animation system.

### How It Works

The SDK bridges a **Legacy Animation** component (drives the mesh) with an **Animator** component (runs your state machine). This is necessary because clips are loaded dynamically at runtime and cannot be embedded in an Animator Controller asset at edit time.

Morph target (blend shape) animations play automatically as part of clip playback — no extra setup needed.

***

### Inspector Setup

1. Enable **Log Animations** on ***`StreamingModel`*** to see available clip names at runtime.
2. Create an **Animator Controller** asset with states. Assign a **dummy AnimationClip** to each state, named to match the model's clip name (e.g. `"Idle"`, `"Walk"`).
3. Assign the controller to **Custom Animator Controller** on ***`StreamingModel`***.
4. Press Play.

> **Clip matching**: The SDK matches by exact name first, then by numeric index (e.g. `"0"`, `"1"`).

***

### Animation Modes

| Mode            | Behavior                                                                                                     |
| --------------- | ------------------------------------------------------------------------------------------------------------ |
| `Auto`          | Default. Uses ***`EmbeddedClips`*** without a controller; ***`VrmMecanim`*** if controller + humanoid bones. |
| `EmbeddedClips` | Standard path for animated GLBs.                                                                             |
| `VrmMecanim`    | Mecanim humanoid retargeting. Requires humanoid skeleton.                                                    |
| `None`          | No animation.                                                                                                |

***

### Quick Code Reference

```csharp
// Wait for load, then control animations
streamingModel.SetLoadedCallback(() =>
{
    streamingModel.PlayAnimation("Idle");
    streamingModel.SetAnimationFloat("Speed", 1.0f);
    streamingModel.SetAnimationTrigger("Jump");
});

// Swap controller at runtime
streamingModel.RuntimeAnimatorController = newController;
```

***

> For the full API reference (StreamingModelAnimator tuning, complete examples, troubleshooting), see `USAGE_ANIMATED_GLB.md` shipped with the SDK package.


# VRM Avatar Streaming

Stream VRM avatars with full VRM 1.0 support: expressions, look-at, spring bones, humanoid animation, VRMA playback, first-person filtering, and MToon material rendering.

### Prerequisites

Core VRM features work **without UniVRM**. VRMA animation and MToon shading require UniVRM:

```
https://github.com/vrm-c/UniVRM.git?path=/Assets/UniGLTF#v0.130.1
https://github.com/vrm-c/UniVRM.git?path=/Assets/VRM10#v0.130.1
```

| Feature                            | Without UniVRM   | With UniVRM            |
| ---------------------------------- | ---------------- | ---------------------- |
| Expressions, look-at, spring bones | Supported        | Supported              |
| Humanoid bones & Mecanim           | Supported        | Supported              |
| First-person filtering             | Supported        | Supported              |
| MToon material (toon shading)      | Fallback (unlit) | Full MToon10 rendering |
| VRMA animation                     | Not supported    | Full support           |

***

### MToon Material Support

VRM models using MToon materials (toon/cel-shading) are automatically rendered with the correct MToon10 shader when UniVRM is installed. The SDK reads `VRMC_materials_mtoon` extension data from the XRG file and applies all MToon properties including shade color, shading shift, rim lighting, outline, and UV animation.

The SDK auto-includes MToon shaders in builds via `MToonShaderIncluder`. If rendering appears incorrect (white/unlit), ensure UniVRM packages are installed and the MToon shader is available in your build target.

| Render Pipeline | Shader Used                               |
| --------------- | ----------------------------------------- |
| Built-in (BRP)  | `VRM10/MToon10`                           |
| URP             | `VRM10/Universal Render Pipeline/MToon10` |

***

### Setup

Same as any model — `StreamController` + `StreamingModel` with a VRM-based XRG URL. VRM controllers are **auto-initialized** on load. No manual VRM component setup needed.

For Mecanim animation, drag an `AnimatorController` into **Custom Animator Controller** and press Play.

***

### Quick API Overview

#### Expressions

```csharp
streamingModel.SetVrmHappy(0.8f);
streamingModel.SetVrmBlink(1.0f);
streamingModel.SetVrmAa(0.5f);    // Lip-sync viseme
streamingModel.ResetVrmExpressions();
```

#### Look-At

```csharp
streamingModel.SetVrmLookAtTarget(Camera.main.transform);
```

#### Spring Bones

```csharp
streamingModel.SetVrmSpringBonesEnabled(true);
streamingModel.ResetVrmSpringBones();  // After teleporting
```

#### VRMA (requires UniVRM)

```csharp
bool ok = await streamingModel.LoadVrmaFromUrlAsync("https://example.com/dance.vrma");
if (ok)
{
    streamingModel.SetVrmaLoop(true);
    streamingModel.PlayVrma();
}
```

#### Humanoid Bones

```csharp
Transform head = streamingModel.GetVrmBone(HumanBodyBones.Head);
```

***

### Important

Always check `IsVrm` and wait for load before calling VRM APIs:

```csharp
streamingModel.SetLoadedCallback(() =>
{
    if (streamingModel.IsVrm)
        streamingModel.SetVrmHappy(1.0f);
});
```

***

> For the full API reference (spring bone physics override, first-person setup, VRM meta, VRMA time control, complete examples, troubleshooting), see `USAGE_VRM_STREAMING.md` shipped with the SDK package.


# Polygon Streaming Unreal Engine SDK

This page details basic information about using VIVERSE Polygon Streaming with Unreal Engine.

***

### Unreal Engine Plugin

Polygon Streaming models can be added to Unreal Engine based projects with the use of the Unreal Engine Plugin. This will allow the streaming of models with multiple millions of polygons to stream from the cloud into your Windows-based projects and applications.

{% stepper %}
{% step %}

#### Installation

Once the plugin’s zip file is downloaded from the Polygon Streaming Web Console. The “**PolygonStreaming-Unreal-Version**” folder should be added to the “**Plugins**” folder of your Unreal Engine Project.

In case the “Plugins” folder does not exist, make sure to create a new folder with the name “Plugins” and add the plugin’s folder inside. Also make sure to close your project before installing the plugin.

<figure><img src="/files/joQPJF8pmPj9GjRHELxh" alt="Create a Plugins folder within your Unreal Engine project if one does not already exist"><figcaption><p>Create a Plugins folder within your Unreal Engine project if one does not already exist</p></figcaption></figure>

After adding the “PolygonStreaming-Unreal-Version” folder inside the “Plugins” folder of your Unreal Engine Project. Double click your project’s file (.uproject). In case it asks you to Re-Build the project, choose “Yes” - now the project will be rebuilt and once it opens, it will have the Polygon Streaming Plugin installed.

Sometimes it may appear like nothing is happening - the Unreal Engine welcome screen may not show, but don’t worry, just give it some time for building and compiling and your project should open normally after a couple of minutes.

{% hint style="warning" %}
The plugin is only available for C++ based projects, and not available for Blueprint only projects. In case you want to use it for a Blueprint based project, first you will need to add C++ classes and make it C++ available.
{% endhint %}
{% endstep %}

{% step %}

#### Verify the Plugin was Installed

If everything went correct, the plugin should now be visible under the Plugins section on your Content Drawer.

<figure><img src="/files/oO72mliN1y4n03ODNh8b" alt="Your plugin should be visible under Plugins in the Content Drawer"><figcaption><p>Your plugin should be visible under Plugins in the Content Drawer</p></figcaption></figure>

In case you can’t see the Plugins folder in your Content Drawer, make sure to tick “Show Plugin Content” on the Content Drawer settings.

<figure><img src="/files/EI1KxfhCeryDCqDh9tVF" alt="Enable Show Plugin Content in your Content Drawer settings"><figcaption><p>Enable <em>Show Plugin Content</em> in your Content Drawer settings</p></figcaption></figure>
{% endstep %}
{% endstepper %}

### Plugin Download and Release Notes

[Download the latest extension version](https://stream.viverse.com/assets/downloads/StreamingUnrealPlugin/2-0-2/PolygonStreaming-Unreal-2-0-2.zip)

**Change Log**

| Release Date | Version | Release Notes                                                                                          |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------ |
|              | 2.0.2   | <ul><li>Updated to work with xrgc5 format</li></ul>                                                    |
|              | 2.0.1   | <ul><li>Updated the plugin to work with Unreal 5.4.x versions.</li></ul>                               |
|              | 2.0.0   | <ul><li>Added support for KTX textures.</li><li>Added model preview to StreamingModel actor.</li></ul> |


# Plugin Usage

This page details the process of setting up the VIVERSE Polygon Streaming plugin in Unreal Engine.

***

To start using the plugin, there are two main classes (Actors) that can be found in the Plugin's folder, or in the Classes menu, which will be all you need to stream your models inside Unreal Engine. The **Stream Controller**, and the **Streaming Model**.

You can drag and drop both Actors inside your Unreal Scene, and be ready to start streaming your converted 3D model inside the engine.

The **Stream Controller** should only be added once, and it controls the streaming feeds inside your scene. The **Streaming Model** is an Actor to be used with all models that will be streaming from the cloud into your project, you only need to add the URL of your converted model into the URL field to start streaming. You can add as many Streaming Models as you want inside your scene.

<figure><img src="/files/f2f4qYRevbeT7iWqj86I" alt="The Stream Controller and Streaming Model Actors"><figcaption><p>Streaming Model Actors are in place</p></figcaption></figure>

Once that's set, press play and your streaming models will already start streaming inside your project!

<figure><img src="/files/pB0wuhDgBbDS2RICDRjo" alt="Streaming Model Actors streaming inside a project"><figcaption><p>Streaming Model Actors streaming inside a project</p></figcaption></figure>


# Polygon Streaming Project Settings

This page details how to control the global settings of VIVERSE Polygon Streaming within your Unreal project.

***

### Stream Controller

The **Stream Controller** manages the streaming of models and streaming parameters inside your project. There must be only one Stream Controller per scene, since it will control all Streaming Models as one.

<figure><img src="/files/0ajRz90Lv4nvqqTJlmIT" alt=""><figcaption></figcaption></figure>

The **Triangle Budget** is a limit on the amount of triangles that will be drawn per frame. Increasing this will lead to better visual quality, but of course also higher processing and memory utilization. It's recommended to keep Triangle Budget to at least 30% of the full amount of polygons that you are going to stream. For example, if you are going to stream a 3D model of 10 million polygons, it's recommended to use a Triangle Budget of at least 3 million.

In case you use too low Triangle Budget, for example 500 thousand to stream 10 million polygons, you may reach the budget before it's possible to showcase the higher quality of the model. Resulting in only showing a lower quality version of the model in order to stay within the budget.

The **Distance Factor** is a factor between the distance of the camera to the object being streamed. The default value of 1.1 has a neutral preference. A higher value, such as 3 or 5, will have a preference for nearby objects over objects further away. These parameters can be changed at runtime to find the sweet spot for your scene.&#x20;

The **Distance Type** can be set to **Bounding Box** or **Bounding Box Center**. In case it's set to Bounding Box it will calculate the distance between the camera and object based on the edges of the bounding box of the object. In case it's set to Bounding Box Center, it will calculate the distance from the center of the object. It's recommended to use Bounding Box for single objects, and Bounding Box Center for full environments in which the user will be walking inside the object.

The **Close Up Distance** is a change in the distance factor between camera and streaming object when the camera gets too close. For example, when the camera is at 3 units or less of distance from the object, it will use the value at **Close Up Distance Factor**, when the camera is further than 3 units from the object, it will use the value at **Distance Factor**. That way the system forces a strong streaming of data when the camera is very close to an object.

The other parameters should be left in default or you can check an explanation on the Supported Parameters section.

### Supported Parameters

| Name                         | Description                                                                                                                                                                                               | Default Value |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| **Triangle Budget**          | The maximum amount of triangles that you want to allow in the scene at any single point.                                                                                                                  | 3000000       |
| **Distance Factor**          | Preference for nearby objects over objects further away. Values above one mean a preference for nearby objects. Values below one mean a preference for objects further away. One is neutral.              | 1.1           |
| **Distance Type**            | Distance type from camera to the meshes bounding boxes.                                                                                                                                                   | Bounding Box  |
| **Close Up Distance**        | The distance where it starts using Close-up Distance Factor instead of Distance Factor. Set it to 0 to not use close-up distance factor.                                                                  | 3             |
| **Close Up Distance Factor** | The distance factor used when close-up to an object. Should be higher than the standard Distance Factor.                                                                                                  | 5             |
| **Maximum Quality**          | Stops improving geometry that exceeds the maximum quality. This can be used to stop far away objects from showing more detail which can be wasteful. Leaving this at 0 means there is no maximum quality. | 3000000       |


# Streaming Model Component

This page details how to control the settings of individual VIVERSE Polygon Streaming models within your Unreal project.

***

### Streaming Model Component

The **Streaming Model** Actor represents the model to be streamed inside your project. A Streaming Model component always needs a reference to the **Stream Controller** component to function and a **Source URL** which is the Asset ID URL of your converted model. Once those two are correctly set, your model will start streaming inside your project as soon as you press Play.

<figure><img src="/files/cVbtd8gh1PH7hKxR5AM4" alt="Streaming Model Details requires a Source URL to stream your model"><figcaption><p>Streaming Model Details requires a Source URL to stream your model</p></figcaption></figure>

The **URL** field is the Asset ID address of your streaming model, once the Asset ID is pasted in this field, the model can be streamed inside your scene.

You can also set different **Quality Priority** options by clicking at **Streaming Runtime Options Settings**. This means that some models can have higher priority of streaming than others, it can use only integer numbers and it works in the following logic:

The **Stream Controller** is a reference to the Stream Controller inside your scene and it needs to be set, otherwise the model won’t appear or be streamed inside your scene.

**Show Preview** option will create a simple preview of the model in the editor scene, so you can position and scale your model correctly without the need to run the game.

In case you would like to use a custom material in your streaming model, you can tick the **Custom Material** box, which will open a new panel that will allow you to change the materials of the model.

### Supported Parameters

| **Name**              | **Description**                                                                                                                                                            | **Default value** |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| **Source URL**        | Asset ID address of the Streamable Model to be streamed into the scene.                                                                                                    | None              |
| **Quality Priority**  | How much to prioritize the quality of this model relative to the quality of other models in the scene. This parameter does nothing if this is the only model in the scene. | 1                 |
| **Stream Controller** | Set the Stream Controller from the scene.                                                                                                                                  | None              |
| **Show Preview**      | Enable model preview in editor scene.                                                                                                                                      | True              |
| **Custom Material**   | Enable if wish to use custom materials.                                                                                                                                    | False             |

<br>


# Polygon Streaming JavaScript SDK

This document guides you through integrating Polygon Streaming to JavaScript projects.

***

Polygon Streaming models can be added to JavaScript projects with the use of our JavaScript SDKs. These plugins will help to stream high polygon models from the cloud into your projects and build for the web.&#x20;

| 3D Library | Installation                                                                                                                 |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
| PlayCanvas | Instructions for installation and setup can be found [here](/polygon-streaming/polygon-streaming-javascript-sdk/playcanvas). |
| Three.js   | Instructions for installation and setup can be found [here](/polygon-streaming/polygon-streaming-javascript-sdk/three.js).   |


# PlayCanvas

This document provides a guide for integrating Polygon Streaming web player for PlayCanvas using the NPM package.

***

This is a guide to use Polygon Streaming web player for PlayCanvas using the NPM package.

### **Getting Started - Sample Project** <a href="#getting-started-sample-project" id="getting-started-sample-project"></a>

You can download a sample project to get started [here](https://stream.viverse.com/assets/streamablemodel/2.9.2/playcanvas/PolygonStreaming-PlayCanvas-Example.zip).

To run the sample project you first need to make sure you have [Node.js](https://nodejs.org/) installed. Then run the following in a terminal:

```bash
npm install
npm run dev
```

This will open a browser window and display the 3D model.

### Explanation of the Code <a href="#toc_1" id="toc_1"></a>

You first need to import registerComponents function from the package:

```javascript
import { registerComponents } from '@polygon-streaming/web-player-playcanvas';
```

And run it:

```javascript
registerComponents();
```

Next, you need to create an entity with a camera component:

```javascript
const camera = new pc.Entity('camera');
camera.addComponent('camera');
```

Your page must contain an entity that has a stream controller script component. The camera entity created above is passed in as an attribute to the stream controller:

```javascript
const streamController = new pc.Entity('Stream Controller');
streamController.addComponent('script');
streamController.script.create('streamController', {
  attributes: {
    camera,
    cameraType: 'nonPlayer',
    triangleBudget: 5000000,
    mobileTriangleBudget: 3000000
  }
});
```

Next you need to create an entity that has a streaming model script component - one for each of your streaming models where you pass in the URL of your XRG file.

```javascript
const streamableModelEntity = new pc.Entity('Streamable Model');
streamableModelEntity.addComponent('script');
streamableModelEntity.script.create('streamableModel', {
  attributes: {
    path: 'https://stream.viverse.com/demos/jet-engine-11m/',
    qualityPriority: 1
  }
});
```

The streaming model entity needs to be a child of the stream controller entity:

```javascript
streamController.addChild(streamableModelEntity);
```

And then you need to add the stream controller entity to the scene:

```javascript
app.root.addChild(streamController);
```

### To Play an Embedded or VRM Animation

```javascript
streamableModelEntity.fire('streamable-model:play-animation', 'animationName', 0.2);
```

The first argument can be the name of the animation or if you don't know the name an index e.g. 0 for the first animation. The second argument is the transition duration and is optional, it will default to one second. This method should be called after the initial model data has loaded. VRM animations need to be provided in the streaming model attributes. Below is an example of passing in two VRM animations. See the `vrmAnimations` attribute in the Streamable Model Attributes section for more information.

```javascript
streamableModelEntity.script.create('streamableModel', {
  attributes: {
    path: '/model.xrg',
    vrmAnimations: [
      {
        name: 'idle',
        asset: new pc.Asset('', 'container', { url: '/Idle.vrma' }),
        default: true
      },
      {
        name: 'yawn',
        asset: new pc.Asset('', 'container', { url: '/Yawn.vrma' }),
        loop: false
      }
    ]
  }
});
```

### To Animate a VRM Expression

```javascript
streamableModelEntity.fire('vrm-expression:start-emotion', 'expressionName', { 
  times: [0, 0.1, 0.2],  
  values: [0, 1, 0]
});
```

The second argument is the expression name and has to exist in the VRM file that the XRG was generated from. The third argument is the animation keyframes.

### Files Required at the Web Root

Polygon Streaming expects the service worker that is used to cache model data to be available at the web root e.g. `/service-worker.js`. This is handled by Vite in the config file - vite.config.js.

### Using Your Own Model <a href="#toc_2" id="toc_2"></a>

* Upload your 3D model to the [console](https://stream.viverse.com/console).
* To get the model URL go to the models section of the [console](https://stream.viverse.com/console/models) and click on the three dots next to your model and select "Copy asset ID".
* Paste this URL into the path attribute of the streaming model script component.

### Stream Controller Attributes <a href="#toc_3" id="toc_3"></a>

* **camera**: The camera entity in your scene.
* **cameraType**: 'nonPlayer' | 'player', default: 'nonPlayer'
  * **nonPlayer**: A camera that is not attached to a player e.g. a camera that orbits an object.
  * **player**: A camera that is attached to a player.
* **occlusionCulling**: boolean, default: false. Whether occlusion culling is enabled. Requires that device supports WebGL 2.
* **occlusionGeometry**: 'boundingBox' | 'mesh', default: 'boundingBox'
  * **boundingBox**: Use the bounding box of the mesh to check if it's occluded. It's slower but more accurate in determining whether a mesh is occluded.
  * **mesh**: Use the mesh to check if it's occlused. It's quicker but less accurate in determining whether a mesh is occluded.
* **occlusionQueryFrequency**: number, default: 8. Value is in times per second. A value of 0 means will it run on every frame.
* **triangleBudget**: number, default: 5000000. The maximum amount of triangles that you want to be in the scene at any single point.
* **mobileTriangleBudget**: number, default: 3000000. The triangle budget used on a mobile device. If it is set to 0 it will use the non-mobile triangle budget.
* **minimumDistance**: number, default: 0.01. The smallest possible distance to the camera.
* **distanceFactor**: number, default: 1.1. Preference for nearby objects over objects further away. Values above one mean a preference for nearby objects. Values below one mean a preference for objects further away. One is neutral.
* **maximumQuality**: number, default: 15000. Stops improving geometry that exceeds the maximum quality. This can be used to stop far away objects from showing more detail which can be wasteful. Setting it to 0 means there is no maximum quality.
* **closeUpDistance**: number, default: 3. The distance where it starts using close-up distance factor. Set it to 0 to not use close-up distance factor.
* **closeUpDistanceFactor**: number, default: 5. The distance factor used when close-up to an object. Should be higher than the standard distance factor.
* **iOSMemoryLimit**: number, default 0. The maximum amount of memory in MB that meshes and textures can consume on iOS devices to avoid the page crashing. Use 0 to let Polygon Streaming determine the limit or -1 for no limit.
* **showLoadingModel**: boolean, default: true. Whether to show the animated loading model when loading streaming models.
* **loadingModelUrl**: string, default: null. URL of a custom animated loading GLB file that overrides the default one.

### Streamable Model Attributes <a href="#toc_4" id="toc_4"></a>

* **path**: string, default: '/model.xrg'. Path or URL of the model.
* **qualityPriority**: number, default: 1. How much to prioritize the quality of this model relative to the quality of other models in the scene. This parameter does nothing if this is the only model in the scene.
* **initialTrianglePercent**: number, default: 0.1. Percentage of triangle budget to initialize the model with.
* **castShadows**: boolean, default: true. Whether the model should cast shadows.
* **receiveShadows**: boolean, default: true. Whether the model should receive shadows.
* **forceDoubleSided**: boolean, default: false. Render the model double sided regardless of the setting in the model file.
* **useAlpha**: boolean, default: true. Whether to render semi-transparency in materials. You might turn this off to increase performance but all your materials will render opaque.
* **useEmbeddedCollider**: boolean, default: true. Wether to use the embedded collider.
* **playAnimationAutomatically**: boolean, default: true. Whether to play the embedded animation automatically.
* **animation**: string or number, default: null. The name or index of the embedded animation to play. An index value of 0 is the first animation. If not value is supplied it will play the first animation.
* **animationStateGraph**: asset, default: null. Supply a state graph if you want more advanced control over the animations. Use toAnimStateGraphAsset function to convert a JavaScript object of the state graph to an asset.
* **animationStateMappings**: array, default: empty array. If you supply an animation state graph you will also need to supply an array of objects that map states to the embedded animations.
  * **state**: string. The state name used in the state graph.
  * **animation**: string or number. The name or index of the embedded animation.&#x20;
  * **layer**: string, default: null. The layer name used in the state graph. If it's omitted it will default to the base layer.
* **vrmAnimations**: array, default: empty array. This is where you add the VMRA animations to animate an XRG generated from a VRM file.
  * **name**: string, default: null. The name of the animation. This is used in the playAnimation method.
  * **asset**: pc.Asset, default: null. A pc.Asset with asset type container that specifies the URL of the VRMA file.
  * **loop**: boolean, default: true. You would set this to false if you want the animation to play once i.e. for an action. Non-looping animations will go back to the default animation when they finish.
  * **default**: boolean, default: false. The default animation is the animation that plays after a non-looping animation. This is usually the idle animation.
* **environmentAsset**: asset, default: null. Use either a cubemap asset with a prefiltered image or the prefiltered image as a texture asset.
* **hashCode**: string, default: ''. Hash code to validate streaming model.


# Three.js

This document provides a guide for integrating Polygon Streaming web player for Three.js using the NPM package.

This is a guide to use Polygon Streaming web player for Three.js using the NPM package.

### **Getting Started - Sample Project** <a href="#getting-started-sample-project" id="getting-started-sample-project"></a>

You can download a sample project to get started [here](https://stream.viverse.com/assets/streamablemodel/2.9.2/threejs/PolygonStreaming-Threejs-Example.zip).

To run the example you first need to make sure you have [Node.js](https://nodejs.org/) installed. Then run the following in a terminal:

```bash
npm install
npm run dev
```

It will open a browser window and display the 3D model.

### **Code Explanation and Usage** <a href="#code-explanation-and-usage" id="code-explanation-and-usage"></a>

Import the StreamController from the package:

```javascript
import { StreamController } from '@polygon-streaming/web-player-threejs';
```

Instantiate the stream controller:

```javascript
const streamController = new StreamController(camera, renderer, scene, controls.target, {
  cameraType: 'nonPlayer',
  triangleBudget: 5000000,
  mobileTriangleBudget: 3000000
});
```

Add a streaming model, passing it a model URL and a Group to act as a model parent:

```javascript
const modelParent = new THREE.Group();
modelParent.position.set(0, 1, 0);
scene.add(modelParent);
streamController.addModel('https://stream.viverse.com/demos/jet-engine-11m/', modelParent, {
  qualityPriority: 1
});
```

Call the stream controller's update method in the animation loop:

```javascript
function animate() {
  controls.update();
  renderer.render(scene, camera);
  streamController.update();
}
renderer.setAnimationLoop(animate);
```

Now you have everything setup to start streaming your 3D models inside your Three.js application.

### To Play an Embedded or VRM Animation <a href="#files-required-at-the-web-root" id="files-required-at-the-web-root"></a>

```javascript
streamController.playAnimation(modelParent, 'animationName', 0.2);
```

The second argument can be the name of the animation or if you don't know the name an index e.g. 0 for the first animation. The third argument is the transition duration and is optional, it will default to one second. This method should be called after the initial model data has loaded. VRM animations needs to be provided in the streaming model options. Below is an example of passing in two VRM animations. See the `vrmAnimations` parameter in the addModel Parameters section for more information.

```javascript
streamController.addModel('/model.xrg', modelParent, {
  vrmAnimations: [
    {
      name: 'idle',
      asset: '/Idle.vrma',
      default: true
    },
    {
      name: 'yawn',
      asset: '/Yawn.vrma',
      loop: false
    }
  ]
});
```

### To Animate a VRM Expression <a href="#files-required-at-the-web-root" id="files-required-at-the-web-root"></a>

```javascript
streamController.animateVrmExpression(modelParent, 'expressionName', {
  times: [0, 0.1, 0.2],  
  values: [0, 1, 0]
});
```

The second argument is the expression name and has to exist in the VRM file that the XRG was generated from. The third argument is the animation keyframes.

### To Set the VRM Look At Target <a href="#files-required-at-the-web-root" id="files-required-at-the-web-root"></a>

The following code will cause the eyes of the model to follow the mouse cursor. `setVrmLookAtTarget()` needs to be called after the initial model data has loaded like in the `EVENT_MODEL_LOAD` event handler.

```javascript
import { StreamController, EVENT_MODEL_LOAD } from '@polygon-streaming/web-player-threejs';

const vrmLookAtTarget = new THREE.Object3D();
camera.add(vrmLookAtTarget);

window.addEventListener('mousemove', (event) => {
  vrmLookAtTarget.position.x = 10.0 * ((event.clientX - 0.5 * window.innerWidth) / window.innerHeight);
  vrmLookAtTarget.position.y = -10.0 * ((event.clientY - 0.5 * window.innerHeight) / window.innerHeight);
});

streamController.addEventListener(EVENT_MODEL_LOAD, function(event) {
  if (event.isVrm && vrmLookAtTarget != null) {
    streamController.setVrmLookAtTarget(modelParent, vrmLookAtTarget);
  }
});
```

### Files Required at the Web Root <a href="#files-required-at-the-web-root" id="files-required-at-the-web-root"></a>

Polygon Streaming expects the service worker that is used to cache model data to be available at the web root e.g. `/service-worker.js`. Models use KTX2 textures so you will need to make the basis transcoder files available at the web root e.g. `/lib/basis_transcoder.js` and `/lib/basis_transcoder.wasm`. Both of these requirements are handled in Vite's config file - vite.config.js.

### **Using Your Own 3D Model** <a href="#using-your-own-3d-model" id="using-your-own-3d-model"></a>

* Upload your 3D model to the online console: <https://stream.viverse.com/console>
* To get the model URL go to the models section of the console: <https://stream.viverse.com/console/models>\
  and click on the three dots next to your model and select "**Copy asset ID**".
* Paste this URL as the first parameter of streamController.addModel() method.

### **Streaming Controller Parameters** <a href="#streaming-controller-parameters" id="streaming-controller-parameters"></a>

| Parameter               | Description                                                                                        |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| camera (required)       | The camera used in the scene.                                                                      |
| renderer (required)     | The WebGL renderer used in the scene.                                                              |
| scene (required)        | The scene object.                                                                                  |
| cameraTarget (required) | The camera target which is a Vector3. If you are using orbit controls it would be controls.target. |
| options (optional)      | You need to provide an object of options. The available options are listed below.                  |

#### Options

All options are optional.

| Option                | Description                                                                                                                                                                                             | Default Value |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| cameraType            | <p><strong>'nonPlayer':</strong> A camera that is not attached to a player e.g. a camera that orbits an object.<br><strong>'player':</strong> A camera that is attached to a player.</p>                | 'nonPlayer'   |
| triangleBudget        | The maximum amount of triangles that you want to be in the scene at any single point.                                                                                                                   | 5000000       |
| mobileTriangleBudget  | The triangle budget used on a mobile device. If it is set to 0 it will use the non-mobile triangle budget.                                                                                              | 3000000       |
| minimumDistance       | The smallest possible distance to the camera.                                                                                                                                                           | 0.01          |
| distanceFactor        | Preference for nearby objects over objects further away. Values above one mean a preference for nearby objects. Values below one mean a preference for objects further away. One is neutral.            | 1.1           |
| maximumQuality        | Stops improving geometry that exceeds the maximum quality. This can be used to stop far away objects from showing more detail which can be wasteful. Setting it to 0 means there is no maximum quality. | 15000         |
| closeUpDistanceFactor | The distance factor used when close-up to an object. Should be higher than the standard distance factor.                                                                                                | 5             |
| closeUpDistance       | The distance where it starts using close-up distance factor. Set it to 0 to not use close-up distance factor.                                                                                           | 3             |
| iOSMemoryLimit        | The maximum amount of memory in MB that meshes and textures can consume on iOS devices to avoid the page crashing. Use 0 to let Polygon Streaming determine the limit or -1 for no limit.               | 0             |
| showLoadingModel      | Whether to show the animated loading model when loading streaming models.                                                                                                                               | True          |
| loadingModelUrl       | URL of a custom animated loading GLB file that overrides the default one.                                                                                                                               | Null          |

### **addModel Parameters** <a href="#streaming-model-parameters" id="streaming-model-parameters"></a>

| Parameter               | Description                                                                            |
| ----------------------- | -------------------------------------------------------------------------------------- |
| URL (required)          | URL of the XRG model. If it doesn't end with .xrg it will append model.xrg to the URL. |
| model parent (required) | The scene object that the streaming model will be attached to.                         |
| options (optional)      | You need to provide an object of options. The available options are listed below.      |

#### Options

All options are optional.

<table><thead><tr><th width="244.76953125">Option</th><th>Description</th><th>Default Value</th></tr></thead><tbody><tr><td>qualityPriority</td><td>How much to prioritize the quality of this model relative to the quality of other models in the scene. This parameter does nothing if this is the only model in the scene.</td><td>1</td></tr><tr><td>initialTrianglePercent</td><td>Percentage of triangle budget to initialize the model with.</td><td>0.1</td></tr><tr><td>castShadows</td><td>Whether the model should cast shadows.</td><td>true</td></tr><tr><td>receiveShadows</td><td>Whether the model should receive shadows.</td><td>true</td></tr><tr><td>forceDoubleSided</td><td>Render the model double sided regardless of the setting in the model file.</td><td>false</td></tr><tr><td>useAlpha</td><td>Whether to render semi-transparency in materials. You might turn this off to increase performance but all your materials will render opaque.</td><td>true</td></tr><tr><td>playAnimationAutomatically</td><td>Whether to play the embedded animation automatically.</td><td>true</td></tr><tr><td>animation</td><td>The name or index of the embedded animation to play. An index value of 0 is the first animation. If not value is supplied it will play the first animation.</td><td>null</td></tr><tr><td>vrmAnimations</td><td>An array of VRM animation data. The attributes of the animation data are:<br><strong>name</strong>: The name of the animation. This is used in the playAnimation method.<br><strong>asset</strong>: The URL of the VRMA animation file.<br><strong>loop</strong>: You would set this to false if you want the animation to play once i.e. for an action. Non-looping animations will go back to the default animation when they finish.<br><strong>default</strong>: The default animation is the animation that plays after a non-looping animation. This is usually the idle animation. The default animation will always loop.</td><td>null</td></tr><tr><td>environmentMap</td><td>A cube map environment texture.</td><td>null</td></tr><tr><td>hashCode</td><td>Hash code to validate streaming model</td><td>''</td></tr></tbody></table>


# Babylon.js

This document provides a guide for integrating Polygon Streaming web player for Babylon.js using the NPM package.

***

This is a guide to use Polygon Streaming web player for Babylon.js using the NPM package.

### **Getting Started - Sample Project** <a href="#getting-started-sample-project" id="getting-started-sample-project"></a>

You can download a sample project to get started [here](https://stream.viverse.com/assets/streamablemodel/2.4.6/babylonjs/PolygonStreaming-Babylonjs-Example.zip).

To run the sample project you first need to make sure you have [Node.js](https://nodejs.org/) installed. Then run the following in a terminal:

```bash
npm install
npm run dev
```

This will open a browser window and display the 3D model.

### **Code Explanation and Usage** <a href="#code-explanation-and-usage" id="code-explanation-and-usage"></a>

Import Babylon.js, glTF loader and the StreamController:

```javascript
import * as BABYLON from '@babylonjs/core/Legacy/legacy';
import '@babylonjs/loaders/glTF';
import { StreamController, loadWasmModule } from '@polygon-streaming/web-player-babylonjs';
```

Load Ammo.js. This is only required if you want to make use of the optional embedded collider in the model. Other physics plugins are not supported as Ammo.js is the only one that supports concave colliders. The Ammo.js physics plugin uses version 1 of the physics engine so you will need to add physics impostors to your meshes rather than physics aggregates or bodies.

```javascript
import ammoWasmJsUrl from './lib/ammo.wasm.js?url';
import ammoWasmWasmUrl from './lib/ammo.wasm.wasm?url';
import ammoJsUrl from './lib/ammo.js?url';

loadWasmModule('Ammo', ammoWasmJsUrl, ammoWasmWasmUrl, ammoJsUrl).then(ammoInstance => {
```

Instantiate the stream controller:

```javascript
const streamController = new StreamController(camera, engine, scene, cameraTarget, {
  cameraType: 'nonPlayer',
  triangleBudget: 5000000,
  mobileTriangleBudget: 3000000,
  minimumDistance: 0.01,
  distanceFactor: 1.1,
  maximumQuality: 15000,
  closeUpDistanceFactor: 5,
  closeUpDistance: 3,
  ammoInstance: ammoInstance
});
```

Add a streaming model, passing it a model URL and a TransformNode to act as a model parent:

```javascript
const modelParent = new BABYLON.TransformNode('Model parent', scene);
modelParent.position.set(0, 1, 0);
streamController.addModel('the URL of your model to be streamed (Asset ID)', modelParent, {
  qualityPriority: 1,
  initialTrianglePercent: 0.1,
  castShadows: true,
  receiveShadows: true,
  forceDoubleSided: false,
  useAlpha: true,
  environmentMap: null,
  hashCode: ''
});
```

Call the stream controller's update method in the render loop:

<pre class="language-javascript"><code class="lang-javascript"><strong> engine.runRenderLoop(function () {
</strong>  scene.render();
  streamController.update();
});
</code></pre>

Now you have everything setup to stream your 3D model inside your Babylon.js application.

### **Using Your Own 3D Model** <a href="#using-your-own-3d-model" id="using-your-own-3d-model"></a>

* Upload your 3D model to the online console: <https://stream.viverse.com/console>
* To get the model URL go to the models section of the console: <https://stream.viverse.com/console/models>\
  and click on the three dots next to your model and select "**Copy asset ID**".
* Paste this URL as the first parameter of streamController.addModel() method.

### **Stream Controller Parameters** <a href="#stream-controller-parameters" id="stream-controller-parameters"></a>

| Parameter               | Description                                                                       |
| ----------------------- | --------------------------------------------------------------------------------- |
| camera (required)       | The camera used in the scene.                                                     |
| engine (required)       | The engine used in the scene.                                                     |
| scene (required)        | The scene object.                                                                 |
| cameraTarget (required) | The camera target which is a Vector3.                                             |
| options (optional)      | You need to provide an object of options. The available options are listed below. |

#### Options

All options are optional.

| Option                | Description                                                                                                                                                                                             | Default Value |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| cameraType            | <p><strong>'nonPlayer':</strong> A camera that is not attached to a player e.g. a camera that orbits an object.</p><p><strong>'player':</strong> A camera that is attached to a player</p>              | 'nonPlayer'   |
| triangleBudget        | The maximum amount of triangles that you want to be in the scene at any single point.                                                                                                                   | 5000000       |
| mobileTriangleBudget  | The triangle budget used on a mobile device. If it is set to 0 it will use the non-mobile triangle budget.                                                                                              | 3000000       |
| minimumDistance       | The smallest possible distance to the camera.                                                                                                                                                           | 0.01          |
| distanceFactor        | Preference for nearby objects over objects further away. Values above one mean a preference for nearby objects. Values below one mean a preference for objects further away. One is neutral.            | 1.1           |
| maximumQuality        | Stops improving geometry that exceeds the maximum quality. This can be used to stop far away objects from showing more detail which can be wasteful. Setting it to 0 means there is no maximum quality. | 15000         |
| closeUpDistanceFactor | The distance factor used when close-up to an object. Should be higher than the standard distance factor.                                                                                                | 5             |
| closeUpDistance       | The distance where it starts using close-up distance factor. Set it to 0 to not use close-up distance factor.                                                                                           | 3             |
| iOSMemoryLimit        | The maximum amount of memory in MB that meshes and textures can consume on iOS devices to avoid the page crashing. Use 0 to let Polygon Streaming determine the limit or -1 for no limit.               | 0             |
| showLoadingModel      | Whether to show the animated loading model when loading streaming models.                                                                                                                               | True          |
| loadingModelUrl       | URL of a custom animated loading GLB file that overrides the default one.                                                                                                                               | Null          |
| ammoInstance          | The Ammo.js instance. Required if you want to make use of the embedded collider.                                                                                                                        | Null          |

### addModel Parameters

| Parameter              | Description                                                                            |
| ---------------------- | -------------------------------------------------------------------------------------- |
| URL (required)         | URL of the XRG model. If it doesn't end with .xrg it will append model.xrg to the URL. |
| modelParent (required) | The scene object that the streaming model will be attached to.                         |
| options (optional)     | You need to provide an object of options. The available options are listed below.      |

#### Options

All options are optional.

| Option                 | Description                                                                                                                                                                | Default Value |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| qualityPriority        | How much to prioritize the quality of this model relative to the quality of other models in the scene. This parameter does nothing if this is the only model in the scene. | 1             |
| initialTrianglePercent | Percentage of triangle budget to initialize the model with.                                                                                                                | 0.1           |
| castShadows            | Whether the model should cast shadows.                                                                                                                                     | true          |
| receiveShadows         | Whether the model should receive shadows.                                                                                                                                  | true          |
| forceDoubleSided       | Render the model double sided regardless of the setting in the model file.                                                                                                 | false         |
| useAlpha               | Whether to render semi-transparency in materials. You might turn this off to increase performance but all your materials will render opaque.                               | true          |
| environmentMap         | A cube map environment texture.                                                                                                                                            | null          |
| hashCode               | Hash code to validate streaming model                                                                                                                                      | ''            |


# VIVERSE SDK License Agreement

***

Last updated: July 1, 2024

PLEASE READ THIS AGREEMENT CAREFULLY TO UNDERSTAND YOUR RIGHTS AND OBLIGATIONS. BY DOWNLOADING, INSTALLING, OR USING THE SDK, YOU AGREE TO BE BOUND BY THIS AGREEMENT. IF YOU DO NOT AGREE (A) DO NOT INSTALL OR USE THE SDK; AND (B) DELETE OR DISABLE ANY DOWNLOADED COPY OF THE  SDK IN YOUR POSSESSION OR CONTROL.

This SDK License Agreement (“**Agreement**”) is a legal agreement between VIVERSE LIMITED and/or one or more of its affiliates (collectively, “**VIVERSE**”) and You (an individual or entity) that governs Your access and use of the SDK. This Agreement is effective on the date You click to accept or when You begin use of the SDK, whichever event occurs first. You represent that You are lawfully able and have full legal authority to enter into this Agreement (e.g. are competent and of legal age and have full legal authority to bind the entity or company You represent). Your continued access to or use of the SDK following the posting of revised terms means that You accept and agree to any revised terms.

1. **BACKGROUND AND PURPOSE.** This Agreement governs Your access to and use of the SDK(s) and all associated application programming interfaces, media, printed or electronic documentation, updates and related support, including but not limited to, header files, libraries, simulators and software, whether source code or object code (collectively, the “SDK”).
2. **LICENSE GRANTS**
   1. License. During the term of this Agreement and conditioned upon Your full compliance with all of this Agreement’s terms and conditions and all SDK and API requirements, specifications, and documentation provided by VIVERSE to You, VIVERSE grants to You the following personal, non-transferable, non-exclusive, non-sublicenseable, revocable licenses to reproduce, install, and use the SDK solely to develop, test, and promote one or more of Your software programs (each an “Application”) and provide You and/or end users access to VIVERSE services, programs, and/or features through Your Application, in each case, as contemplated by the documentation for the applicable SDK. If the SDK includes any libraries, sample code, or other materials that VIVERSE makes available specifically for incorporation in Your Application (as indicated by the applicable documentation), You may incorporate those materials in Your Application and reproduce and distribute those SDK materials as incorporated in Your Application. You may also modify any such sample source code (if any is provided) to the extent necessary to incorporate it into Your Application. You may use the SDK only in connection with the VIVERSE services, programs, and/or features for which they are made available, unless the documentation for the applicable SDK authorizes broader use.
   2. No Obligatio&#x6E;*.* You agree that VIVERSE may restrict and nothing in this Agreement requires VIVERSE to enable You or any of Your Applications to access, interact with, or retrieve or publish content to any VIVERSE servers or devices.  VIVERSE may, as a condition of providing or enabling access to, interaction with, and/or retrieval or publication of content to VIVERSE servers and devices, require You to execute other agreements with additional commercial terms.
3. **LICENSE LIMITATIONS**
   1. General Restrictions. The licenses granted in Section 2 are conditioned upon Your compliance with the following limitations. You are not permitted to:
      1. modify any component of the SDK (other than sample source code described in Section 2);
      2. work around any technical limitations in the SDK or to use the SDK in an attempt to, or in conjunction with any device, program or service designed to, circumvent technical measures employed to control access to, or the rights in, a content, file, or other work;
      3. reverse engineer, decompile, decipher, translate, disassemble or otherwise attempt to access source code of the SDK (other than sample source code described in Section 2), except and only to the extent that applicable law expressly permits, despite this limitation;
      4. publish, rent, lease, lend, sell, sublicense, distribute, transfer, disclose, or otherwise provide the SDK or any application that uses the SDK to any third party, except as expressly permitted herein;
      5. remove or alter any proprietary notices or labels on or in the SDK, including any copyright, trademark or patent notice in the sample code or redistributable code;
      6. use the SDK in connection with the development or transmission of any virus or malicious code;
      7. use the SDK to infringe the rights of VIVERSE, its affiliates, or any third party, or in any way that does not comply with all applicable laws;
      8. use the SDK (including the creation of any of Your Applications) in any way that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of VIVERSE or any third party;
      9. create or enable others to create any application that could disable, hack or otherwise interfere with any authentication, content protection, digital signing, digital rights management, security or verification mechanisms or disable, override, or otherwise interfere with any device, system or software;
      10. authorize use of Your Applications if such Applications use location-based services for real time route guidance for emergency or life-saving purposes; or
      11. make any use of the SDK in any manner not permitted by this Agreement.
   2. Distribution Requirement&#x73;*.* The licenses to redistribute the SDK as set forth in Section 2 are granted upon Your compliance with the following limitations and requirements:
      1. You will not use the SDK with any software or other materials that are subject to licenses or restrictions (e.g. open source licenses) that, when combined with the SDK, would require You or VIVERSE to disclose, license, distribute, or otherwise make all or any part of the SDK to anyone else;
      2. You will distribute the sample source code only in object code form as part of Your Applications;
      3. You will not permit any third party to distribute the sample source code, except that You may permit further redistribution of the sample source code solely in conjunction with, and as part of, Your Applications.
4. **SECURITY**. You are solely responsible for Your Applications and End User Data You may collect. You will maintain a high level of security and integrity for Your Applications in order to prevent unauthorized use of any End User Data and any of the features and functionality of VIVERSE products and services, including access by viruses, worms, or any device, feature, code, or instruction that damages, deletes, disables, impairs, prevents or interrupts the use of any VIVERSE device or service. You will immediately notify VIVERSE if You know of or suspect any breach of security or potential vulnerability of Your Application or any End User Data that could lead to a breach of this Section and will remedy the breach or potential vulnerability within 24 hours.
5. **RESERVATION OF RIGHTS AND OWNERSHIP.** VIVERSE, its affiliates and its suppliers own all title, copyright, and other intellectual property rights in the SDK (and any derivative works and enhancements thereof developed by or on behalf of VIVERSE) and reserve all rights not expressly granted to You in this Agreement.  You will not challenge VIVERSE’s use, ownership of, or rights to the SDK or any other intellectual property of VIVERSE.  The SDK (and any derivative works and enhancements thereof developed by or on behalf of VIVERSE) are protected by copyright and other intellectual property laws and treaties. As between VIVERSE and You and except for the SDK or other intellectual property of VIVERSE or its Affiliates contained in any of Your Applications, You own all title, copyright and other intellectual property rights in Your For the avoidance of doubt, VIVERSE is not the developer of Your Applications.
6. **FEEDBACK.** You may (but are not obligated to) provide VIVERSE with comments or other feedback, suggestions, or recommended improvements regarding the SDK or VIVERSE products or services (collectively, “Feedback”). You agree that even if Feedback is designated as confidential, VIVERSE and its designees are free to use, disclose, reproduce, license and otherwise distribute and exploit Feedback as VIVERSE sees fit and without obligation or restriction of any kind on account of intellectual property rights or otherwise.
7. **SDK UPDATES.** You acknowledge that VIVERSE may update or modify the SDK (including APIs and other components of the SDK) and VIVERSE products and services at any time in its sole discretion, and that these updates or modifications may impact the ability of Your Applications to operate and to communicate with VIVERSE products or services.  If VIVERSE provides You with an updated version of the SDK, within sixty (60) days of notice  of such update, (or such other period designated by VIVERSE), You must cease use of the previous version of the SDK and update any  of Your Applications that were built with or utilize the prior SDK or any components of the prior   If you do not update Your Application within the aforementioned time period, VIVERSE may suspend or terminate your use of the SDK, and, if applicable, remove your non-updated Applications from any VIVERSE services or products.  If VIVERSE updates or modifies the SDK, You may need to change or update Your Application in order for Your Application to continue to operate.
8. **SUPPORT.** VIVERSE has no obligation to provide or to continue to provide any support for the SDK, Your Applications, or any VIVERSE product or service.  You are responsible for providing all support and technical assistance to Your customers and end users regarding use of Your Applications (including any customer or end user issues related to integration of the SDK). VIVERSE is not obligated to provide support or technical assistance to You or any of Your licensees, customers, or end users and You will not represent otherwise.  However, if VIVERSE chooses to provide any support services to You, Your use of such services will be governed by then-current VIVERSE policies.  With respect to any technical or other information You provide to VIVERSE in connection with the support services, You agree that VIVERSE has an unrestricted right to use such information for its business purposes, including for product support and development.
9. **CONFIDENTIALITY, END USER DATA, AND FACIAL TRACKING DATA**
   1. Confidential Information. “Confidential Information” means all non-public information, technology, materials and know-how that VIVERSE designates either as proprietary or confidential or that, by the nature of the information or the circumstances surrounding its disclosure, ought in good faith to be treated as proprietary or confidential, including the terms and conditions of this Agreement. VIVERSE’s Confidential Information includes this Agreement and the SDK and all software specifications, technology, programming, materials, guidelines and documentation relating to the SDK. You will use VIVERSE’s Confidential Information only for the purposes set forth in this Agreement and will only disclose VIVERSE’s Confidential Information to those employees who have a need to know in order to accomplish those purposes. You will not disclose any of VIVERSE’s Confidential Information to any third party without VIVERSE’s prior, written consent. You will protect VIVERSE’s Confidential Information from unauthorized use, access or disclosure in the same manner that You would use to protect Your own confidential and proprietary information of a similar nature and in any event with no less than a reasonable degree of care. You will not use VIVERSE’s name or trademarks in any manner without VIVERSE’s prior, written consent. Without limiting the foregoing, You will not describe Yourself or Your Application as being in any way endorsed or approved by VIVERSE.
   2. End User Data. “End User Data” means information and data generated in connection with an end user’s use of Your Application or otherwise exchanged between an end user and You. You will be solely responsible for adequately protecting and securing all End User Data You You will protect the privacy and legal rights of end users. You agree to only collect and use End User Data in accordance with applicable laws, Your terms of use and/or end user license agreement and privacy policy. You shall ensure that each End User receives sufficient notification prior to any collection of End User Data. You must also ensure that each user provides affirmative consent prior to such collection and any use of the data by You or any third party on Your behalf if such consent is required in accordance with the applicable law. You agree to take all steps required and reasonably necessary (including appropriate physical, technical, and organizational measures) to protect such End User Data in Your possession or control against loss and unauthorized or unlawful access, use, modification, disclosure, processing or other misuse. You agree to implement and comply with industry-standard measures with respect to the technical and physical security of such End User Data. You agree to ensure that its collection, use, storage, processing, distribution, or transfer (including cross-border transfers), and Your information technology platforms and systems on which such End User Data is collected, used, stored, processed, distributed, or transferred are at all times compliant with applicable law (including all privacy laws and laws governing data security) and the terms and conditions of Your published use and privacy policies. Your standard of care to comply with Your obligations under the foregoing sentences shall conform to the requirements of applicable laws and industry standard security measures with respect to the sensitivity of the End User Data in Your possession or control.
   3. Facial Feature Data. If You collect or use End User Data derived from eye tracking, lip tracking, or other facial tracking technology provided by VIVERSE hereunder (“Facial Feature Data”), You represent and warrant that You will comply with the following limitations and restrictions in addition to those above:
      1. You must conspicuously disclose via a privacy notice to end users describing Your collection and use of Facial Feature Data, the capabilities and limitations of the technology in terms that end users can understand, and obtain the end user’s affirmative consent prior to use of such End User Data.
      2. You will not access, collect, or derive, or attempt to access, collect, or derive actual facial image data using the facial tracking technology provided by VIVERSE;
      3. You will not collect or use Facial Feature Data as an identifier for an end user or for identification or authentication purposes;
      4. Your collection and use of Facial Feature Data will at all times be in compliance with VIVERSE’s published guidelines and all applicable laws;
      5. You may not use any facial tracking technology provided by VIVERSE or Facial Feature Data for profiling or behavioral analysis unless: (a) You are at all times in compliance with the terms of this Section 9, and (b) You provide a mechanism for end users to reject behavioral tracking.
      6. You must not use facial tracking technology provided by VIVERSE or Facial Feature Data for profiling or to unlawfully discriminate under any applicable law against individual consumers or groups of consumers.
      7. You will ensure that any third party who has access to Facial Feature Data directly or indirectly through You has authority to do so and will comply with each of the foregoing.
10. **TERM AND TERMINATION.** The term of this Agreement starts on the date You click to accept it or by downloading, using or copying any part of the SDK or using any related VIVERSE software or services (whichever event occurs first) and shall continue until terminated by either You or VIVERSE as set out below.  Either You or VIVERSE may terminate this Agreement with or without cause by giving thirty (30) days prior written notice to the other party. If terminated, (i) all rights and licenses granted hereunder will immediately expire; (ii) You will stop using the SDK and will stop using and distributing Your Applications built with or containing any component of the SDK; (iii) You must immediately destroy all copies of the SDK and VIVERSE Confidential Information in Your possession or control; and (iv) the following Sections of this Agreement will survive: 2(b),  3-6, and 8-17. In addition, VIVERSE may suspend Your license to access and use the SDK or to distribute Your Applications at any time without terminating this Agreement by giving You written notice. Neither party will be liable for any costs, expenses or damages resulting from its suspension or termination of this Agreement as permitted by this Agreement.
11. **REPRESENTATIONS AND WARRANTIES; DISCLAIMER.** You represent and warrant to VIVERSE that: (i) Your Applications will not infringe any intellectual property or other proprietary right of VIVERSE or any third party or violate any right of or duty owed to any third party (including privacy rights and publicity rights); (ii) Your Applications will not cause any damage to any property, injury, or death; (iii) Your Applications and Your performance under this Agreement (including use of the SDK) do not and will not violate any applicable law, rule, or regulation; and (iv) You will, at all times, fully comply with all the terms, conditions, obligations, and restrictions set forth in this Agreement.\
    \
    TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE SDK AND ANY SUPPORT SERVICES ARE PROVIDED AS IS, WITH ALL FAULTS, AND AS AVAILABLE. THE ENTIRE RISK IN CONNECTION WITH THE SDK AND ANY SUPPORT SERVICES REMAINS WITH YOU. VIVERSE AND ITS AFFILIATES AND SUPPLIERS HEREBY DISCLAIM ALL OTHER WARRANTIES AND CONDITIONS, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING ANY IMPLIED WARRANTIES, DUTIES OR CONDITIONS OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. ALSO THERE IS NO WARRANTY OF TITLE; QUIET ENJOYMENT; NON-INFRINGEMENT; CORRESPONDENCE TO DESCRIPTION; LACK OF VIRUSES OR BUGS; ACCURACY OR COMPLETENESS OF RESPONSES OR RESULTS; SUPPORT; THAT THE SDK WILL CONTINUE TO BE PROVIDED, WILL OPERATE, OPERATION WILL BE UNINTERRUPTED OR ERROR-FREE; MEET ANY OF YOUR REQUIREMENTS; OR THAT ANY DEFECTS WILL BE CORRECTED. VIVERSE MAY DISCONTINUE PROVIDING THE SDK AND SUPPORT SERVICES AND MAY CHANGE THEIR NATURE, FEATURES AND FUNCTIONALITY AT ANY TIME. YOUR ACCESS TO AND USE OF THE SDK AND SUPPORT SERVICES ARE AT YOUR OWN RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO COMPUTERS, NETWORKS, AND SYSTEMS AND LOSS OF DATA IN CONNECTION WITH THE SDK OR SUPPORT SERVICES.
12. **LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES.**\
    IN NO EVENT WILL VIVERSE OR ITS AFFILIATES OR SUPPLIERS BE LIABLE FOR, AND YOU CANNOT RECOVER, ANY SPECIAL, INCIDENTAL, PUNITIVE, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING DAMAGES FOR LOST PROFITS, LOST DATA, LOSS OF USE, DAMAGES TO YOUR COMPUTER OR OTHER SYSTEMS, OR LOSS OF GOODWILL, BUSINESS, ANTICIPATED SAVINGS, OR USE, CONFIDENTIAL INFORMATION, OR OTHER INFORMATION, BUSINESS INTERRUPTION, PERSONAL INJURY, PROPERTY DAMAGE, LOSS OF PRIVACY, FAILURE TO MEET ANY DUTY OF GOOD FAITH OR REASONABLE CARE, NEGLIGENCE, AND ANY OTHER PECUNIARY OR OTHER LOSS) ARISING OUT OF OR IN ANY WAY RELATED TO THE SDK OR SUPPORT SERVICES (IF ANY), OR THIS AGREEMENT, REGARDLESS OF THE FORM OF THE CAUSE OF ACTION OR THE ALLEGED BASIS OF THE CLAIM AND EVEN IF THE PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF THE DAMAGES, OR REMEDIES FAIL OF THEIR ESSENTIAL PURPOSE.\
    \
    VIVERSE’S AND ITS AFFILIATES’ AND SUPPLIERS’ ENTIRE LIABILITY UNDER THIS AGREEMENT AND YOUR EXCLUSIVE REMEDY WILL BE LIMITED TO THE ACTUAL DIRECT DAMAGES YOU INCUR IN REASONABLE RELIANCE ON THE SDK OR THIS AGREEMENT UP TO $50.00 U.S. DOLLARS.\
    \
    NO ACTION, REGARDLESS OF FORM, ARISING OUT OF OR RELATING TO THIS AGREEMENT MAY BE BROUGHT MORE THAN ONE YEAR AFTER YOU HAVE KNOWLEDGE OF THE OCCURRENCE WHICH GIVES RISE TO THE CAUSE OF ACTION. THE EXISTENCE OF MULTIPLE CLAIMS OR SUITS UNDER OR RELATED TO THIS AGREEMENT, THE SDK OR ANY OF YOUR APPLICATIONS WILL NOT ENLARGE OR EXTEND THE LIMITATION OF MONEY DAMAGES.
13. **INDEMNIFICATION**
    1. You will indemnify, defend and hold harmless VIVERSE and its affiliates, officers, directors, employees, agents, licensors, suppliers, and contractors from and against any and all damages, liabilities, penalties, interest, fines, losses, costs, and expenses, including reasonable attorneys’ fees and costs arising from or relating to: (i) Your Applications or Your use of the SDK, including any claims of infringement of any third party’s intellectual property or other proprietary rights or damage to property, bodily injury, or death; (ii) Your negligence or willful misconduct or that of any of Your agents, vendors or contractors; or (iii) Your breach of any term or condition of this Agreement, including but not limited to Your breach of Your representations and warranties under this Agreement.
    2. VIVERSE will give You prompt written notice of any claim subject to indemnification; provided, that VIVERSE’s failure to promptly notify You will not affect Your indemnification obligations except to the extent that VIVERSE’s delay prejudices Your ability to defend the claim. You may defend any claim with counsel acceptable to VIVERSE and settle it as You deem appropriate; provided that You will not enter into any settlement that adversely affects VIVERSE’s rights without VIVERSE’s prior written consent. At Your expense, VIVERSE will reasonably cooperate with You in the defense and settlement of any claim subject to indemnification by You. VIVERSE may participate in the defense, any appeals, and settlement with counsel of its own choosing and expense.
14. **GOVERNING LAW AND VENUE.** THIS AGREEMENT WILL BE GOVERNED BY THE LAWS OF IRELAND, WITHOUT REFERENCE TO RULES GOVERNING CHOICE OF LAWS OR THE U.N. CONVENTION ON CONTRACTS FOR THE INTERNATIONAL SALE OF GOODS. YOU HEREBY IRREVOCABLY CONSENT TO AND WAIVE ANY OBJECTION TO THE EXCLUSIVE JURISDICTION AND VENUE OF THE FEDERAL AND STATE COURTS LOCATED AT KING COUNTY, WASHINGTON WITH RESPECT TO ANY CLAIMS, SUITS OR PROCEEDINGS ARISING OUT OF OR IN CONNECTION WITH THIS AGREEMENT OR THE TRANSACTIONS CONTEMPLATED HEREBY.
15. **MODIFICATIONS TO THE AGREEMENT.** VIVERSE may make modifications to this Agreement at any time by posting a notification on the VIVERSE web site. You should regularly check for notice of any modifications. VIVERSE may, but need not, also notify You of such modifications via email. Modifications will become effective, and will be deemed accepted by You on the date specified in the notice, which will be no sooner than thirty (30) days after the modifications are posted (except modifications required by law which will be effective immediately). If You do not agree with the modifications to the Agreement, You must immediately terminate this Agreement and Your use of the SDK, which will be Your sole and exclusive remedy. You agree that Your continued use of the SDK constitutes Your agreement to the modified terms of this Agreement.
16. **CONTACT INFORMATION.** Please direct all notices and correspondence to:\
    VIVERSE LIMITED\
    Attn: Legal Department\
    10 Earlsfort Terrace, Dublin 2,\
    Dublin, D02 T380,\
    Ireland\
    \
    with a copy to:\
    Viveport Digital Corporation\
    Attn: Legal Department\
    No. 88, Section 3, Zhongxing Road\
    Xindian Dist., New Taipei City 231\
    Taiwan
17. **General**
    1. You will bear all of Your own costs and expenses in connection with the use of the SDK and Your performance under this Agreement.
    2. VIVERSE may use sub-contractors or consultants in connection with the performance of its rights and obligations hereunder, which may include allowing such sub-contractors or consultants to have access or use of Your data, and materials provided hereunder provided that such sub-contractors or consultants shall be subject to no less restrictive obligations as those set forth herein.
    3. You are an independent contractor and not an employee, agent, partner, joint venturer, representative, broker or principal of VIVERSE for any purpose.
    4. VIVERSE’s failure to act with respect to a breach by You does not waive its rights to act with respect to subsequent or similar breaches. No waiver of any provision of this Agreement will be effective unless it is in a signed writing, and no waiver will constitute a waiver of any other provision(s) or of the same provision on another occasion.
    5. If a court of competent jurisdiction holds any term, covenant or restriction of this Agreement to be illegal, invalid or unenforceable, the remaining terms, covenants and restrictions will remain in full force and effect and will in no way be affected, impaired or invalidated.
    6. You acknowledge and agree that each member of the group of companies of which VIVERSE is the parent shall be third party beneficiaries to this Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of this Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to this Agreement.
    7. You must comply with all national and international laws, rules and regulations that apply to Your use of the SDK, including the U.S. Export Administration Regulations, as well as end-user, end-use, and destination restrictions issued by U.S. or other governments.
    8. You may not assign any of Your rights or obligations under this Agreement, whether by operation of law or otherwise, without VIVERSE’s prior, written consent, except that You may assign all of Your rights and obligations under this Agreement to any corporation or other entity without consent in connection with a merger or the sale of all or substantially all of Your assets, provided You give VIVERSE written notice of any such assignment no later than ten (10) business days before such assignment. Subject to the foregoing limitation, this Agreement will be binding upon, inure to the benefit of and be enforceable by the Parties and their respective successors and assigns.
    9. This Agreement constitutes the entire agreement between You and VIVERSE with respect to Your use of the SDK and completely replaces any prior agreements between You and VIVERSE in relation to the SDK.


# Introduction to Developer Tools

Introducing the SDKs and services that are available to developers making 3D projects on VIVERSE

***

> ***NOTE:** VIVERSE SDKs cannot be used with projects published via the* [*PlayCanvas Create SDK extension*](https://docs.viverse.com/playcanvas-sdk/playcanvas-extension-setup)*, which do not have App IDs.*

## How Developers Use Our Tools

Our goal is to make it simple to publish rich experiences to VIVERSE. These tools help our creator community build more advanced 3D experiences, including multiplayer games, and utilize the account and avatar system to give users a more seamless experience when hopping from world to world in VIVERSE.

While we do not require developers use these tools — and while we also make it possible for developers to include their own servers/databases/external APIs in VIVERSE projects — we highly recommend that all creators familiarize themselves with these offerings and consider integrating the VIVERSE account & avatar system into their projects. It makes the experience better for our users and many of these services are available for free!

## Overview of Our Tools

<table><thead><tr><th width="158.1304931640625">Name</th><th width="332.62384033203125">Description</th><th width="171.2432861328125">Cost to Developer</th></tr></thead><tbody><tr><td><a href="/pages/ll6eQM9DqjGA2KjEj40s">Login &#x26; Authentication</a> [Beta]</td><td>Get a user's account information when they join your experience on VIVERSE. This will allow you to access their display name, avatar information, and account information, making it easier for end-users to travel between VIVERSE experiences while staying connected to their identity and friends.</td><td>Free!</td></tr><tr><td><a href="/pages/Sbksy3BRH6tLzngz6Ia1">Avatar SDK</a> [Beta]</td><td>Download and use a user's avatar file in your VIVERSE experience. Digital identity is an important consideration in 3D and including end-users' avatars makes them feel more at home in your VIVERSE World.</td><td>Free!</td></tr><tr><td><a href="/pages/i7IneT5Y8vdV9FZb07ve">Leaderboard SDK</a> [Beta]</td><td>Access and save information about players interacting with your world. Keep track of high scores to boost engagement with your player base.</td><td>Free!</td></tr><tr><td><a href="/pages/TFbUC1Qdy1qIMF2JcWJT">Matchmaking &#x26; Networking</a> [Beta]</td><td>Save and network game-state between clients in your VIVERSE world. Use this SDK to build richer multiplayer experiences.</td><td>Free!</td></tr></tbody></table>

## SDK Versioning

Latest: [v1.3.3](https://www.viverse.com/static-assets/viverse-sdk/1.3.3/index.umd.cjs) (2025-11-20)

[Change log](/developer-tools/changelog)

## Provisioning Your Own Game Servers & Services

While the above services are available for free to our developers to use, we frequently allow developers to include their own, externally hosted services in their VIVERSE creations. If you have an API endpoint, database, or hosted gameserver that you would like to access in VIVERSE, please email <michael_morran@htc.com> and <james_kane@htc.com> OR join our [Discord Server](https://discord.gg/viversecreators) and message us with more information about the nature of your service and the URL you would like whitelisted!


# Login & Authentication SDK

Learn how to check for and login to VIVERSE services to access user information including their preferred avatars

***

This guide is designed to help creators integrate VIVERSE SDKs when uploading WebGL content from engines like Unity, three.js or Wonderland Engine to VIVERSE Studio.

> BEFORE GETTING STARTED:
>
> 1. An App ID needs to be created, either through the CLI or the VIVERSE Studio workflow. [See our docs](https://docs.viverse.com/publishing-with-your-viverse-account#select-create-new-world) for this information.
> 2. The VIVERSE SDK is hosted at this URL and must be integrated into your JavaScript/WebGL project and target engine. Either target a specific version like [`https://www.viverse.com/static-assets/viverse-sdk/1.3.3/index.umd.cjs`](https://www.viverse.com/static-assets/viverse-sdk/1.3.3/index.umd.cjs) or always target the latest version at [`https://www.viverse.com/static-assets/viverse-sdk/index.umd.cjs`](https://www.viverse.com/static-assets/viverse-sdk/index.umd.cjs)

## Authentication & Authorization

User login is required to check for user information like name, profile picture URL, and .vrm avatar URL.

#### Step 1: **Initialize the Client**

Before any authentication, initialize the SDK client in your application:

```
// Initialize a new client
globalThis.viverseClient = new globalThis.viverse.client({
    clientId: '{yourAppID}',
    domain: 'account.htcvive.com', // HTC Account domain
    cookieDomain: '{yourCookieDomain}', // Optional
});
```

#### Step 2: Check for Existing Authentication

Once the client is initialized, await its `checkAuth()` function to check for valid user credentials:

<pre><code><strong>const result = await globalThis.viverseClient.checkAuth();
</strong></code></pre>

If the user is logged in, you'll get their authentication information in an object structured like so:

```
{
    access_token: string; // The access token to be used in API requests
    account_id: string; // The unique user account ID
    expires_in: number; // Remaining token lifetime in seconds
    state: string; // Optional custom state value from the original login
}
```

If the user is not logged in, the result will come back `undefined`.

#### Step 3: **Trigger Login via VIVERSE Worlds**

If login is required for your experience, an automated login and single sign-on (SSO) workflow is available. To request it, this method can be called, which will forward the user through this login flow within the iframe:

```
globalThis.viverseClient.loginWithWorlds()
```

**Step 4: Handle Post-Login State on Page Load**

You can wrap your logic in an arrow function callback on the window's `load` event to handle the automatic login flow.

```
// this callback will run when the iframe is refreshed
window.addEventListener('load', async () => {
    // reinitialize
    globalThis.viverseClient = new globalThis.viverse.client({
        clientId: '{yourAppID}',
        domain: 'account.htcvive.com', // HTC Account domain
        cookieDomain: '{yourCookieDomain}', // Optional
    });
    // check login status again
    const result = await globalThis.viverseClient.checkAuth();
    
    if (result === undefined) {
        // This will cause a refresh
        globalThis.viverseClient.loginWithWorlds();
    }
    else {
        // `result` contains credentials to make authorized requests 
    }
});
```

If `result` has valid authorization credentials, you can then utilize features like the [Avatar SDK](/developer-tools/avatar-sdk), Leaderboard SDK and Matchmaking SDK.

## API Reference

### `new viverse.client(options)`

Initializes a new VIVERSE client instance.

| Parameter        | Type     | Description                                                      | Required |
| ---------------- | -------- | ---------------------------------------------------------------- | -------- |
| `options`        | `object` | An object containing configuration for the client.               | Yes      |
| ↳ `clientId`     | `string` | Your App ID obtained from VIVERSE Studio.                        | Yes      |
| ↳ `domain`       | `string` | The authentication domain. This should be `account.htcvive.com`. | Yes      |
| ↳ `cookieDomain` | `string` | The domain on which the cookie will be set for authentication.   | No       |

### `checkAuth()`

Checks if the user is currently authenticated. Returns a `Promise` that resolves with the authentication object if successful, or `undefined` if not.

**Returns:** `Promise<object | undefined>`

The resolved object will have the following properties:

| Property       | Type     | Description                                          |
| -------------- | -------- | ---------------------------------------------------- |
| `access_token` | `string` | The access token to be used in API requests.         |
| `account_id`   | `string` | The unique user account ID.                          |
| `expires_in`   | `number` | Remaining token lifetime in seconds.                 |
| `state`        | `string` | Optional custom state value from the original login. |

### `loginWithWorlds(options)`

Redirects the user to the VIVERSE Worlds login page to authenticate. This is used for Single Sign-On (SSO). After login, the page will be refreshed.

| Parameter | Type     | Description                                                                                                                         | Required |
| --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `options` | `object` | An object containing configuration for the client.                                                                                  | No       |
| ↳ `state` | `string` | A customize value. If this parameter is set in the request, then it is returned to the application as part of the redirection\_url. | No       |


# PlayCanvas Login & Auth minimal example

Learn how to add VIVERSE login and authentication features into your standalone PlayCanvas game

### Pre-requisite: Create a World and App ID in VIVERSE Studio

All SDK usage requires an App ID tied to a specific VIVERSE World, which can be created via [VIVERSE Studio](https://studio.viverse.com/upload). This process is described in detail in [our documentation on VIVERSE Studio](/publishing-with-your-viverse-account) — but simply create a new app and copy its App ID to get started.

> ***NOTE:** VIVERSE SDKs cannot be used with projects published via the PlayCanvas Create SDK extension, which do not have App IDs.*

<div><figure><img src="/files/FVbxDNI5j7V83hHUqpHv" alt=""><figcaption></figcaption></figure> <figure><img src="/files/kQubXd6Phb2asQJ8Qrj3" alt=""><figcaption></figcaption></figure></div>

### Step 1: Create a new PlayCanvas project and add the VIVERSE SDK as an external script

In a new PlayCanvas project, go to the project Settings, and in the EXTERNAL SCRIPTS menu, add one URL entry, and point it to [`https://www.viverse.com/static-assets/viverse-sdk/index.umd.cjs`](https://www.viverse.com/static-assets/viverse-sdk/index.umd.cjs) as in this screenshot. This will ensure the VIVERSE SDK is loaded first and that your PlayCanvas logic has full access to its global methods.

<figure><img src="/files/WwGGuTBtVkHi66aCB27h" alt="" width="153"><figcaption></figcaption></figure>

### Step 2: Initialize the SDK in a new .mjs script

Add a new script called `viverse-manager.mjs` in the project to handle all VIVERSE SDK services and authentication. This process is described generically in our documentation, [**Login & Authentication for the SDK**](/developer-tools/login-and-authentication-for-the-sdk), but here is how it would apply to a modular PlayCanvas script.

```javascript
import { Script, Entity } from "playcanvas";

export class ViverseManager extends Script {
  static scriptName = "viverseManager";
  
  initialize() {
    this.appId = "gqvuyd5duu"  // get this from VIVERSE Studio
    this.profile = null  // we'll use this soon
    this.accessToken = null  // this, too
    
    this.loadViverse()
  }

  async loadViverse() {
    // Create a new viverseClient instance on globalThis
    globalThis.viverseClient = new globalThis.viverse.client({
      clientId: this.appId,  // 
      domain: 'account.htcvive.com',  // HTC Account domain
    })
  }
}
```

### Step 3: Check whether users are logged in

Once the `viverseClient` is instantiated, use its `checkAuth()` function to determine whether the user is logged in. It will return `undefined` if they are **not** logged-in, or credentials if they are.

```javascript
async loadViverse() {
  // Create a new viverseClient instance on globalThis
  globalThis.viverseClient = new globalThis.viverse.client({
    clientId: this.appId,
    domain: 'account.htcvive.com',  // HTC Account domain
  })
  
  // check login status again
  const result = await globalThis.viverseClient.checkAuth()
  
  if (result === undefined) {  // Not logged in
    // We'll come back and add a login function
  } else {
    // Since the user is logged in, get the token to start making requests;
    this.accessToken = await globalThis.viverseClient.getToken()

    if (this.accessToken == undefined) {
      console.warn("Sanity-check - should have an accessToken at this point.")
    } else {
      // initialize avatar client instance
      globalThis.avatarClient = new globalThis.viverse.avatar({
          baseURL: 'https://sdk-api.viverse.com/',  // VIVERSE API domain
          token: this.accessToken,  // required to query user-specific data
      });

      this.profile = await globalThis.avatarClient.getProfile();
      console.log(this.profile.name)
    }
  }
}
```

### Step 3: Add attribute reference and UI elements

Now that we're checking auth status, let's add some Element UI components to our demo scene to display logged-in users' names, and a button to prompt the login process if they are logged out.

Add two entity attributes to the `viverse-manager.mjs` script to reference a "Login" button and a "Logged In As" status label text. Let's also set up a callback on init to call the VIVERSE SDK's `loginWithWorlds()` function on button click, as well.

```javascript
import { Script, Entity } from "playcanvas";

export class ViverseManager extends Script {
  static scriptName = "viverseManager";

  /**
   * @attribute
   * @type {Entity}
   */
  loggedInAsLabelEntity

  /**
   * @attribute
   * @type {Entity}
   */
  loginButton
  
  initialize() {
    this.appId = "gqvuyd5duu"
    this.profile = null
    this.accessToken = null

    this.loginButton.button.on("click", () => { 
      globalThis.viverseClient.loginWithWorlds()  // This will cause a refresh and run the SSO login loop.
    })

    // Call the VIVERSE SDK init logic defined below in this component
    this.loadViverse()
  }
```

Once the script is saved and parsed, add these UI elements in the scene in the PlayCanvas editor and set both attribute references, pointing to the "Logged In As" label's text element and the "Login" button entity.

<figure><img src="/files/5pJBIMp7XvaaMAUX9zVt" alt=""><figcaption></figcaption></figure>

### Step 5: Hook SDK code into UI

With attribute references set, we can now act on these buttons and text elements. We've already set up the button's click callback to run the SSO login redirect if necessary. Let's also change the status message to encourage the user to login in that case.

But if they user is *already* logged-in (whether from previous VIVERSE experiences, or because they just completed the SSO redirect loop), let's disable the login button, and change its text.

Then, finally, instantiate an `avatarClient` and await the response from its `getProfile()` function to get the user's profile info, including display name and active avatar information.

```javascript
async loadViverse() {
    // Create a new viverseClient instance on globalThis
    globalThis.viverseClient = new globalThis.viverse.client({
      clientId: this.appId,  // 
      domain: 'account.htcvive.com',  // HTC Account domain
    })
    
    // check login status again
    const result = await globalThis.viverseClient.checkAuth()
    
    if (result === undefined) {  // Not logged in
      this.loggedInAsLabelEntity.element.text = "Not logged in! Please log in."
    } else {
      // `result` contains credentials, we're logged in
      
      // Disable the login button and change its child text label
      this.loginButton.button.active = false;
      this.loginButton.children[1].element.text = "Logged In"

      // Since the user is logged in, get the token to start making requests
      this.accessToken = await globalThis.viverseClient.getToken()

      if (this.accessToken == undefined) {
        console.warn("Sanity-check - should have an accessToken at this point.")
      } else {
        // initialize avatar client instance
        globalThis.avatarClient = new globalThis.viverse.avatar({
            baseURL: 'https://sdk-api.viverse.com/',  // VIVERSE API domain
            token: this.accessToken,  // required to query user-specific data
        })

        this.profile = await globalThis.avatarClient.getProfile();
        // Apply name to "Logged In As" label
        this.loggedInAsLabelEntity.element.text = "Logged In As: " + this.profile.name
      }
    }
  }
```

### Step 6: Export and Publish to VIVERSE

That's it! That should fulfil our minimal requirements to log in to VIVERSE using PlayCanvas UI. Now we just need to export and publish.

In PlayCanvas' Publish/Download menus, choose the "DOWNLOAD ZIP," then set your export options and click the first grey "DOWNLOAD," which will prepare you a build, exposing the final orange "DOWNLOAD" button after a few seconds.

<div><figure><img src="/files/63WI75y75vMhHTvU6vRg" alt=""><figcaption></figcaption></figure> <figure><img src="/files/NuHna3TtnnNTmcJjlnD6" alt=""><figcaption></figcaption></figure></div>

Once the build .zip is downloaded, navigate to VIVERSE Studio's Upload section, click "Manage Content" next to the app you created earlier, and use the "Upload Content" section to select the .zip.

From there, you can preview your build, or submit it for content review and approval. This process is explored in great detail in [the VIVERSE Studio section](/publishing-with-your-viverse-account) of our docs if you have further questions.

<figure><img src="/files/O2Y5hKAPxvmSES3tnOdO" alt=""><figcaption></figcaption></figure>

### Final Build

That's it! The build is ready. Here are the relevant links:

Public PlayCanvas project: <https://playcanvas.com/project/1375569/overview/viverse-auth-sdk--playcanvas>

Demo scene live on VIVERSE: <https://worlds.viverse.com/Rkofb3v>

<figure><img src="/files/KSwmEOGXEojMH2eqfMKa" alt=""><figcaption></figcaption></figure>




---

[Next Page](/llms-full.txt/1)

