Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions creator/scene-editor/configure/scene-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ The **Details** tab lets you configure several fields about your scene. These fi

The following fields are available:

* **Name**
* **Description**
* **Thumbnail**

{% hint style="info" %}
**💡 Tip**: If no thumbnail is provided, it uses the automatic capture you see on the scene's card. We recommend uploading a more attractive image
{% endhint %}
* **Categories**
* **Creator name** (optional)
* **Creator contact email** (optional)
* **Creator wallet address** (optional)
- **Name**
- **Description**
- **Thumbnail**

{% hint style="info" %}
**💡 Tip**: If no thumbnail is provided, it uses the automatic capture you see on the scene's card. We recommend uploading a more attractive image
{% endhint %}

- **Categories**
- **Creator name** (optional)
- **Creator contact email** (optional)
- **Creator wallet address** (optional)

The thumbnail should be a .png image of a recommended size of 228x160 pixels. The minimum supported size is 196x143 pixels. The image may be stretched if the width-to-height proportions don’t match 228x160.

Expand All @@ -40,14 +41,12 @@ See [scene metadata](../../sdk7/projects/scene-metadata.md) for more details on
**📔 Note**: The scene's **Age Rating** is not edited on this panel. You can set the `rating` field directly in the `scene.json` file, or, for scenes published to a World, change the **Content Rating** in the Creator Hub's World Settings after publishing. Decentraland is an 18+ platform, so the rating to set is `A` for Adults. See [Age Rating](../../sdk7/projects/scene-metadata.md#age-rating).
{% endhint %}


### Tipping

You can receive tips from players who visit your scene. To enable tipping, got to the **Details** tab on the scene settings and provide an Ethereum address under **Creator wallet address**.

![](../../.gitbook/assets/creator-wallet-address.png)


When a player visits your scene, they will see a piggy bank icon on the top-left of the screen. Clicking on it opens a modal where they can send you a tip. This menu can also be accessed by opening your scene's info on the map.

![](../../.gitbook/assets/TipIcon.png)
Expand All @@ -56,9 +55,12 @@ The tip modal allows the player to select the amount of MANA they want to send.

![](../../.gitbook/assets/tipping-modal.png)


You will receive a notification on the Decentraland notifications tab whenever a player sends you a tip.

{% hint style="info" %}
**💡 Tip**: You can also make tipping more discoverable by adding 3D content that invites players to tip, like a tip jar or a donation sign, and using [`openExplorerUi()`](../../sdk7/interactivity/external-links.md#open-explorer-ui-panels) so that clicking it opens the tipping UI.
{% endhint %}

## Layout

You can edit the size of your scene by clicking the _pencil icon_ and then changing the number of rows and columns.
Expand Down Expand Up @@ -99,9 +101,9 @@ You can disable certain functionalities on your scene if you chose, in case they

![](../../images/editor/scene-restrictions.png)

* **Silence Voice Chat**: Prevent players on your scene from using voice chat.
* **Disable Nearby Voice Chat**: Prevent players on your scene from using the nearby (proximity-based) voice chat.
* **Disable Smart Wearables & Portable Experiences**: Prevent players from using [Smart Wearables](../../sdk7/projects/smart-wearables.md) or [Portable Experiences](../../sdk7/projects/portable-experiences.md).
- **Silence Voice Chat**: Prevent players on your scene from using voice chat.
- **Disable Nearby Voice Chat**: Prevent players on your scene from using the nearby (proximity-based) voice chat.
- **Disable Smart Wearables & Portable Experiences**: Prevent players from using [Smart Wearables](../../sdk7/projects/smart-wearables.md) or [Portable Experiences](../../sdk7/projects/portable-experiences.md).

## Skybox Control

Expand Down
12 changes: 12 additions & 0 deletions creator/sdk7/3d-essentials/camera.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ function main() {
}
```

## Field of View

You can set a custom field of view (FOV) on a virtual camera with the `fov` property. The value is in degrees. If omitted, it defaults to 60.

```ts
VirtualCamera.create(myCustomCamera, {
fov: 90,
})
```

A wider FOV (higher value) shows more of the scene at once and creates a sense of speed, which can be useful for racing games. A narrower FOV (lower value) zooms in and is useful for aiming or cinematic shots.

## Camera Transitions

Whenever the scene switches between virtual cameras, or between the default camera behavior and virtual cameras, players see a transition. The position, rotation and any other parameters of the virtual camera change smoothly over a period time.
Expand Down
71 changes: 70 additions & 1 deletion creator/sdk7/interactivity/external-links.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ The player will spawn in one of the spawn points of the scene in that world, reg

To send a player back to Genesis City from a world, use `changeRealm` setting the `realm` field to _'https://realm-provider-ea.decentraland.org/main'_.


## External links

To add a link to an external website, use the `openExternalUrl()` command.
Expand All @@ -67,6 +66,76 @@ The link is opened in a new tab, keeping the original tab in Decentraland.

If players tick the _trust this domain_ checkbox, they won't be prompted again during their session, as long as the link comes from the same scene and is to the same domain.

## Open Explorer UI panels

Use `openExplorerUi()` to open one of the Explorer's built-in fullscreen panels (the map, settings, backpack, and others) from your scene. The player must trigger the action with an explicit click or button event, the same as `openExternalUrl`.

```ts
import { openExplorerUi } from '~system/RestrictedActions'

// Open the map panel
const result = await openExplorerUi({ ui: 1 })
```

The `ui` field selects which panel to open. Use the `ExplorerUi` values from `~system/RestrictedActions`:

| Value | Name | Panel |
| ----- | ---------------- | ----------- |
| 0 | `EU_SETTINGS` | Settings |
| 1 | `EU_MAP` | Map |
| 2 | `EU_BACKPACK` | Backpack |
| 3 | `EU_CAMERA_REEL` | Camera Reel |
| 4 | `EU_COMMUNITIES` | Communities |
| 5 | `EU_PLACES` | Places |
| 6 | `EU_EVENTS` | Events |

The function returns a promise with an `openResult` field that tells you what happened:

| Result | Meaning |
| ---------------------------- | ------------------------------------------------------------------ |
| `OPENED` | The panel was opened successfully. |
| `WAS_ALREADY_OPEN` | A fullscreen panel was already open. |
| `REJECTED_NOT_CURRENT_SCENE` | The player is no longer in the scene that made the request. |
| `REJECTED_FEATURE_DISABLED` | The requested panel is disabled or unavailable in this client. |
| `REJECTED_NO_USER_GESTURE` | The call did not come from a user gesture (click or button event). |

Here is a full example that opens the map when a player clicks a cube:

```ts
import { openExplorerUi } from '~system/RestrictedActions'

// Create a clickable cube that opens the map
const mapButton = engine.addEntity()
Transform.create(mapButton, { position: Vector3.create(8, 1, 8) })
MeshRenderer.setBox(mapButton)
MeshCollider.setBox(mapButton)

pointerEventsSystem.onPointerDown(
{
entity: mapButton,
opts: { button: InputAction.IA_POINTER, hoverText: 'Open Map' },
},
async () => {
const result = await openExplorerUi({ ui: 1 })
if (result.openResult === 1) {
console.log('Map opened')
}
}
)
```

{% hint style="warning" %}
**Note:** Like other restricted actions, `openExplorerUi` can only be called from an explicit user gesture (a click or button event). Calling it from a timer, collision area, or any other trigger will be rejected.
{% endhint %}

### Practical uses

Opening Explorer panels directly from your scene's content removes friction for players who might not know where to find these menus. Some ideas:

- **Tipping**: If your scene has [tipping enabled](../projects/scene-metadata.md#tipping), place a tip jar or donation sign as clickable 3D content that UI.
- **Performance settings**: If your scene detects that the player is running at a low frame rate, show a UI prompt suggesting they lower their graphics settings, with a button that opens the settings panel. See [performance optimization](../optimizing/performance-optimization.md#help-players-adjust-their-settings).
- **Onboarding**: If your experience relies on wearables or emotes, add a mirror or wardrobe that opens the backpack panel so players can equip them without leaving the flow of your scene.
- **Photo opportunities**: Next to a scenic photo spot, add a sign that opens the camera reel panel so players can review the pictures they've taken.

## Copy to clipboard

Expand Down
12 changes: 8 additions & 4 deletions creator/sdk7/networking/authoritative-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ During local development, storage is written to `node_modules/@dcl/sdk-commands/
Storage is durable persistence for data that must survive server restarts and redeploys. It is not a live datastore. Keep your working game state in memory on the server. That's faster and it's the right pattern for a server. Write to Storage only when you really need to, at meaningful checkpoints.

{% hint style="warning" %}
**⚠️ Warning**: The server runtime allows a maximum of **40 in-flight host calls** at once, shared across *everything* the scene asks the runtime to do: every storage request, `signedFetch`, and other runtime APIs all count against the same limit. Excess calls are **not queued**. They reject immediately with a `too many concurrent host calls` error. The SDK catches the rejection and resolves the `Storage.set` promise to `false` instead of throwing. If your code discards that boolean, the failed save is invisible, and your persisted data ends up stale or lost. Check the result of every write (see the Note above).
**⚠️ Warning**: The server runtime allows a maximum of **40 in-flight host calls** at once, shared across _everything_ the scene asks the runtime to do: every storage request, `signedFetch`, and other runtime APIs all count against the same limit. Excess calls are **not queued**. They reject immediately with a `too many concurrent host calls` error. The SDK catches the rejection and resolves the `Storage.set` promise to `false` instead of throwing. If your code discards that boolean, the failed save is invisible, and your persisted data ends up stale or lost. Check the result of every write (see the Note above).
{% endhint %}

Good moments to persist:
Expand Down Expand Up @@ -917,7 +917,7 @@ Each connected peer can send up to approximately **300 messages per 1,000 ms**.

### External fetch

Concurrent `signedFetch` calls are capped at **32** in-flight. Additional fetches queue until a slot opens. Each fetch attempt has a **15-second** timeout with up to **2** retries.
Concurrent `signedFetch` calls are capped at **32** in-flight. Additional fetches queue until a slot opens. Each fetch attempt has a **15-second** timeout. Fetch responses are capped at **10 MB**. WebSocket connections are limited to **32** concurrent sockets, with a maximum message size of **1 MB** per message.

### In-flight host calls

Expand Down Expand Up @@ -978,9 +978,13 @@ engine.addSystem(() => {

### Wait for the server to start up

The server is only active if there's at least one player present in the scene. If nobody's currently there, the server shuts down after a few minutes.
The server is only active while at least one player is in the scene. After the last player leaves, the server stays up for roughly two minutes, then shuts down. The next visit cold-starts a fresh instance, which takes approximately **15 seconds in production**. Local preview starts the server instantly, so cold-start issues almost always surface only after publishing.

When a first player comes into the scene after a while of inactivity, the server takes a few seconds to start up. Your scene's code should be prepared to have to wait for the server to be online. Initial requests to the server should have catch and retry mechanisms to provide resilience.
Your scene's code should be prepared to wait for the server to come online. Messages sent before the server finishes starting are silently lost. Show a "server waking up" message to the player while waiting, and add retry logic to initial server requests.

{% hint style="info" %}
**Tip:** A reliable way to detect server readiness is a heartbeat: have the server write `Date.now()` to a synced component field every 2 seconds, and have the client track when it last saw the value change. If no change arrives within 6 seconds, treat the server as offline. This is more robust than `isStateSyncronized()`, which only confirms the transport is connected, not that the server is running.
{% endhint %}

## Complete Example

Expand Down
57 changes: 57 additions & 0 deletions creator/sdk7/optimizing/performance-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,60 @@ Keep in mind that the performance you experience in preview may differ from that
It's always a good practice to try deploying your scene first to a [Decentraland World](../publishing/publishing-options.md#decentraland-worlds) to do some more thorough testing.

Always ask players for feedback. Never take for granted that how you experience the scene is the same for everyone else.

## Help players adjust their settings

Even a well optimized scene can run poorly on low-end hardware, or when the player has their graphics settings set too high for their machine. You can help these players by detecting a low frame rate from your scene's code and showing a UI tip that recommends lowering their graphics settings, with a button that opens the Explorer's settings panel directly via [`openExplorerUi()`](../interactivity/external-links.md#open-explorer-ui-panels).

Your scene can estimate the frame rate by counting how many times a system runs over a period of time. In the example below, if the average is under 20 FPS for a 5 second period, the scene displays a UI prompt. Clicking the prompt's button opens the settings panel, this click counts as the user gesture that `openExplorerUi()` requires.

```tsx
import { engine } from '@dcl/sdk/ecs'
import { openExplorerUi } from '~system/RestrictedActions'
import ReactEcs, { Button, Label, ReactEcsRenderer, UiEntity } from '@dcl/sdk/react-ecs'

let showSettingsTip = false

// Estimate the average FPS over 5 second intervals
let elapsed = 0
let frames = 0
engine.addSystem((dt: number) => {
elapsed += dt
frames += 1
if (elapsed >= 5) {
if (frames / elapsed < 20) {
showSettingsTip = true
}
elapsed = 0
frames = 0
}
})

ReactEcsRenderer.setUiRenderer(() => (
<UiEntity
uiTransform={{
display: showSettingsTip ? 'flex' : 'none',
flexDirection: 'column',
positionType: 'absolute',
position: { top: 40, right: 40 },
padding: 10,
}}
uiBackground={{ color: { r: 0, g: 0, b: 0, a: 0.8 } }}
>
<Label value="Running slow? Try lowering your graphics settings." fontSize={16} />
<Button
value="Open Settings"
fontSize={16}
uiTransform={{ height: 36, margin: { top: 8 } }}
onMouseDown={() => {
showSettingsTip = false
openExplorerUi({ ui: 0 }) // 0 = Settings panel
}}
/>
</UiEntity>
))
```

{% hint style="info" %}
**💡 Tip**: Only show this kind of prompt once, and let players dismiss it. A recurring popup on a machine that's already struggling only makes the experience worse.
{% endhint %}
Loading