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, ITiledMap,
ITiledMapLayer, ITiledMapLayer,
ITiledMapObject, ITiledMapObject,
ITiledMapObjectLayer,
} from "@workadventure/tiled-map-type-guard/dist"; } from "@workadventure/tiled-map-type-guard/dist";
import { User } from "_Model/User"; import { User } from "_Model/User";
import { variablesRepository } from "./Repository/VariablesRepository"; 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 { flattenGroupLayersMap } from "../Map/LayersFlattener";
import TilemapLayer = Phaser.Tilemaps.TilemapLayer; import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
import { GameMapProperties } from "./GameMapProperties"; import { GameMapProperties } from "./GameMapProperties";
import { MathUtils } from '../../Utils/MathUtils';
export type PropertyChangeCallback = ( export type PropertyChangeCallback = (
newValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined,
@ -15,23 +16,46 @@ export type layerChangeCallback = (
allLayersOnNewPosition: Array<ITiledMapLayer> allLayersOnNewPosition: Array<ITiledMapLayer>
) => void; ) => void;
export type zoneChangeCallback = (
zonesChangedByAction: Array<ITiledMapObject>,
allZonesOnNewPosition: Array<ITiledMapObject>
) => void;
/** /**
* A wrapper around a ITiledMap interface to provide additional capabilities. * A wrapper around a ITiledMap interface to provide additional capabilities.
* It is used to handle layer properties. * It is used to handle layer properties.
*/ */
export class GameMap { export class GameMap {
// oldKey is the index of the previous tile. /**
* oldKey is the index of the previous tile.
*/
private oldKey: number | undefined; private oldKey: number | undefined;
// key is the index of the current tile. /**
* key is the index of the current tile.
*/
private key: number | undefined; 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 lastProperties = new Map<string, string | boolean | number>();
private propertiesChangeCallbacks = new Map<string, Array<PropertyChangeCallback>>(); private propertiesChangeCallbacks = new Map<string, Array<PropertyChangeCallback>>();
private enterLayerCallbacks = Array<layerChangeCallback>(); private enterLayerCallbacks = Array<layerChangeCallback>();
private leaveLayerCallbacks = Array<layerChangeCallback>(); private leaveLayerCallbacks = Array<layerChangeCallback>();
private enterZoneCallbacks = Array<zoneChangeCallback>();
private leaveZoneCallbacks = Array<zoneChangeCallback>();
private tileNameMap = new Map<string, number>(); private tileNameMap = new Map<string, number>();
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {}; private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {};
public readonly flatLayers: ITiledMapLayer[]; public readonly flatLayers: ITiledMapLayer[];
public readonly tiledObjects: ITiledMapObject[];
public readonly phaserLayers: TilemapLayer[] = []; public readonly phaserLayers: TilemapLayer[] = [];
public exitUrls: Array<string> = []; public exitUrls: Array<string> = [];
@ -44,6 +68,8 @@ export class GameMap {
terrains: Array<Phaser.Tilemaps.Tileset> terrains: Array<Phaser.Tilemaps.Tileset>
) { ) {
this.flatLayers = flattenGroupLayersMap(map); this.flatLayers = flattenGroupLayersMap(map);
this.tiledObjects = this.getObjectsFromLayers(this.flatLayers);
let depth = -2; let depth = -2;
for (const layer of this.flatLayers) { for (const layer of this.flatLayers) {
if (layer.type === "tilelayer") { if (layer.type === "tilelayer") {
@ -88,6 +114,9 @@ export class GameMap {
* This will trigger events if properties are changing. * This will trigger events if properties are changing.
*/ */
public setPosition(x: number, y: number) { public setPosition(x: number, y: number) {
this.oldPosition = this.position;
this.position = { x, y };
this.oldKey = this.key; this.oldKey = this.key;
const xMap = Math.floor(x / this.map.tilewidth); const xMap = Math.floor(x / this.map.tilewidth);
@ -102,6 +131,7 @@ export class GameMap {
this.triggerAllProperties(); this.triggerAllProperties();
this.triggerLayersChange(); this.triggerLayersChange();
this.triggerZonesChange();
} }
private triggerAllProperties(): void { private triggerAllProperties(): void {
@ -126,7 +156,7 @@ export class GameMap {
} }
} }
private triggerLayersChange() { private triggerLayersChange(): void {
const layersByOldKey = this.oldKey ? this.getLayersByKey(this.oldKey) : []; const layersByOldKey = this.oldKey ? this.getLayersByKey(this.oldKey) : [];
const layersByNewKey = this.key ? this.getLayersByKey(this.key) : []; 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> { public getCurrentProperties(): Map<string, string | boolean | number> {
return this.lastProperties; return this.lastProperties;
} }
@ -251,6 +329,20 @@ export class GameMap {
this.leaveLayerCallbacks.push(callback); 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 { public findLayer(layerName: string): ITiledMapLayer | undefined {
return this.flatLayers.find((layer) => layer.name === layerName); return this.flatLayers.find((layer) => layer.name === layerName);
} }
@ -362,4 +454,22 @@ export class GameMap {
this.trigger(oldPropName, oldPropValue, undefined, emptyProps); 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); 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 type { RoomConnection } from "../../Connexion/RoomConnection";
import { iframeListener } from "../../Api/IframeListener"; import { iframeListener } from "../../Api/IframeListener";
import type { GameMap } from "./GameMap"; import type { GameMap } from "./GameMap";
import type { ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer } from "../Map/ITiledMap"; import type { ITiledMapLayer, ITiledMapObject } from "../Map/ITiledMap";
import { GameMapProperties } from "./GameMapProperties"; import { GameMapProperties } from "./GameMapProperties";
interface Variable { interface Variable {

View File

@ -1,5 +1,4 @@
import * as Phaser from "phaser"; import * as Phaser from "phaser";
import { Scene } from "phaser";
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
import type { ITiledMapObject } from "../../Map/ITiledMap"; import type { ITiledMapObject } from "../../Map/ITiledMap";
import type { ItemFactoryInterface } from "../ItemFactoryInterface"; 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, "width":128,
"x":512, "x":512,
"y":0 "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, "opacity":1,
"type":"objectgroup", "type":"objectgroup",
@ -192,7 +214,7 @@
"y":0 "y":0
}], }],
"nextlayerid":39, "nextlayerid":39,
"nextobjectid":9, "nextobjectid":11,
"orientation":"orthogonal", "orientation":"orthogonal",
"properties":[ "properties":[
{ {
@ -226,7 +248,7 @@
"value":"..\/dist\/script.js" "value":"..\/dist\/script.js"
}], }],
"renderorder":"right-down", "renderorder":"right-down",
"tiledversion":"1.7.0", "tiledversion":"1.7.2",
"tileheight":32, "tileheight":32,
"tilesets":[ "tilesets":[
{ {