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:
parent
e086f6dac0
commit
90f7287860
@ -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)
|
||||||
|
13
front/src/Api/Events/ColorEvent.ts
Normal file
13
front/src/Api/Events/ColorEvent.ts
Normal 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>;
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
40
front/src/Stores/OutlineColorStore.ts
Normal file
40
front/src/Stores/OutlineColorStore.ts
Normal 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();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
93
maps/tests/Outline/outline.json
Normal file
93
maps/tests/Outline/outline.json
Normal 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
|
||||||
|
}
|
36
maps/tests/Outline/outline.php
Normal file
36
maps/tests/Outline/outline.php
Normal 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>
|
@ -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">
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user