Player
Get the player name
WA.player.name: string;
The player name is available from the WA.player.name
property.
You need to wait for the end of the initialization before accessing WA.player.name
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.
You need to wait for the end of the initialization before accessing WA.player.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.
You need to wait for the end of the initialization before accessing WA.player.language
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.
You need to wait for the end of the initialization before accessing WA.player.tags
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.
You need to wait for the end of the initialization before calling WA.player.getPosition()
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.
A typical use-case for the user-room token is providing logo upload capabilities in a map. The token can be used as a way to authenticate a WorkAdventure player and ensure he is indeed in the map and authorized to upload a logo.
You need to wait for the end of the initialization before accessing WA.player.userRoomToken
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.
You need to wait for the end of the initialization before calling WA.player.getPosition()
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 the
RemotePlayer.state
object (you can get aRemotePlayer
object) usingWA.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.
Depending on the server you are using, the server might itself decide of a maximum TTL for player variables. So far, WorkAdventure SAAS version has no maximum TTL set. However, please note you should probably not use player variables for sensitive / important information.
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.
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".
Player variables can be stored in 2 different places. If the player is logged, the player variables are stored on the WorkAdventure server. If the player is not logged, the player variables are stored in local storage (so in the player's browser).
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);
You can also teleport a player to a specific entry point of the current map using the WA.nav.goToRoom
function:
// 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
This feature is experimental. The signature of the function might change in the future.
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
This feature is experimental. The signature of the function might change in the future.
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.
You don't want to put an await
in front of your call to appendAudioData
. Indeed, you should put as much as possible
in the audio buffer. If you wait for the sound to be played before emitting the next bit of sound, the sound will stutter.
Please note the sound is played to all the players in the bubble except the player who called the function.
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
This feature is experimental. The signature of the function might change in the future.
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
This feature is experimental. The signature of the function might change in the future.
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
This feature is experimental. The signature of the function might change in the future.
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
This feature is experimental. The signature of the function might change in the future.
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' });
});