Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The closed-source Mod remains authoritative for generated client and server API

## Structure

- `guides/` contains scripting concepts and tutorials. Each subdirectory is a sidebar section named after the directory (`guides/Getting started/first-resource.md` is served at `/guides/getting-started/first-resource/`). Sections are ordered by the lowest `sidebar.order` among their pages, so each section owns a range: Getting started 10–19, Core concepts 20–29, Players 30–39, NPCs and creatures 40–49, World 50–59, User interface 60–69, Hosting a server 90–99.
- `guides/` contains scripting concepts and tutorials. Each subdirectory is a sidebar section named after the directory (`guides/Getting started/first-resource.md` is served at `/guides/getting-started/first-resource/`). Sections are ordered by the lowest `sidebar.order` among their pages, so each section owns a range: Getting started 10–19, Core concepts 20–29, Players 30–39, NPCs and creatures 40–49, World 50–59, User interface 60–69, Camera and sound 70–79, Hosting a server 90–99.
- Image directories live beside the Markdown document that references them.
- `docs.config.json` owns the generator pin, branding, links, navigation, and community-content mapping.
- `scripts/sync_contract.mjs` downloads and verifies the public scripting contract.
Expand Down
90 changes: 90 additions & 0 deletions guides/Camera and sound/audio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: Audio
description: Play the game's sounds at a place, on a player or for one player, stop sounds that keep playing, load soundbanks and change the music, from the server or a client.
sidebar:
order: 71
---

Every sound in the game is a Wwise event, named like `cabbage_explosion` or `accio_cast`. The server's `Audio` global plays events on players' clients, and a client's `Audio` plays them for its own player. Event names ignore case, and positions and ranges are world centimetres.

## Playing sounds from the server

| Call | Who hears it |
| --- | --- |
| `Audio.playSoundAt(event, x, y, z, options)` | Players within `range` of the point, from that point |
| `Audio.playSoundOnPlayer(player, event, options)` | Players within `range` of the player, from the player as they move, the player included |
| `Audio.playSoundForPlayer(player, event, options)` | Only that player, at full volume wherever they are |
| `Audio.playSoundForAll(event, options)` | Every connected player, at full volume wherever they are |

`range` is 10000 (100 m) by default, and 0 sends the sound to everyone. Each call returns a sound id for `Audio.stopSound(id)`:

```js
// server/main.js
function thunderAt(x, y, z) {
return Audio.playSoundAt("Amb3D_Storm_ThunderClaps_Play", x, y, z, { range: 20000 });
}
```

The server sends a sound once, to the players in range as it starts. A player who arrives or joins afterwards does not hear it, and a player still on a loading screen misses it.

