Adding the display of a circle around the group
authorDavid Négrier <d.negrier@thecodingmachine.com>
Thu, 7 May 2020 22:35:36 +0000 (00:35 +0200)
committerDavid Négrier <d.negrier@thecodingmachine.com>
Thu, 7 May 2020 22:35:36 +0000 (00:35 +0200)
This PR adds the display of a circle around groups. This is useful to view where you need to go to speak to someone but also to debug.

Note: implementation is suboptimal, relying on a "graphics" object that is known to be slow. In the future, we need to use a circle as a sprite instead.

back/src/Controller/IoSocketController.ts
back/src/Model/World.ts
back/tests/WorldTest.ts
front/src/Connexion.ts
front/src/Phaser/Game/GameManager.ts
front/src/Phaser/Game/GameScene.ts
front/src/Phaser/Player/Animation.ts

index 64012349b5b3d996a625d1605bf9400b77f8dd4d..d865fc7c3241a4dc894f01f462ff17f8973cecde 100644 (file)
@@ -9,6 +9,7 @@ import {ExtRooms, RefreshUserPositionFunction} from "../Model/Websocket/ExtRoom"
 import {ExtRoomsInterface} from "../Model/Websocket/ExtRoomsInterface";
 import {World} from "../Model/World";
 import {Group} from "_Model/Group";
