Tutorial 3 - Closing Doors - WorkAdventure Documentation
Tutorial 3 - Closing Doors - WorkAdventure Documentation
Recent posts
In the code
Final result
The tutorial also shows you how you can install a bell close to the door and everyone within a short
radius can hear the bell.
Preparation
Search for door (open and closed state) and bell tiles.
Find a sound file for the bell. Make sure you got the right license for your project to use it.
Make sure your map was built on the WorkAdventure Starter-Kit in order to have a ready to use API
1. Create a simple door that can be operated from the inside of the room
2. Make sure the door can be operated from the outside of the room if there is no one inside
3. Add a bell
The scripting API can modify the local map of the player. We will use it to open and close the door (by
hiding or showing the correct layers). When you show or hide a layer using the scripting API, it only
happens locally (in the browser of the user). It does not affect the map for other users by default.
We will therefore need a way to synchronize the state of the door between all players. We will use
"variables" to do that. Variables can be attached to a map or to a player.
We will create a variable attached to the map named doorState that will contain the state of the door
(open or closed). When a player opens or closes the door, we will update the variable. When a player
enters the room, we will check the state of the door. If the door is closed, we will open it automatically.
Prepare a door tile with an open and closed door state on your map. Place the tiles in a folder called
door and name the open door tile layer door_opened and the closed door door_closed Make sure
you hide the open_door state (click on the 'eye' image). The default state of the door is closed.
Map variables must be declared in Tiled before being usable in the scripting API.
Create a new object layer above the floorLayer and call it configuration .
Now create a new object point and call it doorState . You can also use any other name for it, but
then you need to make sure that you adjust the variables in the code. You can put the point wherever
you want on the map, it does not matter.
INFO
If you want to create a map with several doors, each door should have its own variable with its
own name.
Make sure the doorState object point is located under the configuration layer.
Now we need to turn this "point" object into a real "variable". Go to the doorState object and write in
the value field of class the name variable .
Also add a custom property of type boolean and give it the name default . Select the default property.
INFO
Variables can be declared in any object layer. However, if you explicitly declare it in a layer named
configuration , the variable will automatically appear and be editable in the "Configure the room"
menu of WorkAdventure.
We need to define tiles before and behind the door, which are triggered when the WOKA walks on it.
They either open or close the door.
Create a new tile folder called doorsteps and define two different layer types inside the folder:
outside_doorstep and inside_doorstep . outside_doorstep is the area when you enter the door
and inside_doorstep the area in the room, when you want to close the door. Define the tiles for both
areas. If the Woka steps on these tiles the door opens or closes.
And finally click in the Navigation on Map then Map Properties and double-check the custom
property script is set to src/main.ts .
There should be a main.ts file here. If not, create the main.ts file.
/**
* Utility function to display the correct door image depending on the state of the
*/
function displayDoor(state: boolean) {
if (state === true) {
WA.room.showLayer('door/door_opened');
WA.room.hideLayer('door/door_closed');
} else {
WA.room.hideLayer('door/door_opened');
WA.room.showLayer('door/door_closed');
}
}
// The line below bootstraps the Scripting API Extra library that adds a number
// Most notably for us, it is used to generate the "Configure the room" menu and
bootstrapExtra().then(() => {
console.log('Scripting API Extra ready');
}).catch(e => console.error(e));
// After load, we listen to variable change to display the correct door image.
WA.state.onVariableChange('doorState').subscribe((doorState) => {
// Each time the "doorState" variable changes, we call the "displayDoor" fun
displayDoor(doorState as boolean);
});
// When someone walks on the doorstep (inside the room), we display a message to
WA.room.onEnterLayer('doorsteps/inside_doorstep').subscribe(() => {
openCloseMessage = WA.ui.displayActionMessage({
message: "Press 'space' to open/close the door",
callback: () => {
WA.state.doorState = !WA.state.doorState;
}
});
});
// When someone leaves the doorstep (inside the room), we remove the message
WA.room.onLeaveLayer('doorsteps/inside_doorstep').subscribe(() => {
if (openCloseMessage !== undefined) {
openCloseMessage.remove();
}
});
}).catch(e => console.error(e));
export {};
The script makes use of the WA global object that is provided by the WorkAdventure scripting API. This
object provides a few objects / methods that we will use:
WA.onInit() is a method that returns a Promise that is resolved when the scripting API is ready to
be used. We need to wait for WorkAdventure to be ready before we can use the complete scripting
API.
WA.room.showLayer() and WA.room.hideLayer() are methods that allow us to show or hide a
layer on the map. In our case, we want to show or hide the door/door_opened and
door/door_closed layers depending on the state of the door.
The WA.state object contains all the variables that are attached to the map. We can use it to read
or write the doorState variable using WA.state.doorState .
WA.state.onVariableChange can be used to listen to variable changes. In our case, we want to
update the door image each time the doorState variable changes.
WA.room.onEnterLayer and WA.room.onLeaveLayer is used to detect when we enter or leave a
doorstep.
When we enter the doorstep, we display an action message using the
WA.ui.displayActionMessage method. This action message will display a message to the user
and will allow him to press the "space" key to open or close the door. When the user presses the
"space" key, we update the doorState variable.
With this script, we have a working door that can be operated from inside the room. However, we have
an issue. If someone is inside the room, closes the door, and then closes the WorkAdventure tab, no
one will be able to open it again from the outside.
In order to do that, we will attach a "currentRoom" variable to each player. This variable will contain the
name of the meeting room the player is currently in. When a player enters the room, we will set the
variable to the name of the room. When a player leaves the room, we will set the variable to undefined .
Each player can read the currentRoom variable of other players. We will use this to check if someone is
inside the room.
TIP
You can put this layer at the bottom of the layer list if you want it to be not visible. Also, pressing
the "H" key in Tiled will display the layer in transparency mode, so you can view your layer, even if it
is hidden bellow the floor.
In the code
In the main.ts file, add the following code:
WA.room.onEnterLayer('meetingRoom').subscribe(() => {
WA.player.state.saveVariable("currentRoom", "meetingRoom", {
public: true,
persist: false
});
});
WA.room.onLeaveLayer('meetingRoom').subscribe(() => {
WA.player.state.saveVariable("currentRoom", undefined, {
public: true,
persist: false
});
});
The WA.player.state object is used to read or write variables that are attached to the player. In our
case, we want to write the currentRoom variable. We use the WA.player.state.saveVariable to set
this variable. You will notice we pass 2 additional parameters to this method:
{
public: true,
persist: false
}
The public parameter is used to make the variable readable by other players. If you set it to false ,
other players will not be able to read the variable. The persist parameter is used to make the variable
persistent. If you set it to true , the variable will be saved. If the player closes the WorkAdventure tab
and comes back later, the variable will still be there. In our case, we don't want this variable to be
persistent, so we set it to false .
Now that we successfully write the currentRoom variable, we need to read it to check if someone is
inside the room.
// When someone walks on the doorstep (outside the room), we check if the door i
// If the door is closed, and if no one is inside (because no player has the "cu
// we open the door automatically.
WA.room.onEnterLayer('doorsteps/outside_doorstep').subscribe(() => {
if (WA.state.doorState === false) {
const players = WA.players.list();
for (const player of players) {
if (player.state.currentRoom === "meetingRoom") {
// Someone is in the room
return;
}
}
// If no one is in the room and if the door is closed, we open it automa
WA.state.doorState = true;
}
});
WA.players.list is used to get the list of players that are currently connected to the map. It
returns a list of remote players. Each player has a state property that contains the public variables
attached to the player.
Each time the user enters the doorsteps/outside_doorstep layer (so each time the user gets
need to the door from the outside), we check if the door is closed. If it is closed, we get the list of
players. For each player, we check if the currentRoom variable is set to meetingRoom . If it is the
case, it means that someone is inside the room. We don't open the door. If no one is inside the
room, we open the door.
Create a new point object called bell . You can also use any other name for it, but then you need
to make sure that you adjust the variables in the code. Then, create one rectangle called
bellPopup for the Pop-up window area for the bell.
Now go to the bell object and declare the object as a variable as in the step before.
Create a custom property of the type boolean and name it bell . Select the bell custom property.
For the Bell Sound: Create a custom property of the type string and name it bellSound . Enter the
name of your sound file. Make sure you have placed your music file it in your public folder.
For the Bell Radius: Create a custom property of the type string and name it soundRadius enter 200
to make sure just Wokas, which are near the door are able to hear it. The value is expressed in pixels. A
Woka is 32 pixels tall.
The bell pop up window is shown, when you walk close to the bell. Create a new tile layer called
bellZone with the following Custom Properties:
bellButtonText of type string : This is the text or the emoji that appears in the window. bellPopup
of type string with the value bellPopup : This is the Pop up Window itself. bellVariable of type
string with the value bell
Final result
Success! We now have a door that can be opened from the inside or the outside, and that can be locked
if someone is inside the room. We also have a bell that can be rung by anyone close to the door.
/**
* Display the correct door image depending on the state of the door.
*/
function displayDoor(state: unknown) {
if (state === true) {
WA.room.showLayer('door/door_opened');
WA.room.hideLayer('door/door_closed');
} else {
WA.room.hideLayer('door/door_opened');
WA.room.showLayer('door/door_closed');
}
}
// The line below bootstraps the Scripting API Extra library that adds a number
bootstrapExtra().then(() => {
console.log('Scripting API Extra ready');
}).catch(e => console.error(e));
await WA.players.configureTracking({
players: true,
movement: false,
});
displayDoor(WA.state.doorState);
// When someone walks on the doorstep (inside the room), we display a message to
WA.room.onEnterLayer('doorsteps/inside_doorstep').subscribe(() => {
openCloseMessage = WA.ui.displayActionMessage({
message: "Press 'space' to open/close the door",
callback: () => {
WA.state.doorState = !WA.state.doorState;
}
});
});
// When someone leaves the doorstep (inside the room), we remove the message
WA.room.onLeaveLayer('doorsteps/inside_doorstep').subscribe(() => {
if (openCloseMessage !== undefined) {
openCloseMessage.remove();
}
});
WA.room.onEnterLayer('meetingRoom').subscribe(() => {
WA.player.state.saveVariable("currentRoom", "meetingRoom", {
public: true,
persist: false
});
});
WA.room.onLeaveLayer('meetingRoom').subscribe(() => {
WA.player.state.saveVariable("currentRoom", undefined, {
public: true,
persist: false
});
});
// When someone walks on the doorstep (outside the room), we check if the door i
// If the door is closed, and if no one is inside (because no player has the "cu
// we open the door automatically.
WA.room.onEnterLayer('doorsteps/outside_doorstep').subscribe(() => {
if (WA.state.doorState === false) {
const players = WA.players.list();
for (const player of players) {
if (player.state.currentRoom === "meetingRoom") {
// Someone is in the room
return;
}
}
// If no one is in the room and if the door is closed, we open it automa
WA.state.doorState = true;
}
});
export {};