detecting zoe enter and leave events

This commit is contained in:
Hanusiak Piotr 2021-12-01 14:48:14 +01:00
parent 591467f1e3
commit eecf831ca5
7 changed files with 526 additions and 347 deletions

View file

@ -5,7 +5,6 @@ import {
ITiledMap,
ITiledMapLayer,
ITiledMapObject,
ITiledMapObjectLayer,
} from "@workadventure/tiled-map-type-guard/dist";
import { User } from "_Model/User";
import { variablesRepository } from "./Repository/VariablesRepository";

View file

@ -1,8 +1,9 @@
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty } from "../Map/ITiledMap";
import type { ITiledMap, ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer, ITiledMapProperty } from "../Map/ITiledMap";
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
import { GameMapProperties } from "./GameMapProperties";
import { MathUtils } from '../../Utils/MathUtils';
export type PropertyChangeCallback = (
newValue: string | number | boolean | undefined,
@ -15,23 +16,46 @@ export type layerChangeCallback = (
allLayersOnNewPosition: Array<ITiledMapLayer>
) => void;
export type zoneChangeCallback = (
zonesChangedByAction: Array<ITiledMapObject>,
allZonesOnNewPosition: Array<ITiledMapObject>
) => void;
/**
* A wrapper around a ITiledMap interface to provide additional capabilities.
* It is used to handle layer properties.
*/
export class GameMap {
// oldKey is the index of the previous tile.
/**
* oldKey is the index of the previous tile.
*/
private oldKey: number | undefined;
// key is the index of the current tile.
/**
* key is the index of the current tile.
*/
private key: number | undefined;
/**
* oldPosition is the previous position of the player.
*/
private oldPosition: { x: number, y: number } | undefined;
/**
* position is the current position of the player.
*/
private position: { x: number, y: number } | undefined;
private lastProperties = new Map<string, string | boolean | number>();
private propertiesChangeCallbacks = new Map<string, Array<PropertyChangeCallback>>();
private enterLayerCallbacks = Array<layerChangeCallback>();
private leaveLayerCallbacks = Array<layerChangeCallback>();
private enterZoneCallbacks = Array<zoneChangeCallback>();
private leaveZoneCallbacks = Array<zoneChangeCallback>();
private tileNameMap = new Map<string, number>();
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {};
public readonly flatLayers: ITiledMapLayer[];
public readonly tiledObjects: ITiledMapObject[];
public readonly phaserLayers: TilemapLayer[] = [];
public exitUrls: Array<string> = [];
@ -44,6 +68,8 @@ export class GameMap {
terrains: Array<Phaser.Tilemaps.Tileset>
) {
this.flatLayers = flattenGroupLayersMap(map);
this.tiledObjects = this.getObjectsFromLayers(this.flatLayers);
let depth = -2;
for (const layer of this.flatLayers) {
if (layer.type === "tilelayer") {
@ -88,6 +114,9 @@ export class GameMap {
* This will trigger events if properties are changing.
*/
public setPosition(x: number, y: number) {
this.oldPosition = this.position;
this.position = { x, y };
this.oldKey = this.key;
const xMap = Math.floor(x / this.map.tilewidth);
@ -102,6 +131,7 @@ export class GameMap {
this.triggerAllProperties();
this.triggerLayersChange();
this.triggerZonesChange();
}
private triggerAllProperties(): void {
@ -126,7 +156,7 @@ export class GameMap {
}
}
private triggerLayersChange() {
private triggerLayersChange(): void {
const layersByOldKey = this.oldKey ? this.getLayersByKey(this.oldKey) : [];
const layersByNewKey = this.key ? this.getLayersByKey(this.key) : [];
@ -155,6 +185,54 @@ export class GameMap {
}
}
/**
* We user Tiled Objects with type "zone" as zones with defined x, y, width and height for easier event triggering.
*/
private triggerZonesChange(): void {
const zones = this.tiledObjects.filter(object => object.type === "zone");
// P.H. NOTE: We could also get all of the zones and add properties of occupied tiles to them, so we could later on check collision by using tileKeys
const zonesByOldPosition = this.oldPosition ?
zones.filter((zone) => {
if (!this.oldPosition) {
return false;
}
return MathUtils.isOverlappingWithRectangle(this.oldPosition, zone);
}) : [];
const zonesByNewPosition = this.position ?
zones.filter((zone) => {
if (!this.position) {
return false;
}
return MathUtils.isOverlappingWithRectangle(this.position, zone);
}) : [];
const enterZones = new Set(zonesByNewPosition);
const leaveZones = new Set(zonesByOldPosition);
enterZones.forEach((zone) => {
if (leaveZones.has(zone)) {
leaveZones.delete(zone);
enterZones.delete(zone);
}
});
if (enterZones.size > 0) {
const zonesArray = Array.from(enterZones);
for (const callback of this.enterZoneCallbacks) {
callback(zonesArray, zonesByNewPosition);
}
}
if (leaveZones.size > 0) {
const zonesArray = Array.from(leaveZones);
for (const callback of this.leaveZoneCallbacks) {
callback(zonesArray, zonesByNewPosition);
}
}
}
public getCurrentProperties(): Map<string, string | boolean | number> {
return this.lastProperties;
}
@ -251,6 +329,20 @@ export class GameMap {
this.leaveLayerCallbacks.push(callback);
}
/**
* Registers a callback called when the user moves inside another zone.
*/
public onEnterZone(callback: zoneChangeCallback) {
this.enterZoneCallbacks.push(callback);
}
/**
* Registers a callback called when the user moves outside another zone.
*/
public onLeaveZone(callback: zoneChangeCallback) {
this.leaveZoneCallbacks.push(callback);
}
public findLayer(layerName: string): ITiledMapLayer | undefined {
return this.flatLayers.find((layer) => layer.name === layerName);
}
@ -362,4 +454,22 @@ export class GameMap {
this.trigger(oldPropName, oldPropValue, undefined, emptyProps);
}
}
private getObjectsFromLayers(layers: ITiledMapLayer[]): ITiledMapObject[] {
const objects: ITiledMapObject[] = [];
const objectLayers = layers.filter(layer => layer.type === "objectgroup");
for (const objectLayer of objectLayers) {
if (this.isOfTypeITiledMapObjectLayer(objectLayer)) {
objects.push(...objectLayer.objects);
}
}
return objects;
}
// NOTE: Simple typeguard for Objects Layer.
private isOfTypeITiledMapObjectLayer(obj: ITiledMapLayer): obj is ITiledMapObjectLayer {
return (obj as ITiledMapObjectLayer).objects !== undefined;
}
}

View file

@ -778,6 +778,28 @@ export class GameScene extends DirtyScene {
iframeListener.sendLeaveLayerEvent(layer.name);
});
});
this.gameMap.onEnterZone((zones) => {
console.log('enter zones');
console.log(zones);
// zones.forEach((zone) => {
// iframeListener.sendEnterLayerEvent(zone.name);
// });
});
this.gameMap.onLeaveZone((zones) => {
console.log('leave zones');
console.log(zones);
// zones.forEach((zone) => {
// iframeListener.sendEnterLayerEvent(zone.name);
// });
});
// this.gameMap.onLeaveLayer((layers) => {
// layers.forEach((layer) => {
// iframeListener.sendLeaveLayerEvent(layer.name);
// });
// });
});
}

