Now, when a user moves, only his/her position is sent back to the other users. The position of all users is not sent each time.
The messages sent to the browser are now:
- the list of all users as a return to the join_room event (you can send responses to events in socket.io)
- a "join_room" event sent when a new user joins the room
- a "user_moved" event when a user moved
- a "user_left" event when a user left the room
The GameScene tracks all these events and reacts accordingly.
Also, I made a number of refactoring in the classes and removed the GameSceneInterface that was useless (it was implemented by the LogincScene for no reason at all)
import {Group} from "_Model/Group";
import {UserInterface} from "_Model/UserInterface";
import {SetPlayerDetailsMessage} from "_Model/Websocket/SetPlayerDetailsMessage";
+import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
+import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved";
enum SockerIoEvent {
CONNECTION = "connection",
DISCONNECT = "disconnect",
- JOIN_ROOM = "join-room",
- USER_POSITION = "user-position",
+ JOIN_ROOM = "join-room", // bi-directional
+ USER_POSITION = "user-position", // bi-directional
+ USER_MOVED = "user-moved", // From server to client
+ USER_LEFT = "user-left", // From server to client
WEBRTC_SIGNAL = "webrtc-signal",
WEBRTC_OFFER = "webrtc-offer",
WEBRTC_START = "webrtc-start",
x: user x position on map
y: user y position on map
*/
- socket.on(SockerIoEvent.JOIN_ROOM, (roomId: any): void => {
+ socket.on(SockerIoEvent.JOIN_ROOM, (roomId: any, answerFn): void => {
try {
if (typeof(roomId) !== 'string') {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Expected roomId as a string.'});
this.leaveRoom(Client);
//join new previous room
- this.joinRoom(Client, roomId);
+ let world = this.joinRoom(Client, roomId);
//add function to refresh position user in real time.
- this.refreshUserPosition(Client);
+ //this.refreshUserPosition(Client);
- let messageUserPosition = new MessageUserPosition(Client.id, Client.name, Client.character,new Point(0, 0, 'none'));
+ let messageUserJoined = new MessageUserJoined(Client.id, Client.name, Client.character);
- socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserPosition);
+ socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserJoined);
+
+ // The answer shall contain the list of all users of the room with their positions:
+ let listOfUsers = Array.from(world.getUsers(), ([key, user]) => {
+ let player = this.searchClientByIdOrFail(user.id);
+ return new MessageUserPosition(user.id, player.name, player.character, player.position);
+ });
+ answerFn(listOfUsers);
} catch (e) {
console.error('An error occurred on "join_room" event');
console.error(e);
Client.position = position;
//refresh position of all user in all rooms in real time
- this.refreshUserPosition(Client);
+ //this.refreshUserPosition(Client);
+
+ // update position in the world
+ let world = this.Worlds.get(Client.roomId);
+ if (!world) {
+ console.error("Could not find world with id '", Client.roomId, "'");
+ return;
+ }
+ world.updatePosition(Client, Client.position);
+
+ socket.to(Client.roomId).emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.id, Client.position));
} catch (e) {
console.error('An error occurred on "user_position" event');
console.error(e);
socket.on(SockerIoEvent.DISCONNECT, () => {
try {
let Client = (socket as ExSocketInterface);
- //this.sendDisconnectedEvent(Client);
- //refresh position of all user in all rooms in real time (to remove the disconnected user)
- this.refreshUserPosition(Client);
+ if (Client.roomId) {
+ socket.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, socket.id);
+ }
//leave room
this.leaveRoom(Client);
}
leaveRoom(Client : ExSocketInterface){
- //lease previous room and world
+ // leave previous room and world
if(Client.roomId){
+ Client.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, Client.id);
+
//user leave previous world
let world : World|undefined = this.Worlds.get(Client.roomId);
if(world){
world.leave(Client);
- //this.Worlds.set(Client.roomId, world);
}
//user leave previous room
Client.leave(Client.roomId);
}
}
- joinRoom(Client : ExSocketInterface, roomId: string){
+ private joinRoom(Client : ExSocketInterface, roomId: string): World {
//join user in room
Client.join(roomId);
Client.roomId = roomId;
Client.position = new Point(-1000, -1000);
//check and create new world for a room
- if(!this.Worlds.get(roomId)){
- let world = new World((user1: string, group: Group) => {
+ let world = this.Worlds.get(roomId)
+ if(world === undefined){
+ world = new World((user1: string, group: Group) => {
this.connectedUser(user1, group);
}, (user1: string, group: Group) => {
this.disConnectedUser(user1, group);
this.Worlds.set(roomId, world);
}
- let world : World|undefined = this.Worlds.get(roomId);
-
- if(world) {
- // Dispatch groups position to newly connected user
- world.getGroups().forEach((group: Group) => {
- Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
- position: group.getPosition(),
- groupId: group.getId()
- });
+ // Dispatch groups position to newly connected user
+ world.getGroups().forEach((group: Group) => {
+ Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
+ position: group.getPosition(),
+ groupId: group.getId()
});
- //join world
- world.join(Client, Client.position);
- this.Worlds.set(roomId, world);
- }
+ });
+ //join world
+ world.join(Client, Client.position);
+ return world;
}
/**
return;
}
world.updatePosition(Client, Client.position);
- this.Worlds.set(Client.roomId, world);
}
//Hydrate and manage error
--- /dev/null
+
+export class MessageUserJoined {
+ constructor(public userId: string, public name: string, public character: string) {
+ }
+}
--- /dev/null
+import {PointInterface} from "./PointInterface";
+
+export class MessageUserMoved {
+ constructor(public userId: string, public position: PointInterface) {
+ }
+}
export interface PointInterface {
- x: number;
- y: number;
- direction: string;
+ readonly x: number;
+ readonly y: number;
+ readonly direction: string;
}
return this.groups;
}
+ public getUsers(): Map<string, UserInterface> {
+ return this.users;
+ }
+
public join(socket : Identificable, userPosition: PointInterface): void {
this.users.set(socket.id, {
id: socket.id,
return;
}
- user.position.x = userPosition.x;
- user.position.y = userPosition.y;
+ user.position = userPosition;
if (typeof user.group === 'undefined') {
// If the user is not part of a group:
WEBRTC_SIGNAL = "webrtc-signal",
WEBRTC_START = "webrtc-start",
WEBRTC_JOIN_ROOM = "webrtc-join-room",
- JOIN_ROOM = "join-room",
- USER_POSITION = "user-position",
+ JOIN_ROOM = "join-room", // bi-directional
+ USER_POSITION = "user-position", // bi-directional
+ USER_MOVED = "user-moved", // From server to client
+ USER_LEFT = "user-left", // From server to client
MESSAGE_ERROR = "message-error",
WEBRTC_DISCONNECT = "webrtc-disconect",
GROUP_CREATE_UPDATE = "group-create-update",
direction : string;
}
-class Point implements PointInterface{
+export class Point implements PointInterface{
x: number;
y: number;
direction : string;
position: PointInterface;
}
+export interface MessageUserMovedInterface {
+ userId: string;
+ position: PointInterface;
+}
+
class MessageUserPosition extends Message implements MessageUserPositionInterface{
position: PointInterface;
}
}
+export interface MessageUserJoined {
+ userId: string;
+ name: string;
+ character: string;
+}
+
export interface ListMessageUserPositionInterface {
roomId: string;
listUsersPosition: Array<MessageUserPosition>;
this.errorMessage();
this.groupUpdatedOrCreated();
this.groupDeleted();
+ this.onUserJoins();
+ this.onUserMoved();
+ this.onUserLeft();
return new Promise<ConnexionInterface>((resolve, reject) => {
this.socket.emit(EventMessage.SET_PLAYER_DETAILS, {
* @param character
*/
joinARoom(roomId: string): void {
- this.socket.emit(EventMessage.JOIN_ROOM, roomId);
+ this.socket.emit(EventMessage.JOIN_ROOM, roomId, (userPositions: MessageUserPositionInterface[]) => {
+ console.log("GOT AN ANSWER FROM JOIN_ROOM");
+ this.GameManager.initUsersPosition(userPositions);
+ });
this.lastRoom = roomId;
}
});
}
+ onUserJoins(): void {
+ this.socket.on(EventMessage.JOIN_ROOM, (message: MessageUserJoined) => {
+ this.GameManager.onUserJoins(message);
+ });
+ }
+
+ onUserMoved(): void {
+ this.socket.on(EventMessage.USER_MOVED, (message: MessageUserMovedInterface) => {
+ this.GameManager.onUserMoved(message);
+ });
+ }
+
+ onUserLeft(): void {
+ this.socket.on(EventMessage.USER_LEFT, (userId: string) => {
+ this.GameManager.onUserLeft(userId);
+ });
+ }
+
private groupUpdatedOrCreated(): void {
this.socket.on(EventMessage.GROUP_CREATE_UPDATE, (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => {
//console.log('Group ', groupCreateUpdateMessage.groupId, " position :", groupCreateUpdateMessage.position.x, groupCreateUpdateMessage.position.y)
--- /dev/null
+import {PointInterface} from "../../Connexion";
+
+export interface AddPlayerInterface {
+ userId: string;
+ name: string;
+ character: string;
+ position: PointInterface;
+}
-import {GameScene, GameSceneInterface} from "./GameScene";
+import {GameScene} from "./GameScene";
import {
Connexion,
GroupCreatedUpdatedMessageInterface,
- ListMessageUserPositionInterface
+ ListMessageUserPositionInterface, MessageUserJoined, MessageUserMovedInterface, MessageUserPositionInterface, Point
} from "../../Connexion";
import {SimplePeerInterface, SimplePeer} from "../../WebRtc/SimplePeer";
import {getMapKeyByUrl} from "../Login/LogincScene";
import ScenePlugin = Phaser.Scenes.ScenePlugin;
+import {AddPlayerInterface} from "./AddPlayerInterface";
export enum StatusGameManagerEnum {
IN_PROGRESS = 1,
export class GameManager {
status: number;
private ConnexionInstance: Connexion;
- private currentGameScene: GameSceneInterface;
+ private currentGameScene: GameScene;
private playerName: string;
SimplePeer : SimplePeerInterface;
private characterUserSelected: string;
});
}
- setCurrentGameScene(gameScene: GameSceneInterface) {
+ setCurrentGameScene(gameScene: GameScene) {
this.currentGameScene = gameScene;
}
this.ConnexionInstance.joinARoom(sceneKey);
}
+ onUserJoins(message: MessageUserJoined): void {
+ let userMessage: AddPlayerInterface = {
+ userId: message.userId,
+ character: message.character,
+ name: message.name,
+ position: new Point(-1000, -1000)
+ }
+ this.currentGameScene.addPlayer(userMessage);
+ }
+
+ onUserMoved(message: MessageUserMovedInterface): void {
+ this.currentGameScene.updatePlayerPosition(message);
+ }
+
+ onUserLeft(userId: string): void {
+ this.currentGameScene.removePlayer(userId);
+ }
+
/**
* Share position in game
* @param ListMessageUserPosition
+ * @deprecated
*/
shareUserPosition(ListMessageUserPosition: ListMessageUserPositionInterface): void {
if (this.status === StatusGameManagerEnum.IN_PROGRESS) {
}
}
+ initUsersPosition(usersPosition: MessageUserPositionInterface[]): void {
+ // Shall we wait for room to be loaded?
+ /*if (this.status === StatusGameManagerEnum.IN_PROGRESS) {
+ return;
+ }*/
+ try {
+ this.currentGameScene.initUsersPosition(usersPosition)
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
/**
* Share group position in game
*/
import {GameManager, gameManager, HasMovedEvent, MapObject, StatusGameManagerEnum} from "./GameManager";
-import {GroupCreatedUpdatedMessageInterface, MessageUserPositionInterface} from "../../Connexion";
+import {
+ GroupCreatedUpdatedMessageInterface,
+ MessageUserJoined,
+ MessageUserMovedInterface,
+ MessageUserPositionInterface
+} from "../../Connexion";
import {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player";
import { DEBUG_MODE, RESOLUTION, ROOM, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
import {ITiledMap, ITiledMapLayer, ITiledTileSet} from "../Map/ITiledMap";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
-import CreateSceneFromObjectConfig = Phaser.Types.Scenes.CreateSceneFromObjectConfig;
+import {AddPlayerInterface} from "./AddPlayerInterface";
export enum Textures {
Player = "male1"
}
-export interface GameSceneInterface extends Phaser.Scene {
- Map: Phaser.Tilemaps.Tilemap;
- createCurrentPlayer() : void;
- shareUserPosition(UsersPosition : Array<MessageUserPositionInterface>): void;
- shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface): void;
- updateOrCreateMapPlayer(UsersPosition : Array<MessageUserPositionInterface>): void;
- deleteGroup(groupId: string): void;
-}
-export class GameScene extends Phaser.Scene implements GameSceneInterface, CreateSceneFromObjectConfig{
+export class GameScene extends Phaser.Scene {
GameManager : GameManager;
Terrains : Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer: CurrentGamerInterface;
MapPlayers : Phaser.Physics.Arcade.Group;
+ MapPlayersByKey : Map<string, GamerInterface> = new Map<string, GamerInterface>();
Map: Phaser.Tilemaps.Tilemap;
Layers : Array<Phaser.Tilemaps.StaticTilemapLayer>;
Objects : Array<Phaser.Physics.Arcade.Sprite>;
/**
* Share position in scene
* @param UsersPosition
+ * @deprecated
*/
shareUserPosition(UsersPosition : Array<MessageUserPositionInterface>): void {
this.updateOrCreateMapPlayer(UsersPosition);
if(!player){
this.addPlayer(userPosition);
}else{
- player.updatePosition(userPosition);
+ player.updatePosition(userPosition.position);
}
});
});
}
+ public initUsersPosition(usersPosition: MessageUserPositionInterface[]): void {
+ if(!this.CurrentPlayer){
+ console.error('Cannot initiate users list because map is not loaded yet')
+ return;
+ }
+
+ let currentPlayerId = this.GameManager.getPlayerId();
+
+ // clean map
+ this.MapPlayersByKey.forEach((player: GamerInterface) => {
+ player.destroy();
+ this.MapPlayers.remove(player);
+ });
+ this.MapPlayersByKey = new Map<string, GamerInterface>();
+
+ // load map
+ usersPosition.forEach((userPosition : MessageUserPositionInterface) => {
+ if(userPosition.userId === currentPlayerId){
+ return;
+ }
+ this.addPlayer(userPosition);
+ console.log("Added player ", userPosition)
+ });
+
+ console.log("Initialized with ", usersPosition);
+ }
+
private findPlayerInMap(UserId : string) : GamerInterface | null{
- let player = this.MapPlayers.getChildren().find((player: Player) => UserId === player.userId);
+ return this.MapPlayersByKey.get(UserId);
+ /*let player = this.MapPlayers.getChildren().find((player: Player) => UserId === player.userId);
if(!player){
return null;
}
- return (player as GamerInterface);
+ return (player as GamerInterface);*/
}
/**
* Create new player
- * @param MessageUserPosition
*/
- addPlayer(MessageUserPosition : MessageUserPositionInterface) : void{
+ public addPlayer(addPlayerData : AddPlayerInterface) : void{
//initialise player
let player = new Player(
- MessageUserPosition.userId,
+ addPlayerData.userId,
this,
- MessageUserPosition.position.x,
- MessageUserPosition.position.y,
- MessageUserPosition.name,
- MessageUserPosition.character
+ addPlayerData.position.x,
+ addPlayerData.position.y,
+ addPlayerData.name,
+ addPlayerData.character
);
player.initAnimation();
this.MapPlayers.add(player);
- player.updatePosition(MessageUserPosition);
+ this.MapPlayersByKey.set(player.userId, player);
+ player.updatePosition(addPlayerData.position);
//init collision
/*this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => {
});*/
}
+ public removePlayer(userId: string) {
+ let player = this.MapPlayersByKey.get(userId);
+ if (player === undefined) {
+ console.error('Cannot find user with id ', userId);
+ }
+ player.destroy();
+ this.MapPlayers.remove(player);
+ this.MapPlayersByKey.delete(userId);
+ }
+
+ updatePlayerPosition(message: MessageUserMovedInterface): void {
+ let player : GamerInterface | undefined = this.MapPlayersByKey.get(message.userId);
+ if (player === undefined) {
+ throw new Error('Cannot find player with ID "' + message.userId +'"');
+ }
+ player.updatePosition(message.position);
+ }
+
shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
let groupId = groupPositionMessage.groupId;
import {TextField} from "../Components/TextField";
import {TextInput} from "../Components/TextInput";
import {ClickButton} from "../Components/ClickButton";
-import {GameScene, GameSceneInterface} from "../Game/GameScene";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
-import {GroupCreatedUpdatedMessageInterface, MessageUserPositionInterface} from "../../Connexion";
+import {GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserPositionInterface} from "../../Connexion";
export function getMapKeyByUrl(mapUrlStart: string){
// FIXME: the key should be computed from the full URL of the map.
mainFont = "main_font"
}
-export class LogincScene extends Phaser.Scene implements GameSceneInterface {
+export class LogincScene extends Phaser.Scene {
private nameInput: TextInput;
private textField: TextField;
private playButton: ClickButton;
this.selectedPlayer = this.players[0];
this.selectedPlayer.play(PLAYER_RESOURCES[0].name);
}
-
- shareUserPosition(UsersPosition: import("../../Connexion").MessageUserPositionInterface[]): void {
- throw new Error("Method not implemented.");
- }
-
- deleteGroup(groupId: string): void {
- throw new Error("Method not implemented.");
- }
-
- shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface): void {
- throw new Error("Method not implemented.");
- }
-
- updateOrCreateMapPlayer(UsersPosition: Array<MessageUserPositionInterface>): void {
- throw new Error("Method not implemented.");
- }
}
import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animation";
-import {GameSceneInterface, Textures} from "../Game/GameScene";
-import {MessageUserPositionInterface} from "../../Connexion";
+import {GameScene, Textures} from "../Game/GameScene";
+import {MessageUserPositionInterface, PointInterface} from "../../Connexion";
import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {PlayableCaracter} from "../Entity/PlayableCaracter";
export interface GamerInterface extends PlayableCaracter{
userId : string;
initAnimation() : void;
- updatePosition(MessageUserPosition : MessageUserPositionInterface) : void;
+ updatePosition(position: PointInterface): void;
say(text : string) : void;
}
constructor(
userId: string,
- Scene: GameSceneInterface,
+ Scene: GameScene,
x: number,
y: number,
name: string,
}
//todo: put this method into the NonPlayer class instead
- updatePosition(MessageUserPosition: MessageUserPositionInterface) {
- playAnimation(this, MessageUserPosition.position.direction);
- this.setX(MessageUserPosition.position.x);
- this.setY(MessageUserPosition.position.y);
- this.setDepth(MessageUserPosition.position.y);
+ updatePosition(position: PointInterface): void {
+ playAnimation(this, position.direction);
+ this.setX(position.x);
+ this.setY(position.y);
+ this.setDepth(position.y);
}
}
import Map = Phaser.Structs.Map;
-import {GameSceneInterface} from "../Game/GameScene";
+import {GameScene} from "../Game/GameScene";
interface UserInputManagerDatum {
keyCode: number;
set(event: UserInputEvent, value: boolean): boolean {
return this.KeysCode[event] = true;
}
-}
+}
//this class is responsible for catching user inputs and listing all active user actions at every game tick events.
export class UserInputManager {
{keyCode: Phaser.Input.Keyboard.KeyCodes.Q, event: UserInputEvent.MoveLeft, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.S, event: UserInputEvent.MoveDown, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.D, event: UserInputEvent.MoveRight, keyInstance: null},
-
+
{keyCode: Phaser.Input.Keyboard.KeyCodes.UP, event: UserInputEvent.MoveUp, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.LEFT, event: UserInputEvent.MoveLeft, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.DOWN, event: UserInputEvent.MoveDown, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.RIGHT, event: UserInputEvent.MoveRight, keyInstance: null},
-
+
{keyCode: Phaser.Input.Keyboard.KeyCodes.SHIFT, event: UserInputEvent.SpeedUp, keyInstance: null},
-
+
{keyCode: Phaser.Input.Keyboard.KeyCodes.E, event: UserInputEvent.Interact, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.F, event: UserInputEvent.Shout, keyInstance: null},
];
-
- constructor(Scene : GameSceneInterface) {
+
+ constructor(Scene : GameScene) {
this.KeysCode.forEach(d => {
d.keyInstance = Scene.input.keyboard.addKey(d.keyCode);
});
});
return eventsMap;
}
-}
\ No newline at end of file
+}