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
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 { ChangeLayerEvent } from "./ChangeLayerEvent";
import type { ChangeZoneEvent } from "./ChangeZoneEvent";
import { isColorEvent } from "./ColorEvent";
export interface TypedMessageEvent<T> extends MessageEvent {
data: T;
@ -152,6 +153,14 @@ export const iframeQueryMapTypeGuards = {
query: isCreateEmbeddedWebsiteEvent,
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;

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 { Subject } from "rxjs";
import { apiCallback } from "./registeredCallbacks";
@ -82,6 +82,24 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
}
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();

View File

@ -13,7 +13,8 @@ import { isSilentStore } from "../../Stores/MediaStore";
import { lazyLoadPlayerCharacterTextures, loadAllDefaultModels } from "./PlayerTexturesLoadingManager";
import { TexturesHelper } from "../Helpers/TexturesHelper";
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;
@ -40,6 +41,8 @@ export abstract class Character extends Container {
private emoteTween: Phaser.Tweens.Tween | null = null;
scene: GameScene;
private readonly _pictureStore: Writable<string | undefined>;
private readonly outlineColorStore = createColorStore();
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
constructor(
scene: GameScene,
@ -97,18 +100,26 @@ export abstract class Character extends Container {
});
this.on("pointerover", () => {
this.getOutlinePlugin()?.add(this.playerName, {
thickness: 2,
outlineColor: 0xffff00,
});
this.scene.markDirty();
this.outlineColorStore.pointerOver();
});
this.on("pointerout", () => {
this.getOutlinePlugin()?.remove(this.playerName);
this.scene.markDirty();
this.outlineColorStore.pointerOut();
});
}
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);
this.scene.physics.world.enableBody(this);
@ -315,6 +326,7 @@ export abstract class Character extends Container {
}
}
this.list.forEach((objectContaining) => objectContaining.destroy());
this.outlineColorStoreUnsubscribe();
super.destroy();
}
@ -401,4 +413,12 @@ export abstract class Character extends Container {
public get pictureStore(): 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) => {
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(
@ -1422,6 +1434,7 @@ ${escapedMessage}
iframeListener.unregisterAnswerer("removeActionMessage");
iframeListener.unregisterAnswerer("openCoWebsite");
iframeListener.unregisterAnswerer("getCoWebsites");
iframeListener.unregisterAnswerer("setPlayerOutline");
this.sharedVariablesManager?.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>
</td>
</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>
<h2>CoWebsite</h2>
<table class="table">

View File

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