From 3af6461c017cbd160ad4c12f053c21c5b54e9204 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Wed, 26 Jan 2022 12:57:10 +0100 Subject: [PATCH] Activatable objects handling WIP --- front/src/Phaser/Entity/Character.ts | 4 ++ front/src/Phaser/Entity/RemotePlayer.ts | 49 +++++++++++------ front/src/Phaser/Game/ActivatableInterface.ts | 6 ++ front/src/Phaser/Game/GameScene.ts | 55 +++++++++++++------ front/src/Phaser/Items/ActionableItem.ts | 12 ++-- 5 files changed, 89 insertions(+), 37 deletions(-) create mode 100644 front/src/Phaser/Game/ActivatableInterface.ts diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index b9b23207..42bd4edf 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -140,6 +140,10 @@ export abstract class Character extends Container { } } + public getPosition(): { x: number, y: number } { + return { x: this.x, y: this.y }; + } + private async getSnapshot(): Promise { const sprites = Array.from(this.sprites.values()).map((sprite) => { return { sprite, frame: 1 }; diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index e04fcf7e..22d1b2bf 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -6,14 +6,17 @@ import type { GameScene } from "../Game/GameScene"; import type { PointInterface } from "../../Connexion/ConnexionModels"; import type { PlayerAnimationDirections } from "../Player/Animation"; import type { Unsubscriber } from 'svelte/store'; +import type { ActivatableInterface } from '../Game/ActivatableInterface'; /** * Class representing the sprite of a remote player (a player that plays on another computer) */ -export class RemotePlayer extends Character { - userId: number; +export class RemotePlayer extends Character implements ActivatableInterface { + + public userId: number; + public readonly activationRadius: number; + private visitCardUrl: string | null; - private isActionsMenuInitialized: boolean = false; private actionsMenuStoreUnsubscriber: Unsubscriber; @@ -28,7 +31,8 @@ export class RemotePlayer extends Character { moving: boolean, visitCardUrl: string | null, companion: string | null, - companionTexturePromise?: Promise + companionTexturePromise?: Promise, + activationRadius?: number, ) { super( Scene, @@ -46,23 +50,13 @@ export class RemotePlayer extends Character { //set data this.userId = userId; + this.activationRadius = activationRadius ?? 96; this.visitCardUrl = visitCardUrl; this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => { this.isActionsMenuInitialized = value ? true : false; }); - this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => { - if (event.downElement.nodeName === "CANVAS") { - if (this.isActionsMenuInitialized) { - actionsMenuStore.clear(); - return; - } - actionsMenuStore.initialize(this.playerName); - for (const action of this.getActionsMenuActions()) { - actionsMenuStore.addAction(action.actionName, action.callback); - } - } - }); + this.bindEventHandlers(); } public updatePosition(position: PointInterface): void { @@ -77,12 +71,27 @@ export class RemotePlayer extends Character { } } + public activate(): void { + this.toggleActionsMenu(); + } + public destroy(): void { this.actionsMenuStoreUnsubscriber(); actionsMenuStore.clear(); super.destroy(); } + private toggleActionsMenu(): void { + if (this.isActionsMenuInitialized) { + actionsMenuStore.clear(); + return; + } + actionsMenuStore.initialize(this.playerName); + for (const action of this.getActionsMenuActions()) { + actionsMenuStore.addAction(action.actionName, action.callback); + } + } + private getActionsMenuActions(): { actionName: string, callback: Function }[] { return [ { @@ -112,4 +121,12 @@ export class RemotePlayer extends Character { }, ]; } + + private bindEventHandlers(): void { + this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => { + if (event.downElement.nodeName === "CANVAS") { + this.toggleActionsMenu(); + } + }); + } } diff --git a/front/src/Phaser/Game/ActivatableInterface.ts b/front/src/Phaser/Game/ActivatableInterface.ts new file mode 100644 index 00000000..31a00e76 --- /dev/null +++ b/front/src/Phaser/Game/ActivatableInterface.ts @@ -0,0 +1,6 @@ + +export interface ActivatableInterface { + readonly activationRadius: number; + activate: () => void; + getPosition: () => { x: number, y: number }; +} diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 0c69d19a..2b82946f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -12,7 +12,7 @@ import { UserInputManager } from "../UserInput/UserInputManager"; import { gameManager } from "./GameManager"; import { touchScreenManager } from "../../Touch/TouchScreenManager"; import { PinchManager } from "../UserInput/PinchManager"; -import { waScaleManager, WaScaleManagerEvent } from "../Services/WaScaleManager"; +import { waScaleManager } from "../Services/WaScaleManager"; import { EmoteManager } from "./EmoteManager"; import { soundManager } from "./SoundManager"; import { SharedVariablesManager } from "./SharedVariablesManager"; @@ -89,9 +89,10 @@ import { deepCopy } from "deep-copy-ts"; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import { MapStore } from "../../Stores/Utils/MapStore"; import { followUsersColorStore } from "../../Stores/FollowStore"; -import Camera = Phaser.Cameras.Scene2D.Camera; import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler"; import { locale } from "../../i18n/i18n-svelte"; +import type { ActivatableInterface } from './ActivatableInterface'; +import { MathUtils } from '../../Utils/MathUtils'; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -190,6 +191,7 @@ export class GameScene extends DirtyScene { private gameMap!: GameMap; private actionableItems: Map = new Map(); // The item that can be selected by pressing the space key. + private nearestActivatableObject?: ActivatableInterface; private outlinedItem: ActionableItem | null = null; public userInputManager!: UserInputManager; private isReconnecting: boolean | undefined = undefined; @@ -806,12 +808,10 @@ export class GameScene extends DirtyScene { this.simplePeer = new SimplePeer(this.connection); userMessageManager.setReceiveBanListener(this.bannedUser.bind(this)); - //listen event to share position of user - this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)); - this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this)); - this.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => { - this.gameMap.setPosition(event.x, event.y); - }); + this.CurrentPlayer.on( + hasMovedEventName, + (event: HasPlayerMovedEvent) => { this.handleCurrentPlayerHasMovedEvent(event); }, + ); // Set up variables manager this.sharedVariablesManager = new SharedVariablesManager( @@ -1676,7 +1676,30 @@ ${escapedMessage} } } - createCollisionWithPlayer() { + private handleCurrentPlayerHasMovedEvent(event: HasPlayerMovedEvent): void { + //listen event to share position of user + this.pushPlayerPosition(event); + this.nearestActivatableObject = this.getNearestActivatableObject(); + this.outlineItem(event); + this.gameMap.setPosition(event.x, event.y); + } + + private getNearestActivatableObject(): ActivatableInterface | undefined { + let shortestDistance: number = Infinity; + let closestObject: ActivatableInterface | undefined = undefined; + const currentPlayerPos = this.CurrentPlayer.getPosition(); + + for (const object of [...Array.from(this.MapPlayersByKey.values()), ...this.actionableItems.values()]) { + const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition()); + if (object.activationRadius > distance && shortestDistance > distance) { + shortestDistance = distance; + closestObject = object; + } + } + return closestObject; + } + + private createCollisionWithPlayer() { //add collision layer for (const phaserLayer of this.gameMap.phaserLayers) { this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => { @@ -1695,7 +1718,7 @@ ${escapedMessage} } } - createCurrentPlayer() { + private createCurrentPlayer() { //TODO create animation moving between exit and start const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers); try { @@ -1737,7 +1760,7 @@ ${escapedMessage} this.createCollisionWithPlayer(); } - pushPlayerPosition(event: HasPlayerMovedEvent) { + private pushPlayerPosition(event: HasPlayerMovedEvent) { if (this.lastMoveEventSent === event) { return; } @@ -1823,7 +1846,7 @@ ${escapedMessage} * @param time * @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. */ - update(time: number, delta: number): void { + public update(time: number, delta: number): void { this.dirty = false; this.currentTick = time; this.CurrentPlayer.moveUser(delta, this.userInputManager.getEventListForGameTick()); @@ -1965,7 +1988,7 @@ ${escapedMessage} this.playersPositionInterpolator.removePlayer(userId); } - public updatePlayerPosition(message: MessageUserMovedInterface): void { + private updatePlayerPosition(message: MessageUserMovedInterface): void { this.pendingEvents.enqueue({ type: "UserMovedEvent", event: message, @@ -1995,7 +2018,7 @@ ${escapedMessage} this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement); } - public shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { + private shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { this.pendingEvents.enqueue({ type: "GroupCreatedUpdatedEvent", event: groupPositionMessage, @@ -2094,7 +2117,7 @@ ${escapedMessage} biggestAvailableAreaStore.recompute(); } - public startJitsi(roomName: string, jwt?: string): void { + private startJitsi(roomName: string, jwt?: string): void { const allProps = this.gameMap.getCurrentProperties(); const jitsiConfig = this.safeParseJSONstring( allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined, @@ -2120,7 +2143,7 @@ ${escapedMessage} }); } - public stopJitsi(): void { + private stopJitsi(): void { const silent = this.gameMap.getCurrentProperties().get(GameMapProperties.SILENT); this.connection?.setSilent(!!silent); jitsiFactory.stop(); diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts index 44b633ed..1b0e47bb 100644 --- a/front/src/Phaser/Items/ActionableItem.ts +++ b/front/src/Phaser/Items/ActionableItem.ts @@ -5,10 +5,11 @@ import Sprite = Phaser.GameObjects.Sprite; import type { GameScene } from "../Game/GameScene"; import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js"; +import type { ActivatableInterface } from '../Game/ActivatableInterface'; type EventCallback = (state: unknown, parameters: unknown) => void; -export class ActionableItem { +export class ActionableItem implements ActivatableInterface{ private readonly activationRadiusSquared: number; private isSelectable: boolean = false; private callbacks: Map> = new Map>(); @@ -17,7 +18,7 @@ export class ActionableItem { private id: number, private sprite: Sprite, private eventHandler: GameScene, - private activationRadius: number, + public readonly activationRadius: number, private onActivateCallback: (item: ActionableItem) => void ) { this.activationRadiusSquared = activationRadius * activationRadius; @@ -40,6 +41,10 @@ export class ActionableItem { } } + public getPosition(): { x: number, y: number } { + return { x: this.sprite.x, y: this.sprite.y }; + } + /** * Show the outline of the sprite. */ @@ -70,9 +75,6 @@ export class ActionableItem { return this.sprite.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined; } - /** - * Triggered when the "space" key is pressed and the object is in range of being activated. - */ public activate(): void { this.onActivateCallback(this); }