Sounds ignore [virtual worlds](/guides/core-concepts/virtual-worlds/#what-ignores-worlds): a player within range hears a sound whatever world they are in. To keep a sound to one world, send it to that world's players and [play it on their clients](#playing-sounds-on-the-client).

## Sounds that keep playing

Some events play once and end, and others, such as loops and charging spells, keep playing until something stops them. `Audio.stopEventFor(event)` returns the name of the event that stops one, or an empty string for a sound that ends by itself.

Pass `duration` in seconds to end a sound by itself, or keep its id and call `Audio.stopSound(id)`. Stopping a sound plays its stop event first, looked up from the game's soundbanks, and `stopEvent` names a different one:

```js
// server/main.js
function castEffect(player) {
Audio.playSoundOnPlayer(player, "accio_cast", { duration: 3 });
}
```

On the server, `Audio.stopAllSounds()` stops every sound on every client, the game's own music and ambience included, for when a sound keeps playing and nobody has its id. A client's `Audio.stopAll()` does the same for that player.

## Playing sounds on the client

A client's `Audio.playSoundAt(event, x, y, z, options)` plays a sound only its own player hears, from a point. `Audio.playSound(event, options)` plays one on the player's own character, at full volume. Both take `duration` and `stopEvent`, and return an id for `Audio.stopSound`, or 0 when the game refused the sound. `Audio.stopOnSelf()` stops everything playing on the player's character.

This pair plays a sound for the players in one world only:

```js
// server/main.js
function playInWorld(world, event, x, y, z) {
for (const player of PlayerManager.getAll()) {
if (player.virtualWorld === world) player.emit("sound:play", JSON.stringify({ event, x, y, z }));
}
}
```

```js
// client/main.js
Events.on("sound:play", (sound) => {
Audio.playSoundAt(sound.event, sound.x, sound.y, sound.z);
});
```

## Soundbanks

Wwise keeps events in soundbanks, and the game loads its banks as players move around. When a sound's bank is not loaded, the client loads it and tries the sound again, so you rarely need to load a bank yourself. `Audio.bankFor(event)` names the bank an event needs.

`Audio.loadBank(name)` and `Audio.unloadBank(name)` load and unload a bank, on every client from the server or on one client from a client script. A bank of your own is a `.bnk` file that must be in the game's `Phoenix/Content/WwiseAudio/Windows/` folder on each player's machine.

## Music

`Audio.setMusic(event, priority)` puts a track on the game's music stack, at `priority` 5 by default, which plays over the game's own score. `Audio.clearMusic(event)` takes it off, and whatever was underneath plays again. Only events that start a track work: most of the game's music events only switch the score between moods. A name you have cleared cannot be used again in the same session.

`Audio.setState(group, state)` and `Audio.setRTPC(name, value)` set Wwise states and parameters for the whole sound engine, mostly useful with soundbanks of your own.

Music, states, parameters and loaded banks are sent from the server once, to the players connected at the time, so set them again for players who join later.

## The player's volume

`Settings.getAudio()` reads the player's own volume settings, from 0 to 1, and `Settings.setAudio(patch, options)` changes them. These are the player's choices, so only write them from a settings panel they opened. Voice chat has its own volume, [`Voice.setVolume`](/guides/camera-and-sound/voice-chat/#on-the-client).

## Where to go next

- [Voice chat](/guides/camera-and-sound/voice-chat/) for players talking to each other.
- [Camera](/guides/camera-and-sound/camera/) for the pictures to go with the sound.
152 changes: 152 additions & 0 deletions guides/Camera and sound/camera.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
title: Camera
description: Take over the player's view with a camera of your own, move it in shots, paths and orbits, fade the screen, and hand the view back.
sidebar:
order: 70
---

Each client has one custom camera that client scripts drive with the `Camera` global. While it is active, the player sees through it instead of the game's own camera, and `Camera.restore()` hands the view back. Positions are world centimetres, rotations are degrees and durations are seconds.

The camera only changes what the player sees. Their character still answers their input, so stop it with `LocalPlayer.stopPlayerInput()` for a cutscene, and hand it back with `LocalPlayer.restorePlayerInput()`.

## Taking over the view

`Camera.capture()` returns the game camera's current view, as a position, a rotation and a lens, or `null` in a menu or behind a loading screen. `Camera.activate(view, transition)` switches to a fixed camera at a view, blending over half a second by default:

```js
// client/main.js
function showVault() {
Camera.clearLookAt();
Camera.activate(
{ position: { x: 15200, y: -3400, z: 900 }, rotation: { pitch: -20, yaw: 90, roll: 0 } },
{ duration: 1.5, curve: "easeInOut" },
);
}

function endShot() {
if (Camera.active) Camera.restore({ duration: 1 });
}
```

A transition takes a `duration`, and a `curve` of `"linear"`, `"cubic"`, `"easeIn"`, `"easeOut"`, `"easeInOut"` (the default) or `"preBlended"`. `Camera.restore(transition)` blends back to the player's own camera the same way.

### One camera for every resource

The custom camera belongs to the client, not to the resource that activated it. Every resource drives the same one, and stopping a resource does not give the view back, so restore it yourself as your resource stops:

```js
// client/main.js
Events.on("resourceStop", (resourceName) => {
if (resourceName === "intro-cutscene" && Camera.active) Camera.restore({ duration: 0 });
});
```

Check `Camera.active` before restoring. `restore` always points the view back at the player's character, so calling it while your camera is not active takes the view from whatever else has it, such as the character creator.

Hand the view back before the player respawns or travels to another map, and activate again afterwards if you still need the camera.

## Moving the camera

Moves, paths, orbits and `lookAt` need an active camera and return `false` without one:

| Call | Does |
| --- | --- |
| `Camera.setPose({ position, rotation })` | Moves the camera at once |
| `Camera.animateTo(view, { duration, easing })` | Moves it to a view over `duration` seconds, 1 by default |
| `Camera.playPath(keyframes, { loop, pingPong })` | Flies it through up to 256 keyframes |
| `Camera.orbit(target, { radius, height, speed })` | Circles it around a point |
| `Camera.lookAt(target, track)` | Aims it at a point, and with `track` keeps aiming while it moves |

`easing` is `"linear"`, `"smoothStep"`, `"easeIn"`, `"easeOut"` or `"easeInOut"`, the default. `Camera.pause()`, `Camera.resume()` and `Camera.stop()` control whatever is moving, and `stop` leaves the camera where it is.

Nothing fires when a move ends. `Camera.playing` stays true while a move, path or orbit runs, so check it on a timer.

### Paths

A path starts at its first keyframe at once. Every later keyframe sets how long the camera takes to reach it, with `duration` (1 second by default) and `easing`. A keyframe without a `lens` keeps the previous one's. This intro flies over the grounds with the player held still, then hands back control:

```js
// client/main.js
const start = { position: { x: 0, y: 0, z: 800 }, rotation: { pitch: -10, yaw: 0, roll: 0 } };

function playIntro() {
LocalPlayer.stopPlayerInput();
Camera.clearLookAt();
Camera.activate(start, { duration: 0 });
Camera.playPath([
start,
{ position: { x: 3000, y: 0, z: 900 }, rotation: { pitch: -15, yaw: 30, roll: 0 }, duration: 6 },
{ position: { x: 3000, y: 2500, z: 1200 }, rotation: { pitch: -25, yaw: 90, roll: 0 }, duration: 5, easing: "easeOut" },
]);

const timer = setInterval(() => {
if (Camera.playing) return;
clearInterval(timer);
Camera.restore({ duration: 1 });
LocalPlayer.restorePlayerInput();
}, 250);
}
```

`loop` repeats the path until you stop it, and `pingPong` plays it back and forth, for a menu backdrop that keeps moving.

### Orbits

`Camera.orbit(target, options)` circles a point at `radius` centimetres (300 by default), `height` centimetres above it (0 by default) and `speed` degrees per second (20 by default; negative goes the other way). It keeps the camera aimed at the point.

An orbit starts from wherever the camera is around the point and jumps straight onto the circle, so activate the camera on the circle first:

```js
// client/main.js
function circlePlayer() {
const pos = LocalPlayer.getPosition();
const view = Camera.capture();
if (!pos || !view) return;

Camera.clearLookAt();
Camera.activate({ position: { x: pos.x + 400, y: pos.y, z: pos.z + 150 }, rotation: view.rotation }, { duration: 1 });
Camera.orbit(pos, { radius: 400, height: 150, speed: 15 });
}
```

### Clearing the aim

An orbit, or `lookAt`, leaves the camera aimed at its point after the orbit stops, after `stop` and even after `restore`. The aim then overrides the rotation of every later shot until `Camera.clearLookAt()` lifts it, so clear it before you set up a new shot, as the samples above do.

## Lens

`Camera.setLens(options)` changes the active camera's lens: `fov` is the horizontal field of view in degrees, between 0 and 180, and `focalLength`, `aperture` and `postProcessBlend` are there for cinematic shots. A view passed to `activate` or `animateTo`, or a keyframe, can carry a `lens` too. `Camera.lens` reads the lens back.

```js
// client/main.js
Camera.setLens({ fov: 30 });
```

## Fades

`Camera.fade(options)` fades the screen to a colour, whether or not your camera is active. It goes from opacity `from` (0 by default) to `to` (1 by default) over `duration` seconds (1 by default), in black unless you give a `color` with components from 0 to 1.

When a fade finishes, the screen clears again unless you pass `hold: true`. Fade out with `hold`, do what you need behind the curtain, then fade back in:

```js
// client/main.js
function cutTo(view) {
Camera.fade({ to: 1, duration: 0.5, hold: true });
setTimeout(() => {
Camera.clearLookAt();
Camera.activate(view, { duration: 0 });
Camera.fade({ from: 1, to: 0, duration: 0.5 });
}, 500);
}
```

`Camera.stopFade()` clears a fade at once. `restore` does not, so stop a held fade yourself.

## Shakes and other effects

`Camera.playShake(classPath, { scale, space })` plays one of the game's camera shakes by its full class path, and `Camera.stopShakes()` stops them. `Camera.playAnimation`, `Camera.addLensEffect` and `Camera.addModifier` do the same for the game's camera animations, lens effects and modifiers. A modifier cannot be removed again.

## Where to go next

- [HUD, prompts and nametags](/guides/user-interface/hud-and-nametags/#hiding-the-hud) for hiding the HUD during a cutscene.
- [Audio](/guides/camera-and-sound/audio/) for the sound that goes with it.
Loading
Loading