+import {UserInterface} from "_Model/UserInterface";
 
 enum SockerIoEvent {
     CONNECTION = "connection",
@@ -20,6 +21,8 @@ enum SockerIoEvent {
     WEBRTC_START = "webrtc-start",
     WEBRTC_DISCONNECT = "webrtc-disconect",
     MESSAGE_ERROR = "message-error",
+    GROUP_CREATE_UPDATE = "group-create-update",
+    GROUP_DELETE = "group-delete",
 }
 
 export class IoSocketController {
@@ -51,7 +54,38 @@ export class IoSocketController {
             this.connectedUser(user1, group);
         }, (user1: string, group: Group) => {
             this.disConnectedUser(user1, group);
-        }, MINIMUM_DISTANCE, GROUP_RADIUS);
+        }, MINIMUM_DISTANCE, GROUP_RADIUS, (group: Group) => {
+            this.sendUpdateGroupEvent(group);
+        }, (groupUuid: string, lastUser: UserInterface) => {
+            this.sendDeleteGroupEvent(groupUuid, lastUser);
+        });
+    }
+
+    private sendUpdateGroupEvent(group: Group): void {
+        // Let's get the room of the group. To do this, let's get anyone in the group and find its room.
+        // Note: this is suboptimal
+        let userId = group.getUsers()[0].id;
+        let client: ExSocketInterface|null = this.searchClientById(userId);
+        if (client === null) {
+            return;
+        }
+        let roomId = client.roomId;
+        this.Io.in(roomId).emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
+            position: group.getPosition(),
+            groupId: group.getId()
+        });
+    }
+
+    private sendDeleteGroupEvent(uuid: string, lastUser: UserInterface): void {
+        // Let's get the room of the group. To do this, let's get anyone in the group and find its room.
+        // Note: this is suboptimal
+        let userId = lastUser.id;
+        let client: ExSocketInterface|null = this.searchClientById(userId);
+        if (client === null) {
+            return;
+        }
+        let roomId = client.roomId;
+        this.Io.in(roomId).emit(SockerIoEvent.GROUP_DELETE, uuid);
     }
 
     ioConnection() {
@@ -149,7 +183,7 @@ export class IoSocketController {
     }
 
     /**
-     *
+     * TODO: each call to this method is suboptimal. It means that instead of passing an ID, we should pass a client object.
      * @param userId
      */
     searchClientById(userId: string): ExSocketInterface | null {
@@ -286,7 +320,7 @@ export class IoSocketController {
         this.joinWebRtcRoom(Client, group.getId());
     }
 
-    //connected user
+    //disconnect user
     disConnectedUser(userId: string, group: Group) {
         let Client = this.searchClientById(userId);
         if (!Client) {
index 19eb8ed890e30458ef5b9917dae3526023ba1dea..180740b2d2d6f38b770825f29372fc18b4149d0f 100644 (file)
@@ -9,6 +9,10 @@ import {PositionInterface} from "_Model/PositionInterface";
 export type ConnectCallback = (user: string, group: Group) => void;
 export type DisconnectCallback = (user: string, group: Group) => void;
 
+// callback called when a group is created or moved or changes users
+export type GroupUpdatedCallback = (group: Group) => void;
+export type GroupDeletedCallback = (uuid: string, lastUser: UserInterface) => void;
+
 export class World {
     private minDistance: number;
     private groupRadius: number;
@@ -19,11 +23,15 @@ export class World {
 
     private connectCallback: ConnectCallback;
     private disconnectCallback: DisconnectCallback;
+    private groupUpdatedCallback: GroupUpdatedCallback;
+    private groupDeletedCallback: GroupDeletedCallback;
 
     constructor(connectCallback: ConnectCallback,
                 disconnectCallback: DisconnectCallback,
                 minDistance: number,
-                groupRadius: number)
+                groupRadius: number,
+                groupUpdatedCallback: GroupUpdatedCallback,
+                groupDeletedCallback: GroupDeletedCallback)
     {
         this.users = new Map<string, UserInterface>();
         this.groups = [];
@@ -31,6 +39,8 @@ export class World {
         this.disconnectCallback = disconnectCallback;
         this.minDistance = minDistance;
         this.groupRadius = groupRadius;
+        this.groupUpdatedCallback = groupUpdatedCallback;
+        this.groupDeletedCallback = groupDeletedCallback;
     }
 
     public join(userPosition: MessageUserPosition): void {
@@ -86,6 +96,11 @@ export class World {
                 this.leaveGroup(user);
             }
         }
+
+        // At the very end, if the user is part of a group, let's call the callback to update group position
+        if (typeof user.group !== 'undefined') {
+            this.groupUpdatedCallback(user.group);
+        }
     }
 
     /**
@@ -101,12 +116,15 @@ export class World {
         group.leave(user);
 
         if (group.isEmpty()) {
+            this.groupDeletedCallback(group.getId(), user);
             group.destroy();
             const index = this.groups.indexOf(group, 0);
             if (index === -1) {
                 throw new Error("Could not find group");
             }
             this.groups.splice(index, 1);
+        } else {
+            this.groupUpdatedCallback(group);
         }
     }
 
index d14aaeb093a8654620bfc1106ade1b6d840f1164..57f0f3f4dd1cb5c2a54f070b19673185042f27e7 100644 (file)
@@ -15,7 +15,7 @@ describe("World", () => {
 
         }
 
-        let world = new World(connect, disconnect, 160, 160);
+        let world = new World(connect, disconnect, 160, 160, () => {}, () => {});
 
         world.join(new MessageUserPosition({
             userId: "foo",
@@ -62,7 +62,7 @@ describe("World", () => {
 
         }
 
-        let world = new World(connect, disconnect, 160, 160);
+        let world = new World(connect, disconnect, 160, 160, () => {}, () => {});
 
         world.join(new MessageUserPosition({
             userId: "foo",
@@ -107,7 +107,7 @@ describe("World", () => {
             disconnectCallNumber++;
         }
 
-        let world = new World(connect, disconnect, 160, 160);
+        let world = new World(connect, disconnect, 160, 160, () => {}, () => {});
 
         world.join(new MessageUserPosition({
             userId: "foo",
index 4dfddee1d7e77e9c98ea74e715f9dad51f45b520..d4accde2b0bc7c9e27c73fb8b9e5976ab35efb52 100644 (file)
@@ -11,7 +11,9 @@ enum EventMessage{
     JOIN_ROOM = "join-room",
     USER_POSITION = "user-position",
     MESSAGE_ERROR = "message-error",
-    WEBRTC_DISCONNECT = "webrtc-disconect"
+    WEBRTC_DISCONNECT = "webrtc-disconect",
+    GROUP_CREATE_UPDATE = "group-create-update",
+    GROUP_DELETE = "group-delete",
 }
 
 class Message {
@@ -122,6 +124,16 @@ class ListMessageUserPosition {
     }
 }
 
+export interface PositionInterface {
+    x: number,
+    y: number
+}
+
+export interface GroupCreatedUpdatedMessageInterface {
+    position: PositionInterface,
+    groupId: string
+}
+
 export interface ConnexionInterface {
     socket: any;
     token: string;
@@ -184,6 +196,9 @@ export class Connexion implements ConnexionInterface {
 
                 this.errorMessage();
 
+                this.groupUpdatedOrCreated();
+                this.groupDeleted();
+
                 return this;
             })
             .catch((err) => {
@@ -254,6 +269,19 @@ export class Connexion implements ConnexionInterface {
         });
     }
 
+    private groupUpdatedOrCreated(): void {
+        this.socket.on(EventMessage.GROUP_CREATE_UPDATE, (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => {
+            //console.log('Group ', groupCreateUpdateMessage.groupId, " position :", groupCreateUpdateMessage.position.x, groupCreateUpdateMessage.position.y)
+            this.GameManager.shareGroupPosition(groupCreateUpdateMessage);
+        })
+    }
+
+    private groupDeleted(): void {
+        this.socket.on(EventMessage.GROUP_DELETE, (groupId: string) => {
+            this.GameManager.deleteGroup(groupId);
+        })
+    }
+
     sendWebrtcSignal(signal: any, roomId: string, userId? : string, receiverId? : string) {
         return this.socket.emit(EventMessage.WEBRTC_SIGNAL, JSON.stringify({
             userId: userId ? userId : this.userId,
@@ -280,4 +308,4 @@ export class Connexion implements ConnexionInterface {
     disconnectMessage(callback: Function): void {
         this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback);
     }
-}
\ No newline at end of file
+}
index 4cc95c2a0661079b16441d9aaff573b08965c817..eeed7f5fd60f835c9c4b0b6578f6a40e5a30b247 100644 (file)
@@ -1,6 +1,11 @@
 import {GameScene} from "./GameScene";
 import {ROOM} from "../../Enum/EnvironmentVariable"
-import {Connexion, ConnexionInterface, ListMessageUserPositionInterface} from "../../Connexion";
+import {
+    Connexion,
+    ConnexionInterface,
+    GroupCreatedUpdatedMessageInterface,
+    ListMessageUserPositionInterface
+} from "../../Connexion";
 import {SimplePeerInterface, SimplePeer} from "../../WebRtc/SimplePeer";
 import {LogincScene} from "../Login/LogincScene";
 
@@ -66,6 +71,31 @@ export class GameManager {
         }
     }
 
+    /**
+     * Share group position in game
+     */
+    shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface): void {
+        if (this.status === StatusGameManagerEnum.IN_PROGRESS) {
+            return;
+        }
+        try {
+            this.currentGameScene.shareGroupPosition(groupPositionMessage)
+        } catch (e) {
+            console.error(e);
+        }
+    }
+
+    deleteGroup(groupId: string): void {
+        if (this.status === StatusGameManagerEnum.IN_PROGRESS) {
+            return;
+        }
+        try {
+            this.currentGameScene.deleteGroup(groupId)
+        } catch (e) {
+            console.error(e);
+        }
+    }
+
     getPlayerName(): string {
         return this.playerName;
     }
index f449f0766117a4916d25388e3cf797c7b3571a49..179c6802232ab47aa049e1d60c5e274beda048a6 100644 (file)
@@ -1,11 +1,13 @@
 import {GameManager, gameManager, HasMovedEvent, StatusGameManagerEnum} from "./GameManager";
-import {MessageUserPositionInterface} from "../../Connexion";
+import {GroupCreatedUpdatedMessageInterface, MessageUserPositionInterface} from "../../Connexion";
 import {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player";
 import {DEBUG_MODE, RESOLUTION, ROOM, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
 import Tile = Phaser.Tilemaps.Tile;
 import {ITiledMap, ITiledTileSet} from "../Map/ITiledMap";
 import {cypressAsserter} from "../../Cypress/CypressAsserter";
 import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
+import Circle = Phaser.Geom.Circle;
+import Graphics = Phaser.GameObjects.Graphics;
 
 export const GameSceneName = "GameScene";
 export enum Textures {
@@ -27,9 +29,12 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
     Layers : Array<Phaser.Tilemaps.StaticTilemapLayer>;
     Objects : Array<Phaser.Physics.Arcade.Sprite>;
     map: ITiledMap;
+    groups: Map<string, Circle>
     startX = 704;// 22 case
     startY = 32; // 1 case
 
+    // Note: graphics object is costly to generate. We should find another way (maybe sprite based way to draw circles)
+    graphics: Graphics;
 
     constructor() {
         super({
@@ -37,6 +42,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
         });
         this.GameManager = gameManager;
         this.Terrains = [];
+        this.groups = new Map<string, Circle>();
     }
 
     //hook preload scene
@@ -115,6 +121,8 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
 
         //initialise camera
         this.initCamera();
+
+        this.graphics = this.add.graphics();
     }
 
     //todo: in a dedicated class/function?
@@ -199,6 +207,13 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
      */
     update(time: number, delta: number) : void {
         this.CurrentPlayer.moveUser(delta);
+
+        // Also, let's redraw the circle (can be costly, we need to change this!)
+        this.graphics.clear();
+        this.graphics.lineStyle(1, 0x00ff00, 0.4);
+        this.groups.forEach((circle: Circle) => {
+            this.graphics.strokeCircleShape(circle);
+        })
     }
 
     /**
@@ -272,4 +287,21 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
             CurrentPlayer.say("Hello, how are you ? ");
         });
     }
+
+    shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
+        let groupId = groupPositionMessage.groupId;
+
+        if (this.groups.has(groupId)) {
+            this.groups.get(groupId).setPosition(groupPositionMessage.position.x, groupPositionMessage.position.y);
+        } else {
+            //console.log('Adding group ', groupId, ' to the scene');
+            // TODO: circle radius should not be hard stored
+            this.groups.set(groupId, new Circle(groupPositionMessage.position.x, groupPositionMessage.position.y, 48));
+        }
+    }
+
+    deleteGroup(groupId: string): void {
+        //console.log('Deleting group ', groupId);
+        this.groups.delete(groupId);
+    }
 }
index 1c555ad9024b689164df0bd67c74a8ecacfeb30d..332c862d7b43fae4859c84ff25bcc940362e9bc9 100644 (file)
@@ -53,7 +53,6 @@ export const playAnimation = (Player : Phaser.GameObjects.Sprite, direction : st
     if (direction !== PlayerAnimationNames.None && (!Player.anims.currentAnim || Player.anims.currentAnim.key !== direction)) {
         Player.anims.play(direction);
     } else if (direction === PlayerAnimationNames.None && Player.anims.currentAnim) {
-        //Player.anims.currentAnim.destroy();
         Player.anims.stop();
     }
 }