Adding the ability to set the player's color outline via the scripting API

(currently not shared with the other players yet)
This commit is contained in:
David Négrier 2021-12-21 17:02:18 +01:00
parent e086f6dac0
commit 90f7287860
11 changed files with 282 additions and 9 deletions

View file

@ -106,3 +106,25 @@ Example :
```javascript ```javascript
WA.player.onPlayerMove(console.log); WA.player.onPlayerMove(console.log);
``` ```
### 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.
```typescript
// 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).
![](images/outlines.png)

View file

@ -0,0 +1,13 @@
import * as tg from "generic-type-guard";
export const isColorEvent = new tg.IsInterface()
.withProperties({
red: tg.isNumber,
green: tg.isNumber,
blue: tg.isNumber,
})
.get();
/**
* A message sent from the iFrame to the game to dynamically set the outline of the player.
*/
export type ColorEvent = tg.GuardedType<typeof isColorEvent>;

View file

@ -29,6 +29,7 @@ import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/Trigg
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent";
import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent";
import { isColorEvent } from "./ColorEvent";
export interface TypedMessageEvent<T> extends MessageEvent { export interface TypedMessageEvent<T> extends MessageEvent {
data: T; data: T;
@ -152,6 +153,14 @@ export const iframeQueryMapTypeGuards = {
query: isCreateEmbeddedWebsiteEvent, query: isCreateEmbeddedWebsiteEvent,
answer: tg.isUndefined, answer: tg.isUndefined,
}, },
setPlayerOutline: {
query: isColorEvent,
answer: tg.isUndefined,
},
removePlayerOutline: {
query: tg.isUndefined,
answer: tg.isUndefined,
},
}; };
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never; type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;

View file

@ -1,4 +1,4 @@
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent"; import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent";
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import { apiCallback } from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
@ -82,6 +82,24 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
} }
return userRoomToken; return userRoomToken;
} }
public setOutlineColor(red: number, green: number, blue: number): Promise<void> {
return queryWorkadventure({
type: "setPlayerOutline",
data: {
red,
green,
blue,
},
});
}
public removeOutlineColor(): Promise<void> {
return queryWorkadventure({
type: "removePlayerOutline",
data: undefined,
});
}
} }
export default new WorkadventurePlayerCommands(); export default new WorkadventurePlayerCommands();

View file

@ -13,7 +13,8 @@ import { isSilentStore } from "../../Stores/MediaStore";
import { lazyLoadPlayerCharacterTextures, loadAllDefaultModels } from "./PlayerTexturesLoadingManager"; import { lazyLoadPlayerCharacterTextures, loadAllDefaultModels } from "./PlayerTexturesLoadingManager";
import { TexturesHelper } from "../Helpers/TexturesHelper"; import { TexturesHelper } from "../Helpers/TexturesHelper";
import type { PictureStore } from "../../Stores/PictureStore"; import type { PictureStore } from "../../Stores/PictureStore";
import { Writable, writable } from "svelte/store"; import { Unsubscriber, Writable, writable } from "svelte/store";
import { createColorStore } from "../../Stores/OutlineColorStore";
const playerNameY = -25; const playerNameY = -25;
@ -40,6 +41,8 @@ export abstract class Character extends Container {
private emoteTween: Phaser.Tweens.Tween | null = null; private emoteTween: Phaser.Tweens.Tween | null = null;
scene: GameScene; scene: GameScene;
private readonly _pictureStore: Writable<string | undefined>; private readonly _pictureStore: Writable<string | undefined>;
private readonly outlineColorStore = createColorStore();
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
constructor( constructor(
scene: GameScene, scene: GameScene,
@ -97,18 +100,26 @@ export abstract class Character extends Container {
}); });
this.on("pointerover", () => { this.on("pointerover", () => {
this.getOutlinePlugin()?.add(this.playerName, { this.outlineColorStore.pointerOver();
thickness: 2,
outlineColor: 0xffff00,
});
this.scene.markDirty();
}); });
this.on("pointerout", () => { this.on("pointerout", () => {
this.getOutlinePlugin()?.remove(this.playerName); this.outlineColorStore.pointerOut();
this.scene.markDirty();
}); });
} }
this.outlineColorStoreUnsubscribe = this.outlineColorStore.subscribe((color) => {
if (color === undefined) {
this.getOutlinePlugin()?.remove(this.playerName);
} else {
this.getOutlinePlugin()?.remove(this.playerName);
this.getOutlinePlugin()?.add(this.playerName, {
thickness: 2,
outlineColor: color,
});
}
this.scene.markDirty();
});
scene.add.existing(this); scene.add.existing(this);
this.scene.physics.world.enableBody(this); this.scene.physics.world.enableBody(this);
@ -315,6 +326,7 @@ export abstract class Character extends Container {
} }
} }
this.list.forEach((objectContaining) => objectContaining.destroy()); this.list.forEach((objectContaining) => objectContaining.destroy());
this.outlineColorStoreUnsubscribe();
super.destroy(); super.destroy();
} }
@ -401,4 +413,12 @@ export abstract class Character extends Container {
public get pictureStore(): PictureStore { public get pictureStore(): PictureStore {
return this._pictureStore; return this._pictureStore;
} }
public setOutlineColor(red: number, green: number, blue: number): void {
this.outlineColorStore.setColor((red << 16) | (green << 8) | blue);
}
public removeOutlineColor(): void {
this.outlineColorStore.removeColor();
}
} }

View file

@ -1300,6 +1300,18 @@ ${escapedMessage}
iframeListener.registerAnswerer("removeActionMessage", (message) => { iframeListener.registerAnswerer("removeActionMessage", (message) => {
layoutManagerActionStore.removeAction(message.uuid); layoutManagerActionStore.removeAction(message.uuid);
}); });
iframeListener.registerAnswerer("setPlayerOutline", (message) => {
const normalizeColor = (color: number) => Math.min(Math.max(0, Math.round(color)), 255);
const red = normalizeColor(message.red);
const green = normalizeColor(message.green);
const blue = normalizeColor(message.blue);
this.CurrentPlayer.setOutlineColor(red, green, blue);
});
iframeListener.registerAnswerer("removePlayerOutline", (message) => {
this.CurrentPlayer.removeOutlineColor();
});
} }
private setPropertyLayer( private setPropertyLayer(
@ -1422,6 +1434,7 @@ ${escapedMessage}
iframeListener.unregisterAnswerer("removeActionMessage"); iframeListener.unregisterAnswerer("removeActionMessage");
iframeListener.unregisterAnswerer("openCoWebsite"); iframeListener.unregisterAnswerer("openCoWebsite");
iframeListener.unregisterAnswerer("getCoWebsites"); iframeListener.unregisterAnswerer("getCoWebsites");
iframeListener.unregisterAnswerer("setPlayerOutline");
this.sharedVariablesManager?.close(); this.sharedVariablesManager?.close();
this.embeddedWebsiteManager?.close(); this.embeddedWebsiteManager?.close();

View file

@ -0,0 +1,40 @@
import { writable } from "svelte/store";
export function createColorStore() {
const { subscribe, set } = writable<number | undefined>(undefined);
let color: number | undefined = undefined;
let focused: boolean = false;
const updateColor = () => {
if (focused) {
set(0xffff00);
} else {
set(color);
}
};
return {
subscribe,
pointerOver() {
focused = true;
updateColor();
},
pointerOut() {
focused = false;
updateColor();
},
setColor(newColor: number) {
color = newColor;
updateColor();
},
removeColor() {
color = undefined;
updateColor();
},
};
}

View file

@ -0,0 +1,93 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"height":10,
"id":1,
"name":"floor",
"opacity":1,
"properties":[
{
"name":"openWebsite",
"type":"string",
"value":"outline.php"
},
{
"name":"openWebsiteAllowApi",
"type":"bool",
"value":true
}],
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":2,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":3,
"name":"floorLayer",
"objects":[
{
"height":342.082007343941,
"id":1,
"name":"",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":13,
"text":"Test:\nPlay with the colors and the limits in the form\n\nResult:\nThe outline should be displayed. A mouse over displays the yellow outline but the normal outline comes back on mouse out.\n\nTest:\nClick the remove outline\n\nResult:\nThe outline is removed\n\nTest:\nClick with many players\n\nResult:\nThe outline is correctly shared",
"wrap":true
},
"type":"",
"visible":true,
"width":274.96422378621,
"x":35.7623688177162,
"y":8.73391812865529
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":6,
"nextobjectid":3,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"2021.03.23",
"tileheight":32,
"tilesets":[
{
"columns":11,
"firstgid":1,
"image":"..\/tileset1.png",
"imageheight":352,
"imagewidth":352,
"margin":0,
"name":"tileset1",
"spacing":0,
"tilecount":121,
"tileheight":32,
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.5,
"width":10
}

View file

@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">
<head>
<script src="<?php echo $_SERVER["FRONT_URL"] ?>/iframe_api.js"></script>
<script>
WA.onInit().then(() => {
console.log('After WA init');
const setOutlineButton = document.getElementById('setOutline');
const removeOutlineButton = document.getElementById('removeOutline');
const redField = document.getElementById('red');
const greenField = document.getElementById('green');
const blueField = document.getElementById('blue');
setOutlineButton.addEventListener('click', () => {
console.log('SETTING OUTLINE');
WA.player.setOutlineColor(parseInt(redField.value), parseInt(greenField.value), parseInt(blueField.value));
});
removeOutlineButton.addEventListener('click', () => {
console.log('REMOVING OUTLINE');
WA.player.removeOutlineColor();
});
});
</script>
</head>
<body>
red: <input type="text" id="red" value="0" /><br/>
green: <input type="text" id="green" value="0" /><br/>
blue: <input type="text" id="blue" value="0" /><br/>
<button id="setOutline">Set outline</button>
<button id="removeOutline">Remove outline</button>
</body>
</html>

View file

@ -251,6 +251,14 @@
<a href="#" class="testLink" data-testmap="ChangeLayerApi/change_layer_api.json" target="_blank">Testing scripting API for enters/leaves layer</a> <a href="#" class="testLink" data-testmap="ChangeLayerApi/change_layer_api.json" target="_blank">Testing scripting API for enters/leaves layer</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-outline-api"> Success <input type="radio" name="test-outline-api"> Failure <input type="radio" name="test-outline-api" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="Outline/outline.json" target="_blank">Testing scripting API for outline on players</a>
</td>
</tr>
</table> </table>
<h2>CoWebsite</h2> <h2>CoWebsite</h2>
<table class="table"> <table class="table">

View file

@ -13,6 +13,7 @@ message PositionMessage {
} }
Direction direction = 3; Direction direction = 3;
bool moving = 4; bool moving = 4;
uint32 outlineColor = 5;
} }
message PointMessage { message PointMessage {