Added a radial menu to run emotes
This commit is contained in:
parent
a1d52b4265
commit
35b37a6a88
@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
|
||||||
|
- Added the emote feature to Workadventure. (@Kharhamel, @Tabascoeye)
|
||||||
|
- The emote menu can be opened by clicking on your character.
|
||||||
|
- Clicking on one of its element will close the menu and play an emote above your character.
|
||||||
|
- This emote can be seen by other players.
|
||||||
- Mobile support has been improved
|
- Mobile support has been improved
|
||||||
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
|
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
|
||||||
- Mouse wheel support to zoom in / out
|
- Mouse wheel support to zoom in / out
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, 'layout_modes', 3);
|
super(scene, x, y, 'layout_modes', 3);
|
||||||
@ -6,6 +8,6 @@ export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
|
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
|
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
|
||||||
export const joystickBaseKey = 'joystickBase';
|
export const joystickBaseKey = 'joystickBase';
|
||||||
@ -19,8 +20,8 @@ export class MobileJoystick extends VirtualJoystick {
|
|||||||
x: -1000,
|
x: -1000,
|
||||||
y: -1000,
|
y: -1000,
|
||||||
radius: radius * window.devicePixelRatio,
|
radius: radius * window.devicePixelRatio,
|
||||||
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).setDepth(99999),
|
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).setDepth(DEPTH_INGAME_TEXT_INDEX),
|
||||||
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).setDepth(99999),
|
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).setDepth(DEPTH_INGAME_TEXT_INDEX),
|
||||||
enable: true,
|
enable: true,
|
||||||
dir: "8dir",
|
dir: "8dir",
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {discussionManager} from "../../WebRtc/DiscussionManager";
|
import {discussionManager} from "../../WebRtc/DiscussionManager";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export const openChatIconName = 'openChatIcon';
|
export const openChatIconName = 'openChatIcon';
|
||||||
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
||||||
@ -9,7 +10,7 @@ export class OpenChatIcon extends Phaser.GameObjects.Image {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
|
|
||||||
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, 'layout_modes', 0);
|
super(scene, x, y, 'layout_modes', 0);
|
||||||
@ -6,6 +8,6 @@ export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
58
front/src/Phaser/Components/RadialMenu.ts
Normal file
58
front/src/Phaser/Components/RadialMenu.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
|
import {DEPTH_UI_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
|
export interface RadialMenuItem {
|
||||||
|
sprite: string,
|
||||||
|
frame: number,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuRadius = 80;
|
||||||
|
export const RadialMenuClickEvent = 'radialClick';
|
||||||
|
|
||||||
|
export class RadialMenu extends Phaser.GameObjects.Container {
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene, x: number, y: number, private items: RadialMenuItem[]) {
|
||||||
|
super(scene, x, y);
|
||||||
|
this.setDepth(DEPTH_UI_INDEX)
|
||||||
|
this.scene.add.existing(this);
|
||||||
|
this.initItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initItems() {
|
||||||
|
const itemsNumber = this.items.length;
|
||||||
|
this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber))
|
||||||
|
}
|
||||||
|
|
||||||
|
private createRadialElement(item: RadialMenuItem, index: number, itemsNumber: number) {
|
||||||
|
const image = new Sprite(this.scene, 0, menuRadius, item.sprite, item.frame);
|
||||||
|
this.add(image);
|
||||||
|
this.scene.sys.updateList.add(image);
|
||||||
|
image.setDepth(DEPTH_UI_INDEX)
|
||||||
|
image.setInteractive({
|
||||||
|
hitArea: new Phaser.Geom.Circle(0, 0, 25),
|
||||||
|
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
useHandCursor: true,
|
||||||
|
});
|
||||||
|
image.on('pointerdown', () => this.emit(RadialMenuClickEvent, item));
|
||||||
|
image.on('pointerover', () => {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: image,
|
||||||
|
scale: 2,
|
||||||
|
duration: 500,
|
||||||
|
ease: 'Power3',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
image.on('pointerout', () => {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: image,
|
||||||
|
scale: 1,
|
||||||
|
duration: 500,
|
||||||
|
ease: 'Power3',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const angle = 2 * Math.PI * index / itemsNumber;
|
||||||
|
Phaser.Actions.RotateAroundDistance([image], {x: 0, y: 0}, angle, menuRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,6 +6,8 @@ import Sprite = Phaser.GameObjects.Sprite;
|
|||||||
import {TextureError} from "../../Exception/TextureError";
|
import {TextureError} from "../../Exception/TextureError";
|
||||||
import {Companion} from "../Companion/Companion";
|
import {Companion} from "../Companion/Companion";
|
||||||
import {getEmoteAnimName} from "../Game/EmoteManager";
|
import {getEmoteAnimName} from "../Game/EmoteManager";
|
||||||
|
import {GameScene} from "../Game/GameScene";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
const playerNameY = - 25;
|
const playerNameY = - 25;
|
||||||
|
|
||||||
@ -17,6 +19,8 @@ interface AnimationData {
|
|||||||
frames : number[]
|
frames : number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const interactiveRadius = 40;
|
||||||
|
|
||||||
export abstract class Character extends Container {
|
export abstract class Character extends Container {
|
||||||
private bubble: SpeechBubble|null = null;
|
private bubble: SpeechBubble|null = null;
|
||||||
private readonly playerName: BitmapText;
|
private readonly playerName: BitmapText;
|
||||||
@ -28,14 +32,16 @@ export abstract class Character extends Container {
|
|||||||
public companion?: Companion;
|
public companion?: Companion;
|
||||||
private emote: Phaser.GameObjects.Sprite | null = null;
|
private emote: Phaser.GameObjects.Sprite | null = null;
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene,
|
constructor(scene: GameScene,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
texturesPromise: Promise<string[]>,
|
texturesPromise: Promise<string[]>,
|
||||||
name: string,
|
name: string,
|
||||||
direction: PlayerAnimationDirections,
|
direction: PlayerAnimationDirections,
|
||||||
moving: boolean,
|
moving: boolean,
|
||||||
frame?: string | number
|
frame: string | number,
|
||||||
|
companion: string|null,
|
||||||
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(scene, x, y/*, texture, frame*/);
|
super(scene, x, y/*, texture, frame*/);
|
||||||
this.PlayerValue = name;
|
this.PlayerValue = name;
|
||||||
@ -49,19 +55,18 @@ export abstract class Character extends Container {
|
|||||||
this.invisible = false
|
this.invisible = false
|
||||||
})
|
})
|
||||||
|
|
||||||
/*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3);
|
|
||||||
this.teleportation.setInteractive();
|
|
||||||
this.teleportation.visible = false;
|
|
||||||
this.teleportation.on('pointerup', () => {
|
|
||||||
this.report.visible = false;
|
|
||||||
this.teleportation.visible = false;
|
|
||||||
});
|
|
||||||
this.add(this.teleportation);*/
|
|
||||||
|
|
||||||
this.playerName = new BitmapText(scene, 0, playerNameY, 'main_font', name, 7);
|
this.playerName = new BitmapText(scene, 0, playerNameY, 'main_font', name, 7);
|
||||||
this.playerName.setOrigin(0.5).setCenterAlign().setDepth(99999);
|
this.playerName.setOrigin(0.5).setCenterAlign().setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
this.add(this.playerName);
|
this.add(this.playerName);
|
||||||
|
|
||||||
|
if (this.isClickable()) {
|
||||||
|
this.setInteractive({
|
||||||
|
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
||||||
|
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
useHandCursor: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
scene.add.existing(this);
|
scene.add.existing(this);
|
||||||
|
|
||||||
this.scene.physics.world.enableBody(this);
|
this.scene.physics.world.enableBody(this);
|
||||||
@ -73,6 +78,10 @@ export abstract class Character extends Container {
|
|||||||
this.setDepth(-1);
|
this.setDepth(-1);
|
||||||
|
|
||||||
this.playAnimation(direction, moving);
|
this.playAnimation(direction, moving);
|
||||||
|
|
||||||
|
if (typeof companion === 'string') {
|
||||||
|
this.addCompanion(companion, companionTexturePromise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
||||||
@ -81,13 +90,14 @@ export abstract class Character extends Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract isClickable(): boolean;
|
||||||
|
|
||||||
public addTextures(textures: string[], frame?: string | number): void {
|
public addTextures(textures: string[], frame?: string | number): void {
|
||||||
for (const texture of textures) {
|
for (const texture of textures) {
|
||||||
if(!this.scene.textures.exists(texture)){
|
if(!this.scene.textures.exists(texture)){
|
||||||
throw new TextureError('texture not found');
|
throw new TextureError('texture not found');
|
||||||
}
|
}
|
||||||
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
||||||
sprite.setInteractive({useHandCursor: true});
|
|
||||||
this.add(sprite);
|
this.add(sprite);
|
||||||
this.getPlayerAnimations(texture).forEach(d => {
|
this.getPlayerAnimations(texture).forEach(d => {
|
||||||
this.scene.anims.create({
|
this.scene.anims.create({
|
||||||
@ -234,18 +244,26 @@ export abstract class Character extends Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playEmote(emoteKey: string) {
|
playEmote(emoteKey: string) {
|
||||||
if (this.emote) return;
|
this.cancelPreviousEmote();
|
||||||
|
|
||||||
this.playerName.setVisible(false);
|
this.playerName.setVisible(false);
|
||||||
this.emote = new Sprite(this.scene, 0, -40, emoteKey, 1);
|
this.emote = new Sprite(this.scene, 0, -40, emoteKey, 1);
|
||||||
this.emote.setDepth(99999);
|
this.emote.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
this.add(this.emote);
|
this.add(this.emote);
|
||||||
this.scene.sys.updateList.add(this.emote);
|
this.scene.sys.updateList.add(this.emote);
|
||||||
this.emote.play(getEmoteAnimName(emoteKey));
|
this.emote.play(getEmoteAnimName(emoteKey));
|
||||||
this.emote.on(Phaser.Animations.Events.SPRITE_ANIMATION_COMPLETE, () => {
|
this.emote.on(Phaser.Animations.Events.ANIMATION_COMPLETE, () => {
|
||||||
this.emote?.destroy();
|
this.emote?.destroy();
|
||||||
this.emote = null;
|
this.emote = null;
|
||||||
this.playerName.setVisible(true);
|
this.playerName.setVisible(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelPreviousEmote() {
|
||||||
|
if (!this.emote) return;
|
||||||
|
|
||||||
|
this.emote?.destroy();
|
||||||
|
this.emote = null;
|
||||||
|
this.playerName.setVisible(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,10 @@ export class RemotePlayer extends Character {
|
|||||||
companion: string|null,
|
companion: string|null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
|
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
|
||||||
|
|
||||||
//set data
|
//set data
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
|
||||||
if (typeof companion === 'string') {
|
|
||||||
this.addCompanion(companion, companionTexturePromise);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(position: PointInterface): void {
|
updatePosition(position: PointInterface): void {
|
||||||
@ -42,4 +38,8 @@ export class RemotePlayer extends Character {
|
|||||||
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isClickable(): boolean {
|
||||||
|
return false; //todo: make remote players clickable if they are logged in.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
8
front/src/Phaser/Game/DepthIndexes.ts
Normal file
8
front/src/Phaser/Game/DepthIndexes.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//this file contains all the depth indexes which will be used in our game
|
||||||
|
|
||||||
|
export const DEPTH_TILE_INDEX = 0;
|
||||||
|
//Note: Player characters use their y coordinate as their depth to simulate a perspective.
|
||||||
|
//See the Character class.
|
||||||
|
export const DEPTH_OVERLAY_INDEX = 10000;
|
||||||
|
export const DEPTH_INGAME_TEXT_INDEX = 100000;
|
||||||
|
export const DEPTH_UI_INDEX = 1000000;
|
@ -2,6 +2,7 @@ import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
|||||||
import {createLoadingPromise} from "../Entity/PlayerTexturesLoadingManager";
|
import {createLoadingPromise} from "../Entity/PlayerTexturesLoadingManager";
|
||||||
import {emoteEventStream} from "../../Connexion/EmoteEventStream";
|
import {emoteEventStream} from "../../Connexion/EmoteEventStream";
|
||||||
import {GameScene} from "./GameScene";
|
import {GameScene} from "./GameScene";
|
||||||
|
import {RadialMenuItem} from "../Components/RadialMenu";
|
||||||
|
|
||||||
enum RegisteredEmoteTypes {
|
enum RegisteredEmoteTypes {
|
||||||
short = 1,
|
short = 1,
|
||||||
@ -14,10 +15,11 @@ interface RegisteredEmote extends BodyResourceDescriptionInterface {
|
|||||||
type: RegisteredEmoteTypes
|
type: RegisteredEmoteTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//the last 3 emotes are courtesy of @tabascoeye
|
||||||
export const emotes: {[key: string]: RegisteredEmote} = {
|
export const emotes: {[key: string]: RegisteredEmote} = {
|
||||||
'emote-exclamation': {name: 'emote-exclamation', img: 'resources/emotes/pipo-popupemotes001.png', type: RegisteredEmoteTypes.short},
|
'emote-exclamation': {name: 'emote-exclamation', img: 'resources/emotes/pipo-popupemotes001.png', type: RegisteredEmoteTypes.short, },
|
||||||
'emote-interrogation': {name: 'emote-interrogation', img: 'resources/emotes/pipo-popupemotes002.png', type: RegisteredEmoteTypes.short},
|
'emote-interrogation': {name: 'emote-interrogation', img: 'resources/emotes/pipo-popupemotes002.png', type: RegisteredEmoteTypes.short},
|
||||||
'emote-sleep': {name: 'emote-sleep', img: 'resources/emotes/pipo-popupemotes002.png', type: RegisteredEmoteTypes.short},
|
'emote-sleep': {name: 'emote-sleep', img: 'resources/emotes/pipo-popupemotes021.png', type: RegisteredEmoteTypes.short},
|
||||||
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/taba-clap-emote.png', type: RegisteredEmoteTypes.short},
|
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/taba-clap-emote.png', type: RegisteredEmoteTypes.short},
|
||||||
'emote-thumbsdown': {name: 'emote-thumbsdown', img: 'resources/emotes/taba-thumbsdown-emote.png', type: RegisteredEmoteTypes.long},
|
'emote-thumbsdown': {name: 'emote-thumbsdown', img: 'resources/emotes/taba-thumbsdown-emote.png', type: RegisteredEmoteTypes.long},
|
||||||
'emote-thumbsup': {name: 'emote-thumbsup', img: 'resources/emotes/taba-thumbsup-emote.png', type: RegisteredEmoteTypes.long},
|
'emote-thumbsup': {name: 'emote-thumbsup', img: 'resources/emotes/taba-thumbsup-emote.png', type: RegisteredEmoteTypes.long},
|
||||||
@ -30,16 +32,6 @@ export const getEmoteAnimName = (emoteKey: string): string => {
|
|||||||
export class EmoteManager {
|
export class EmoteManager {
|
||||||
|
|
||||||
constructor(private scene: GameScene) {
|
constructor(private scene: GameScene) {
|
||||||
|
|
||||||
//todo: use a radial menu instead?
|
|
||||||
this.registerEmoteOnKey('keyup-Y', 'emote-clap');
|
|
||||||
this.registerEmoteOnKey('keyup-U', 'emote-thumbsup');
|
|
||||||
this.registerEmoteOnKey('keyup-I', 'emote-thumbsdown');
|
|
||||||
this.registerEmoteOnKey('keyup-O', 'emote-exclamation');
|
|
||||||
this.registerEmoteOnKey('keyup-P', 'emote-interrogation');
|
|
||||||
this.registerEmoteOnKey('keyup-T', 'emote-sleep');
|
|
||||||
|
|
||||||
|
|
||||||
emoteEventStream.stream.subscribe((event) => {
|
emoteEventStream.stream.subscribe((event) => {
|
||||||
const actor = this.scene.MapPlayersByKey.get(event.userId);
|
const actor = this.scene.MapPlayersByKey.get(event.userId);
|
||||||
if (actor) {
|
if (actor) {
|
||||||
@ -50,15 +42,6 @@ export class EmoteManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerEmoteOnKey(keyboardKey: string, emoteKey: string) {
|
|
||||||
this.scene.input.keyboard.on(keyboardKey, () => {
|
|
||||||
this.scene.connection?.emitEmoteEvent(emoteKey);
|
|
||||||
this.lazyLoadEmoteTexture(emoteKey).then(emoteKey => {
|
|
||||||
this.scene.CurrentPlayer.playEmote(emoteKey);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
lazyLoadEmoteTexture(textureKey: string): Promise<string> {
|
lazyLoadEmoteTexture(textureKey: string): Promise<string> {
|
||||||
const emoteDescriptor = emotes[textureKey];
|
const emoteDescriptor = emotes[textureKey];
|
||||||
if (emoteDescriptor === undefined) {
|
if (emoteDescriptor === undefined) {
|
||||||
@ -70,6 +53,9 @@ export class EmoteManager {
|
|||||||
});
|
});
|
||||||
this.scene.load.start();
|
this.scene.load.start();
|
||||||
return loadPromise.then(() => {
|
return loadPromise.then(() => {
|
||||||
|
if (this.scene.anims.exists(getEmoteAnimName(textureKey))) {
|
||||||
|
return Promise.resolve(textureKey);
|
||||||
|
}
|
||||||
const frameConfig = emoteDescriptor.type === RegisteredEmoteTypes.short ? {frames: [0,1,2]} : {frames : [0,1,2,3,4,5,6,7]};
|
const frameConfig = emoteDescriptor.type === RegisteredEmoteTypes.short ? {frames: [0,1,2]} : {frames : [0,1,2,3,4,5,6,7]};
|
||||||
this.scene.anims.create({
|
this.scene.anims.create({
|
||||||
key: getEmoteAnimName(textureKey),
|
key: getEmoteAnimName(textureKey),
|
||||||
@ -80,4 +66,20 @@ export class EmoteManager {
|
|||||||
return textureKey;
|
return textureKey;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMenuImages(): Promise<RadialMenuItem[]> {
|
||||||
|
const promises = [];
|
||||||
|
for (const key in emotes) {
|
||||||
|
const promise = this.lazyLoadEmoteTexture(key).then((textureKey) => {
|
||||||
|
const emoteDescriptor = emotes[textureKey];
|
||||||
|
return {
|
||||||
|
sprite: textureKey,
|
||||||
|
name: textureKey,
|
||||||
|
frame: emoteDescriptor.type === RegisteredEmoteTypes.short ? 1 : 4,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ import type {
|
|||||||
PositionInterface,
|
PositionInterface,
|
||||||
RoomJoinedMessageInterface
|
RoomJoinedMessageInterface
|
||||||
} from "../../Connexion/ConnexionModels";
|
} from "../../Connexion/ConnexionModels";
|
||||||
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
import {hasMovedEventName, Player, requestEmoteEventName} from "../Player/Player";
|
||||||
import {
|
import {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
JITSI_PRIVATE_MODE,
|
JITSI_PRIVATE_MODE,
|
||||||
@ -90,6 +90,7 @@ import {TextUtils} from "../Components/TextUtils";
|
|||||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||||
import {PinchManager} from "../UserInput/PinchManager";
|
import {PinchManager} from "../UserInput/PinchManager";
|
||||||
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick";
|
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick";
|
||||||
|
import {DEPTH_OVERLAY_INDEX} from "./DepthIndexes";
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
import {EmoteManager} from "./EmoteManager";
|
import {EmoteManager} from "./EmoteManager";
|
||||||
|
|
||||||
@ -132,7 +133,7 @@ const defaultStartLayerName = 'start';
|
|||||||
|
|
||||||
export class GameScene extends DirtyScene implements CenterListener {
|
export class GameScene extends DirtyScene implements CenterListener {
|
||||||
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
||||||
CurrentPlayer!: CurrentGamerInterface;
|
CurrentPlayer!: Player;
|
||||||
MapPlayers!: Phaser.Physics.Arcade.Group;
|
MapPlayers!: Phaser.Physics.Arcade.Group;
|
||||||
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
||||||
Map!: Phaser.Tilemaps.Tilemap;
|
Map!: Phaser.Tilemaps.Tilemap;
|
||||||
@ -428,7 +429,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
||||||
depth = 10000;
|
depth = DEPTH_OVERLAY_INDEX;
|
||||||
}
|
}
|
||||||
if (layer.type === 'objectgroup') {
|
if (layer.type === 'objectgroup') {
|
||||||
for (const object of layer.objects) {
|
for (const object of layer.objects) {
|
||||||
@ -1132,6 +1133,12 @@ ${escapedMessage}
|
|||||||
this.companion,
|
this.companion,
|
||||||
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
||||||
);
|
);
|
||||||
|
this.CurrentPlayer.on('pointerdown', () => {
|
||||||
|
this.emoteManager.getMenuImages().then((emoteMenuElements) => this.CurrentPlayer.openOrCloseEmoteMenu(emoteMenuElements))
|
||||||
|
})
|
||||||
|
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
|
||||||
|
this.connection?.emitEmoteEvent(emoteKey);
|
||||||
|
})
|
||||||
}catch (err){
|
}catch (err){
|
||||||
if(err instanceof TextureError) {
|
if(err instanceof TextureError) {
|
||||||
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
|
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
|
||||||
|
@ -2,17 +2,15 @@ import {PlayerAnimationDirections} from "./Animation";
|
|||||||
import type {GameScene} from "../Game/GameScene";
|
import type {GameScene} from "../Game/GameScene";
|
||||||
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
||||||
import {Character} from "../Entity/Character";
|
import {Character} from "../Entity/Character";
|
||||||
|
import {RadialMenu, RadialMenuClickEvent, RadialMenuItem} from "../Components/RadialMenu";
|
||||||
|
|
||||||
export const hasMovedEventName = "hasMoved";
|
export const hasMovedEventName = "hasMoved";
|
||||||
export interface CurrentGamerInterface extends Character{
|
export const requestEmoteEventName = "requestEmote";
|
||||||
moveUser(delta: number) : void;
|
|
||||||
say(text : string) : void;
|
|
||||||
isMoving(): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Player extends Character implements CurrentGamerInterface {
|
export class Player extends Character {
|
||||||
private previousDirection: string = PlayerAnimationDirections.Down;
|
private previousDirection: string = PlayerAnimationDirections.Down;
|
||||||
private wasMoving: boolean = false;
|
private wasMoving: boolean = false;
|
||||||
|
private emoteMenu: RadialMenu|null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
Scene: GameScene,
|
Scene: GameScene,
|
||||||
@ -26,14 +24,10 @@ export class Player extends Character implements CurrentGamerInterface {
|
|||||||
companion: string|null,
|
companion: string|null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
|
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
|
||||||
|
|
||||||
//the current player model should be push away by other players to prevent conflict
|
//the current player model should be push away by other players to prevent conflict
|
||||||
this.getBody().setImmovable(false);
|
this.getBody().setImmovable(false);
|
||||||
|
|
||||||
if (typeof companion === 'string') {
|
|
||||||
this.addCompanion(companion, companionTexturePromise);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
moveUser(delta: number): void {
|
moveUser(delta: number): void {
|
||||||
@ -88,4 +82,33 @@ export class Player extends Character implements CurrentGamerInterface {
|
|||||||
public isMoving(): boolean {
|
public isMoving(): boolean {
|
||||||
return this.wasMoving;
|
return this.wasMoving;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openOrCloseEmoteMenu(emotes:RadialMenuItem[]) {
|
||||||
|
if(this.emoteMenu) {
|
||||||
|
this.closeEmoteMenu();
|
||||||
|
} else {
|
||||||
|
this.openEmoteMenu(emotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isClickable(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
openEmoteMenu(emotes:RadialMenuItem[]): void {
|
||||||
|
this.cancelPreviousEmote();
|
||||||
|
this.emoteMenu = new RadialMenu(this.scene, 0, 0, emotes)
|
||||||
|
this.emoteMenu.on(RadialMenuClickEvent, (item: RadialMenuItem) => {
|
||||||
|
this.closeEmoteMenu();
|
||||||
|
this.emit(requestEmoteEventName, item.name);
|
||||||
|
this.playEmote(item.name);
|
||||||
|
})
|
||||||
|
this.add(this.emoteMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeEmoteMenu(): void {
|
||||||
|
if (!this.emoteMenu) return;
|
||||||
|
this.emoteMenu.destroy();
|
||||||
|
this.emoteMenu = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
client.adminConnection = adminRoomStream;
|
client.adminConnection = adminRoomStream;
|
||||||
|
|
||||||
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
|
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
|
||||||
|
|
||||||
if (message.hasUserjoinedroom()) {
|
if (message.hasUserjoinedroom()) {
|
||||||
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
|
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
|
||||||
if (!client.disconnecting) {
|
if (!client.disconnecting) {
|
||||||
@ -331,6 +332,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
debug('Leaving room %s.', socket.roomId);
|
debug('Leaving room %s.', socket.roomId);
|
||||||
|
|
||||||
room.leave(socket);
|
room.leave(socket);
|
||||||
if (room.isEmpty()) {
|
if (room.isEmpty()) {
|
||||||
this.rooms.delete(socket.roomId);
|
this.rooms.delete(socket.roomId);
|
||||||
|
Loading…
Reference in New Issue
Block a user