Player#
Get the player name#
WA.player.name: string;
The player name is available from the WA.player.name property.
WA.onInit().then(() => {
console.log('Player name: ', WA.player.name);
})
// Will display:
// Player name: Alice
Get the player ID#
WA.player.id: string|undefined;
The player ID is available from the WA.player.id property.
This is a unique identifier for a given player. Anonymous player might not have an id.
WA.onInit().then(() => {
console.log('Player ID: ', WA.player.id);
})
// Will display:
// Player ID: a293c901-4455-4b1e-cf39-f4c0420de6f5
Get the player language#
WA.player.language: string;
The current language of player is available from the WA.player.language property.
WA.onInit().then(() => {
console.log('Player language: ', WA.player.language);
})
// Will display:
// Player language: fr-FR
Get the tags of the player#
WA.player.tags: string[];
The player tags are available from the WA.player.tags property.
They represent a set of rights the player acquires after login in.
Tags attributed to a user depend on the authentication system you are using. For the hosted version
of WorkAdventure, you can define tags related to the user in the administration panel.
WA.onInit().then(() => {
console.log('Tags: ', WA.player.tags);
})
Get the position of the player#
WA.player.getPosition(): Promise<Position>
The player's current position is available using the WA.player.getPosition() function.
Position has the following attributes :
- x (number) : The coordinate x of the current player's position.
- y (number) : The coordinate y of the current player's position.
WA.onInit().then(async () => {
console.log('Position: ', await WA.player.getPosition());
})
Get the woka of the player#
WA.player.getWokaPicture(): Promise<string>
The player's Woka picture can be fetched using the WA.player.getWokaPicture() function.
This will return a promise resolving to a base64 encoded PNG of the Woka.
The Woka is facing south, in "standing" position.
Get the user-room token of the player#
WA.player.userRoomToken: string;
The user-room token is available from the WA.player.userRoomToken property.
This token can be used by third party services to authenticate a player and prove that the player is in a given room.
The token is generated by the administration panel linked to WorkAdventure. The token is a string and is depending on your implementation of the administration panel.
In WorkAdventure SAAS version, the token is a JWT token that contains information such as the player's room ID and its associated membership ID.
If you are using the self-hosted version of WorkAdventure and you developed your own administration panel, the token can be anything.
By default, self-hosted versions of WorkAdventure don't come with an administration panel, so the token string will be empty.
WA.onInit().then(() => {
console.log('Token: ', WA.player.userRoomToken);
})
Get the position of the player#
WA.player.getPosition(): Promise<Position>
The player's current position is available using the WA.player.getPosition() function.
Position has the following attributes :
- x (number) : The coordinate x of the current player's position.
- y (number) : The coordinate y of the current player's position.
WA.onInit().then(async () => {
console.log('Position: ', await WA.player.getPosition());
})
Listen to player movement#
WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void;
Listens to the movement of the current user and calls the callback. Sends an event when the user stops moving, changes direction and every 200ms when moving in the same direction.
The event has the following attributes :
- moving (boolean): true when the current player is moving, false otherwise.
- direction (string): "right" | "left" | "down" | "top" the direction where the current player is moving.
- x (number): coordinate X of the current player.
- y (number): coordinate Y of the current player.
- oldX (number): old coordinate X of the current player.
- oldY (number): old coordinate Y of the current player.
callback: the function that will be called when the current player is moving. It contains the event.
Example :
WA.player.onPlayerMove(console.log);
Player specific variables#
Similarly to maps (see API state related functions), it is possible to store data related to a specific player in a "state". Such data will be stored using the local storage from the user's browser. Any value that is serializable to JSON can be stored.
Each variable can be stored and fetched in a variety of ways.
Here is what defines a player variable.
Visibility:
A player variable can be public or private.
- Public variables are automatically shared to players around you. Players around you can view
these variables using theRemotePlayer.stateobject (you can get aRemotePlayerobject) using
WA.players.list(). - Private variables are only accessible by the current user.
Persistence:
A player variable can be persisted or transient
- Persisted variables are stored across sessions. If you refresh your page or come back later,
a persisted variable can be fetched again. Use "persisted variables" to store valuable values
(like a score in a game that is played in the long run) - Transient variables disappear as soon as the connection to the room is lost. So if you
you refresh your page, if your network connection is lost for a brief amount of time, or
if you simply close WorkAdventure and come back later, the transient player variable value
will be lost. Use transient variables for values that are short-lived in inherently tied to
the game state. For instance, if you are doing a live voting system, you can use a transient
variable to store the current vote of the player.
Time to live:
Persisted variables can have a Time to live (TTL):
The TTL (expressed in seconds) is the time after which the stored value will be destroyed.
TTL can be set on persisted variables only. It cannot be set on transient variables.
Scope:
A player variable can have 2 scopes:
- Room scope: the player variable is attached to a given room.
- World scope: the player variable is set for a given world. It is shared with all the rooms
of this world.
:::info About the notion of "world":
If you are using the SAAS version (online version) of WorkAdventure, you can create your worlds from the admin dashboard
and put your rooms in those worlds.
If you are using the self-hosted version of WorkAdventure (with no custom admin API configured), there is only one world,
and it is shared by all the rooms defined in the map-storage (i.e. by all URLs starting with /~/).
In both cases, any URL starting with /_/ is not part of any world. Trying to set a player variable with a scope
"world" for URLs starting with /_/ will be the same as setting the scope to "room".
:::
Setting a player variable#
A player variable can be set simply by assigning a value.
Example:
WA.player.state.foo = "value"
By default, variables saved are persisted and private in the world scope.
If you want to set some options, you will need to use the saveVariable function:
WA.player.state.saveVariable(
key: string,
value: unknown,
options?: {
public?: boolean;
persist?: boolean;
ttl?: number;
scope?: "world" | "room";
}
): Promise<void>;
For instance, setting a variable shared with other players, that is accessible from any rooms of the current world
with a time to live of one day:
WA.player.state.saveVariable("foo", "value", {
public: true,
persist: true,
ttl: 24 * 3600,
scope: "world",
});
Reading a player variable#
A player variable can be read by calling its key from the player's state.
Example:
WA.player.state.foo //will retrieve the variable
Listening to a player variable change#
You can listen to modifications
of any player variable by using the WA.player.state.onVariableChange() method.
WA.player.state.onVariableChange(name: string): Observable<unknown>
Usage:
WA.player.state.onVariableChange('config').subscribe((value) => {
console.log('Variable "config" changed. New value: ', value);
});
The WA.plaeyr.state.onVariableChange method returns an RxJS Observable object. This is
an object on which you can add subscriptions using the subscribe method.
Stopping tracking player variables#
If you want to stop tracking a player variable change, the subscribe method returns a subscription object with an unsubscribe method.
Example with unsubscription:
const subscription = WA.player.state.onVariableChange('config').subscribe((value) => {
console.log('Variable "config" changed. New value: ', value);
});
// Later:
subscription.unsubscribe();
Special rules for users connected several times#
You can be connected several times with the same user to WorkAdventure (and WorkAdventure
will not complain about it, this is by design).
Open another tab, connect again to WorkAdventure and you will be connected to WorkAdventure
twice with the same user. We will call those users connected several times to WorkAdventure
brothers.
Brothers happen to share the same player variables.
Also, if one browser sets a variable to a new value, other brothers can listen to variable
changes using WA.player.state.onVariableChange. They will receive the new value
if they are in the same room. So far, there is a limitation preventing brothers from listening to variable changes if
they are in different rooms in the same world.
Typing player variables#
If you are using Typescript, by default, the type of player variables is unknown. This is for security purpose, as we don't know
the type of the variable.
Internally, we define two interfaces named PublicPlayerState and PrivatePlayerState that contains the type of all player variables.
PublicPlayerState contains the state of all public variables (variables that are shared with other players) and PrivatePlayerState
contains the state of all private variables (variables that are only accessible by the current player).
The default declaration of PublicPlayerState and PrivatePlayerState is:
interface PublicPlayerState {
[key: string]: unknown;
}
interface PrivatePlayerState {
[key: string]: unknown;
}
Typescript allows third party module to merge their own types with existing ones. This means that you can define your own
PublicPlayerState and PrivatePlayerState interfaces in your code, and it will be merged with the default one. You will need to use this syntax in your code:
declare module "@workadventure/iframe-api-typings" {
interface PublicPlayerState {
someVariable: string,
anotherVariable: number,
}
interface PrivatePlayerState {
someSecret: string[],
}
}
This will allow you to access WA.player.state.someVariable and WA.player.state.someSecret with the correct types.
Merging your own declaration of PublicPlayerState and PrivatePlayerState will give you type checking at compile time and autocompletion in your IDE.
However, as it is customary with Typescript, it will not do any actual type checking at runtime. Do not forget that
player variables can be set by any player. This means that even if Typescript tells you that WA.player.state.someVariable
is a string, it could be a number at runtime. The only way to be sure of the type of a variable is to check it at runtime
using type guards or a type checking library like Zod.
Move player to position#
WA.player.moveTo(x: number, y: number, speed?: number): Promise<{ x: number, y: number, cancelled: boolean }>;
The parameters x and y are numbers of pixels, not tiles. So make sure to multiply them by 32 if you are counting tiles.
Player will try to find shortest path to the destination point and proceed to move there.
// Let's move player to x: 250 y: 250 with speed of 10
WA.player.moveTo(250, 250, 10);
You can also chain movement like this:
// Player will move to the next point after reaching first one
await WA.player.moveTo(250, 250, 10);
await WA.player.moveTo(500, 0, 10);
Or like this:
// Player will move to the next point after reaching first one or stop if the movement was cancelled
WA.player.moveTo(250, 250, 10).then((result) => {
if (!result.cancelled) {
WA.player.moveTo(500, 0, 10);
}
});
It is possible to get the information about current player's position on stop and if the movement was interrupted
// Result will store x and y of Player at the moment of movement's end and information if the movement was interrupted
const result = await WA.player.moveTo(250, 250, 10);
// result: { x: number, y: number, cancelled: boolean }
Teleport player to position#
WA.player.teleport(x: number, y: number): Promise<void>;
The parameters x and y are numbers of pixels, not tiles. So make sure to multiply them by 32 if you are counting tiles.
Player will be teleported to the destination point.
// Let's teleport player to x: 250 y: 250
WA.player.teleport(250, 250);
// Let's teleport the player to the entry named "my-entry-point"
WA.nav.goToRoom("#my-entry-point");
Set the outline color of the player#
WA.player.setOutlineColor(red: number, green: number, blue: number): Promise<void>;
WA.player.removeOutlineColor(): Promise<void>;
You can display a thin line around your player's name (the "outline").
Use setOutlineColor to set the outline and removeOutlineColor to remove it.
Colors are expressed in RGB. Each parameter is an integer between 0 and 255.
// Let's add a red outline to our player
WA.player.setOutlineColor(255, 0, 0);
When you set the outline on your player, other players will see the outline too (the outline color is shared across
browsers automatically).
![]()
Detecting when the user enters/leaves a meeting#
WA.player.proximityMeeting.onJoin(): Subscription<RemotePlayerInterface[]>
WA.player.proximityMeeting.onLeave(): Subscription<RemotePlayerInterface[]>
The event is triggered when the user enters or leaves a proximity meeting.
Example:
WA.player.proximityMeeting.onJoin().subscribe(async (players: RemotePlayerInterface[]) => {
WA.chat.sendChatMessage("You joined a proximity chat", "System");
});
WA.player.proximityMeeting.onLeave().subscribe(async () => {
WA.chat.sendChatMessage("You left the proximity chat", "System");
});
Detecting when a participant enters/leaves the current meeting#
WA.player.proximityMeeting.onParticipantJoin(): Subscription<RemotePlayerInterface>
WA.player.proximityMeeting.onParticipantLeave(): Subscription<RemotePlayerInterface>
The event is triggered when a user enters or leaves a proximity meeting.
Example:
WA.player.proximityMeeting.onParticipantJoin().subscribe(async (player: RemotePlayerInterface) => {
WA.chat.sendChatMessage("A participant joined the proximity chat", { scope: 'local', author: 'System' });
});
WA.player.proximityMeeting.onParticipantLeave().subscribe(async (player: RemotePlayerInterface) => {
WA.chat.sendChatMessage("A participant left the proximity chat", { scope: 'local', author: 'System' });
});
Playing a sound to players in the same meeting#
WA.player.proximityMeeting.playSound(url: string): Promise<void>
The playSound function plays a sound to all the players in the same bubble.
The sound will appear to come from the microphone of the player who called the function.
Example:
await WA.player.proximityMeeting.playSound("https://example.com/my_sound.mp3");
The method returns a promise that resolves when the sound has been played.
Streaming sound to players in the same meeting#
You can send a stream of audio to all the players in the same bubble. A typical use case for this feature is to create a
voice chat in WorkAdventure. The sound can be generated on a server and streamed to the players in the bubble.
WA.player.proximityMeeting.startAudioStream(sampleRate: number): Promise<AudioStream>;
interface AudioStream {
appendAudioData(data: Float32Array): Promise<void>;
resetAudioBuffer(): Promise<void>;
close(): Promise<void>;
}
The startAudioStream function starts an audio stream to all the players in the same bubble. The sampleRate parameter
is the sample rate of the audio stream. For a 24kHz audio stream, you would use 24000.
The function returns an AudioStream object that you can use to send audio data to the players.
The appendAudioData function sends a chunk of audio data to the players. The data parameter is an array of
float32 values representing the raw uncompressed audio data.
You can send multiple chunks of audio data in a row. If you are sending chunks of audio data faster than the sound
is played, the audio stream will buffer the data and play it at the correct speed.
appendAudioData returns a promise. The promise resolves only when the sound was actually dispatched/played in the bubble.
If you sent too much data and want to stop the audio stream, you can call the resetAudioBuffer function. This will
empty the audio buffer and stop the audio stream. When you do so, any promise returned by appendAudioData that
match a chunk of audio data that was not played yet will be rejected.
Finally, when you are done with the audio stream, you can call the close function. This will stop the audio stream
and free the resources.
Example:
The following example generates a 10 seconds long sine wave at 440Hz and sends it to the players in the bubble for 5 seconds.
Then it stops the stream and waits for 5 seconds before closing the stream.
const sampleRate = 24000;
const audioStream = await WA.player.proximityMeeting.startAudioStream(sampleRate);
// Generate a sine wave
const frequency = 440;
const amplitude = 0.5;
const duration = 10;
const numSamples = duration * sampleRate;
const samples = new Float32Array(numSamples);
for (let i = 0; i < numSamples; i++) {
samples[i] = amplitude * Math.sin(2 * Math.PI * frequency * i / sampleRate);
}
audioStream.appendAudioData(samples);
// Wait for 5 seconds
await new Promise((resolve) => setTimeout(resolve, 5000));
// Stop the stream.
audioStream.resetAudioBuffer();
// Wait for 5 seconds
await new Promise((resolve) => setTimeout(resolve, 5000));
// Close the stream.
await audioStream.close();
Listening to the microphone of the players in the same meeting#
WA.player.proximityMeeting.listenToAudioStream(sampleRate: number): Observable<Float32Array>
The listenToAudioStream function listens to the microphone of all the players in the same bubble. The sampleRate parameter
is the sample rate of the audio stream. For a 24kHz audio stream, you would use 24000.
The function returns an RxJS Observable object that you can use to listen to the audio data. The observable is called
every few milliseconds with a chunk of audio data.
The voice of all players in the bubble is merged in a single mono stream.
Audio data is sent as an array of float32 values representing the raw uncompressed audio data.
Example:
const sampleRate = 24000;
const subscription = WA.player.proximityMeeting.listenToAudioStream(sampleRate).subscribe((data: Float32Array) => {
// Process the audio data
console.log(data);
});
// When you are done listening to the audio stream, you can unsubscribe from the observable.
subscription.unsubscribe();
Asking users to follow you#
WA.player.proximityMeeting.followMe(): Promise<void>
The followMe function asks all the players in the same bubble to follow the player who called the function.
Unlike the "follow" button in the UI, all the players in the bubble will be forced to follow the player who called the function.
They can still stop following the player by clicking on the "stop following" button in the UI.
Stop leading users#
WA.player.proximityMeeting.stopLeading(): Promise<void>
This function is the opposite of followMe. It ends the "follow" state for all the players in the bubble.
Example:
// Start leading the users
await WA.player.proximityMeeting.followMe();
// Move everybody to (250, 250)
await WA.player.moveTo(250, 250);
// Stop leading the users
await WA.player.proximityMeeting.stopLeading();
Tracking who is following you#
WA.player.proximityMeeting.onFollowed(): Subscription<RemotePlayerInterface>
WA.player.proximityMeeting.onUnfollowed(): Subscription<RemotePlayerInterface>
You can be notified when a player starts following you or stops following you.
Example:
WA.player.proximityMeeting.onFollowed().subscribe(async (player: RemotePlayerInterface) => {
WA.chat.sendChatMessage(`${player.name} is now following you`, { scope: 'local', author: 'System' });
});
WA.player.proximityMeeting.onUnfollowed().subscribe(async (player: RemotePlayerInterface) => {
WA.chat.sendChatMessage(`${player.name} stopped following you`, { scope: 'local', author: 'System' });
});