Adding the display of a circle around the group

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.
This commit is contained in:
David Négrier 2020-05-08 00:35:36 +02:00
parent 05fbcad252
commit 02e6b50b16
7 changed files with 153 additions and 12 deletions

View File

@ -9,6 +9,7 @@ import {ExtRooms, RefreshUserPositionFunction} from "../Model/Websocket/ExtRoom"
import {ExtRoomsInterface} from "../Model/Websocket/ExtRoomsInterface"; import {ExtRoomsInterface} from "../Model/Websocket/ExtRoomsInterface";
import {World} from "../Model/World"; import {World} from "../Model/World";
import {Group} from "_Model/Group"; import {Group} from "_Model/Group";
import {UserInterface} from "_Model/UserInterface";
enum SockerIoEvent { enum SockerIoEvent {
CONNECTION = "connection", CONNECTION = "connection",
@ -20,6 +21,8 @@ enum SockerIoEvent {
WEBRTC_START = "webrtc-start", WEBRTC_START = "webrtc-start",
WEBRTC_DISCONNECT = "webrtc-disconect", WEBRTC_DISCONNECT = "webrtc-disconect",
MESSAGE_ERROR = "message-error", MESSAGE_ERROR = "message-error",
GROUP_CREATE_UPDATE = "group-create-update",
GROUP_DELETE = "group-delete",
} }
export class IoSocketController { export class IoSocketController {
@ -51,7 +54,38 @@ export class IoSocketController {
this.connectedUser(user1, group); this.connectedUser(user1, group);
}, (user1: string, group: Group) => { }, (user1: string, group: Group) => {
this.disConnectedUser(user1, 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() { 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 * @param userId
*/ */
searchClientById(userId: string): ExSocketInterface | null { searchClientById(userId: string): ExSocketInterface | null {
@ -286,7 +320,7 @@ export class IoSocketController {
this.joinWebRtcRoom(Client, group.getId()); this.joinWebRtcRoom(Client, group.getId());
} }
//connected user //disconnect user
disConnectedUser(userId: string, group: Group) { disConnectedUser(userId: string, group: Group) {
let Client = this.searchClientById(userId); let Client = this.searchClientById(userId);
if (!Client) { if (!Client) {

View File

@ -9,6 +9,10 @@ import {PositionInterface} from "_Model/PositionInterface";
export type ConnectCallback = (user: string, group: Group) => void; export type ConnectCallback = (user: string, group: Group) => void;
export type DisconnectCallback = (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 { export class World {
private minDistance: number; private minDistance: number;
private groupRadius: number; private groupRadius: number;
@ -19,11 +23,15 @@ export class World {
private connectCallback: ConnectCallback; private connectCallback: ConnectCallback;
private disconnectCallback: DisconnectCallback; private disconnectCallback: DisconnectCallback;
private groupUpdatedCallback: GroupUpdatedCallback;
private groupDeletedCallback: GroupDeletedCallback;
constructor(connectCallback: ConnectCallback, constructor(connectCallback: ConnectCallback,
disconnectCallback: DisconnectCallback, disconnectCallback: DisconnectCallback,
minDistance: number, minDistance: number,
groupRadius: number) groupRadius: number,
groupUpdatedCallback: GroupUpdatedCallback,
groupDeletedCallback: GroupDeletedCallback)
{ {
this.users = new Map<string, UserInterface>(); this.users = new Map<string, UserInterface>();
this.groups = []; this.groups = [];
@ -31,6 +39,8 @@ export class World {
this.disconnectCallback = disconnectCallback; this.disconnectCallback = disconnectCallback;
this.minDistance = minDistance; this.minDistance = minDistance;
this.groupRadius = groupRadius; this.groupRadius = groupRadius;
this.groupUpdatedCallback = groupUpdatedCallback;
this.groupDeletedCallback = groupDeletedCallback;
} }
public join(userPosition: MessageUserPosition): void { public join(userPosition: MessageUserPosition): void {
@ -86,6 +96,11 @@ export class World {
this.leaveGroup(user); 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); group.leave(user);
if (group.isEmpty()) { if (group.isEmpty()) {
this.groupDeletedCallback(group.getId(), user);
group.destroy(); group.destroy();
const index = this.groups.indexOf(group, 0); const index = this.groups.indexOf(group, 0);
if (index === -1) { if (index === -1) {
throw new Error("Could not find group"); throw new Error("Could not find group");
} }
this.groups.splice(index, 1); this.groups.splice(index, 1);
} else {
this.groupUpdatedCallback(group);
} }
} }

View 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({ world.join(new MessageUserPosition({
userId: "foo", 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({ world.join(new MessageUserPosition({
userId: "foo", userId: "foo",
@ -107,7 +107,7 @@ describe("World", () => {
disconnectCallNumber++; disconnectCallNumber++;
} }
let world = new World(connect, disconnect, 160, 160); let world = new World(connect, disconnect, 160, 160, () => {}, () => {});
world.join(new MessageUserPosition({ world.join(new MessageUserPosition({
userId: "foo", userId: "foo",

View File

@ -11,7 +11,9 @@ enum EventMessage{
JOIN_ROOM = "join-room", JOIN_ROOM = "join-room",
USER_POSITION = "user-position", USER_POSITION = "user-position",
MESSAGE_ERROR = "message-error", MESSAGE_ERROR = "message-error",
WEBRTC_DISCONNECT = "webrtc-disconect" WEBRTC_DISCONNECT = "webrtc-disconect",
GROUP_CREATE_UPDATE = "group-create-update",
GROUP_DELETE = "group-delete",
} }
class Message { 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 { export interface ConnexionInterface {
socket: any; socket: any;
token: string; token: string;
@ -184,6 +196,9 @@ export class Connexion implements ConnexionInterface {
this.errorMessage(); this.errorMessage();
this.groupUpdatedOrCreated();
this.groupDeleted();
return this; return this;
}) })
.catch((err) => { .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) { sendWebrtcSignal(signal: any, roomId: string, userId? : string, receiverId? : string) {
return this.socket.emit(EventMessage.WEBRTC_SIGNAL, JSON.stringify({ return this.socket.emit(EventMessage.WEBRTC_SIGNAL, JSON.stringify({
userId: userId ? userId : this.userId, userId: userId ? userId : this.userId,
@ -280,4 +308,4 @@ export class Connexion implements ConnexionInterface {
disconnectMessage(callback: Function): void { disconnectMessage(callback: Function): void {
this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback); this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback);
} }
} }

View File

@ -1,6 +1,11 @@
import {GameScene} from "./GameScene"; import {GameScene} from "./GameScene";
import {ROOM} from "../../Enum/EnvironmentVariable" 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 {SimplePeerInterface, SimplePeer} from "../../WebRtc/SimplePeer";
import {LogincScene} from "../Login/LogincScene"; 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 { getPlayerName(): string {
return this.playerName; return this.playerName;
} }

View File

@ -1,11 +1,13 @@
import {GameManager, gameManager, HasMovedEvent, StatusGameManagerEnum} from "./GameManager"; 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 {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player";
import {DEBUG_MODE, RESOLUTION, ROOM, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import {DEBUG_MODE, RESOLUTION, ROOM, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
import Tile = Phaser.Tilemaps.Tile; import Tile = Phaser.Tilemaps.Tile;
import {ITiledMap, ITiledTileSet} from "../Map/ITiledMap"; import {ITiledMap, ITiledTileSet} from "../Map/ITiledMap";
import {cypressAsserter} from "../../Cypress/CypressAsserter"; import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter"; import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
import Circle = Phaser.Geom.Circle;
import Graphics = Phaser.GameObjects.Graphics;
export const GameSceneName = "GameScene"; export const GameSceneName = "GameScene";
export enum Textures { export enum Textures {
@ -27,9 +29,12 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
Layers : Array<Phaser.Tilemaps.StaticTilemapLayer>; Layers : Array<Phaser.Tilemaps.StaticTilemapLayer>;
Objects : Array<Phaser.Physics.Arcade.Sprite>; Objects : Array<Phaser.Physics.Arcade.Sprite>;
map: ITiledMap; map: ITiledMap;
groups: Map<string, Circle>
startX = 704;// 22 case startX = 704;// 22 case
startY = 32; // 1 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() { constructor() {
super({ super({
@ -37,6 +42,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
}); });
this.GameManager = gameManager; this.GameManager = gameManager;
this.Terrains = []; this.Terrains = [];
this.groups = new Map<string, Circle>();
} }
//hook preload scene //hook preload scene
@ -115,6 +121,8 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
//initialise camera //initialise camera
this.initCamera(); this.initCamera();
this.graphics = this.add.graphics();
} }
//todo: in a dedicated class/function? //todo: in a dedicated class/function?
@ -199,6 +207,13 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{
*/ */
update(time: number, delta: number) : void { update(time: number, delta: number) : void {
this.CurrentPlayer.moveUser(delta); 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 ? "); 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);
}
} }

View 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)) { if (direction !== PlayerAnimationNames.None && (!Player.anims.currentAnim || Player.anims.currentAnim.key !== direction)) {
Player.anims.play(direction); Player.anims.play(direction);
} else if (direction === PlayerAnimationNames.None && Player.anims.currentAnim) { } else if (direction === PlayerAnimationNames.None && Player.anims.currentAnim) {
//Player.anims.currentAnim.destroy();
Player.anims.stop(); Player.anims.stop();
} }
} }