View file

@ -1,7 +1,7 @@
import type { RoomConnection } from "../../Connexion/RoomConnection";
import { iframeListener } from "../../Api/IframeListener";
import type { GameMap } from "./GameMap";
import type { ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer } from "../Map/ITiledMap";
import type { ITiledMapLayer, ITiledMapObject } from "../Map/ITiledMap";
import { GameMapProperties } from "./GameMapProperties";
interface Variable {

View file

@ -1,5 +1,4 @@
import * as Phaser from "phaser";
import { Scene } from "phaser";
import Sprite = Phaser.GameObjects.Sprite;
import type { ITiledMapObject } from "../../Map/ITiledMap";
import type { ItemFactoryInterface } from "../ItemFactoryInterface";

View file

@ -0,0 +1,27 @@
export class MathUtils {
/**
*
* @param p Position to check.
* @param r Rectangle to check the overlap against.
* @returns true is overlapping
*/
public static isOverlappingWithRectangle(
p: { x: number, y: number},
r: { x: number, y: number, width: number, height: number},
): boolean {
return (this.isBetween(p.x, r.x, r.x + r.width) && this.isBetween(p.y, r.y, r.y + r.height));
}
/**
*
* @param value Value to check
* @param min inclusive min value
* @param max inclusive max value
* @returns true if value is in <min, max>
*/
public static isBetween(value: number, min: number, max: number): boolean {
return (value >= min) && (value <= max);
}
}

View file

@ -148,6 +148,28 @@
"width":128,
"x":512,
"y":0
},
{
"height":128,
"id":9,
"name":"chillZone",
"properties":[
{
"name":"display_name",
"type":"string",
"value":"Chilling Room"
},
{
"name":"focusable",
"type":"bool",
"value":true
}],
"rotation":0,
"type":"zone",
"visible":true,
"width":192,
"x":32,
"y":96
}],
"opacity":1,
"type":"objectgroup",
@ -192,7 +214,7 @@
"y":0
}],
"nextlayerid":39,
"nextobjectid":9,
"nextobjectid":11,
"orientation":"orthogonal",
"properties":[
{
@ -200,33 +222,33 @@
"type":"string",
"value":"Credits: Valdo Romao https:\/\/www.linkedin.com\/in\/valdo-romao\/ \nLicense: CC-BY-SA 3.0 (http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/)"
},
{
"name":"mapDescription",
"type":"string",
"value":"A perfect virtual office to get started with WorkAdventure!"
},
{
"name":"mapImage",
"type":"string",
"value":"map.png"
},
{
"name":"mapLink",
"type":"string",
"value":"https:\/\/thecodingmachine.github.io\/workadventure-map-starter-kit\/map.json"
},
{
"name":"mapName",
"type":"string",
"value":"Starter kit"
},
{
"name":"mapDescription",
"type":"string",
"value":"A perfect virtual office to get started with WorkAdventure!"
},
{
"name":"mapImage",
"type":"string",
"value":"map.png"
},
{
"name":"mapLink",
"type":"string",
"value":"https:\/\/thecodingmachine.github.io\/workadventure-map-starter-kit\/map.json"
},
{
"name":"mapName",
"type":"string",
"value":"Starter kit"
},
{
"name":"script",
"type":"string",
"value":"..\/dist\/script.js"
}],
"renderorder":"right-down",
"tiledversion":"1.7.0",
"tiledversion":"1.7.2",
"tileheight":32,
"tilesets":[
{