From 74dda8ab69715733c347c007c2668729afac1728 Mon Sep 17 00:00:00 2001 From: jonny Date: Sat, 19 Jun 2021 15:17:28 +0200 Subject: [PATCH 1/7] allow properties on tiles # Conflicts: # front/src/Phaser/Game/GameMap.ts # front/src/Phaser/Map/ITiledMap.ts --- front/src/Phaser/Game/GameMap.ts | 48 ++++++++++++++++++++++++++++++ front/src/Phaser/Game/GameScene.ts | 5 ++++ front/src/Phaser/Map/ITiledMap.ts | 9 +++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 7c93a702..dd567585 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -1,5 +1,8 @@ import type {ITiledMap, ITiledMapLayer} from "../Map/ITiledMap"; import {LayersIterator} from "../Map/LayersIterator"; +import { CustomVector, Vector2 } from '../../utility/vector'; +import type { ITiledMap, ITiledMapLayerProperty } from "../Map/ITiledMap"; +import { LayersIterator } from "../Map/LayersIterator"; export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map) => void; @@ -11,12 +14,37 @@ export class GameMap { private key: number|undefined; private lastProperties = new Map(); private callbacks = new Map>(); + + /** + * tileset.firstgid => (Map>) + */ + private tileSetPropertyMap = new Map>>() public readonly layersIterator: LayersIterator; + public exitUrls: Array = [] + public constructor(private map: ITiledMap) { this.layersIterator = new LayersIterator(map); + + for (const tileset of map.tilesets) { + if (!this.tileSetPropertyMap.has(tileset.firstgid)) { + this.tileSetPropertyMap.set(tileset.firstgid, new Map()) + } + tileset?.tiles?.forEach(tile => { + if (tile.properties) { + this.tileSetPropertyMap.get(tileset.firstgid)?.set(tile.id, tile.properties) + tile.properties.forEach(prop => { + if (prop.name == "exitUrl" && typeof prop.value == "string") { + this.exitUrls.push(prop.value); + } + }) + } + }) + } } + + /** * Sets the position of the current player (in pixels) * This will trigger events if properties are changing. @@ -59,12 +87,15 @@ export class GameMap { const properties = new Map(); for (const layer of this.layersIterator) { + + let tileIndex: number | undefined = undefined; if (layer.type !== 'tilelayer') { continue; } const tiles = layer.data as number[]; if (tiles[key] == 0) { continue; + tileIndex = tiles[key] } // There is a tile in this layer, let's embed the properties if (layer.properties !== undefined) { @@ -75,6 +106,23 @@ export class GameMap { properties.set(layerProperty.name, layerProperty.value); } } + + if (tileIndex) { + const tileset = this.map.tilesets.find(tileset => tileset.firstgid + tileset.tilecount > (tileIndex as number)) + if (tileset) { + const tileProperties = this.tileSetPropertyMap.get(tileset?.firstgid)?.get(tileIndex - tileset.firstgid) + if (tileProperties) { + for (const property of tileProperties) { + if (property.value) { + properties.set(property.name, property.value) + } else if (properties.has(property.name)) { + properties.delete(property.name) + } + } + } + } + + } } return properties; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index b5876d5a..7d01de46 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -447,6 +447,11 @@ export class GameScene extends DirtyScene implements CenterListener { } } } + + this.gameMap.exitUrls.forEach(exitUrl => { + this.loadNextGame(exitUrl) + }) + if (depth === -2) { throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at. This layer cannot be contained in a group.'); } diff --git a/front/src/Phaser/Map/ITiledMap.ts b/front/src/Phaser/Map/ITiledMap.ts index c4828911..91a29397 100644 --- a/front/src/Phaser/Map/ITiledMap.ts +++ b/front/src/Phaser/Map/ITiledMap.ts @@ -167,7 +167,7 @@ export interface ITiledTileSet { tilewidth: number; transparentcolor: string; terrains: ITiledMapTerrain[]; - tiles: {[key: string]: { terrain: number[] }}; + tiles?: Array; /** * Refers to external tileset file (should be JSON) @@ -175,6 +175,13 @@ export interface ITiledTileSet { source: string; } +export interface ITile { + id: number, + type?: string + + properties?: Array +} + export interface ITiledMapTerrain { name: string; tile: number; From 27ccdf165c5c851e106d37ec4c713427f15131e4 Mon Sep 17 00:00:00 2001 From: jonny Date: Sat, 19 Jun 2021 15:24:27 +0200 Subject: [PATCH 2/7] fixed merge imports --- front/src/Phaser/Game/GameMap.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index dd567585..6148830e 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -1,6 +1,3 @@ -import type {ITiledMap, ITiledMapLayer} from "../Map/ITiledMap"; -import {LayersIterator} from "../Map/LayersIterator"; -import { CustomVector, Vector2 } from '../../utility/vector'; import type { ITiledMap, ITiledMapLayerProperty } from "../Map/ITiledMap"; import { LayersIterator } from "../Map/LayersIterator"; @@ -11,8 +8,8 @@ export type PropertyChangeCallback = (newValue: string | number | boolean | unde * It is used to handle layer properties. */ export class GameMap { - private key: number|undefined; - private lastProperties = new Map(); + private key: number | undefined; + private lastProperties = new Map(); private callbacks = new Map>(); /** @@ -79,12 +76,12 @@ export class GameMap { } } - public getCurrentProperties(): Map { + public getCurrentProperties(): Map { return this.lastProperties; } - private getProperties(key: number): Map { - const properties = new Map(); + private getProperties(key: number): Map { + const properties = new Map(); for (const layer of this.layersIterator) { @@ -95,7 +92,6 @@ export class GameMap { const tiles = layer.data as number[]; if (tiles[key] == 0) { continue; - tileIndex = tiles[key] } // There is a tile in this layer, let's embed the properties if (layer.properties !== undefined) { From c1d9b2c9ed20ce0ff94a5f18d9b03a5955330020 Mon Sep 17 00:00:00 2001 From: jonny Date: Sat, 19 Jun 2021 15:41:58 +0200 Subject: [PATCH 3/7] coverted property map to object --- front/src/Phaser/Game/GameMap.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 6148830e..4fd5b832 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -12,10 +12,7 @@ export class GameMap { private lastProperties = new Map(); private callbacks = new Map>(); - /** - * tileset.firstgid => (Map>) - */ - private tileSetPropertyMap = new Map>>() + private tileSetPropertyMap: { [tilset_firstgid: number]: { [tile_id: number]: Array } } = {} public readonly layersIterator: LayersIterator; public exitUrls: Array = [] @@ -24,12 +21,12 @@ export class GameMap { this.layersIterator = new LayersIterator(map); for (const tileset of map.tilesets) { - if (!this.tileSetPropertyMap.has(tileset.firstgid)) { - this.tileSetPropertyMap.set(tileset.firstgid, new Map()) + if (!this.tileSetPropertyMap[tileset.firstgid]) { + this.tileSetPropertyMap[tileset.firstgid] = {} } tileset?.tiles?.forEach(tile => { if (tile.properties) { - this.tileSetPropertyMap.get(tileset.firstgid)?.set(tile.id, tile.properties) + this.tileSetPropertyMap[tileset.firstgid][tile.id] = tile.properties tile.properties.forEach(prop => { if (prop.name == "exitUrl" && typeof prop.value == "string") { this.exitUrls.push(prop.value); @@ -106,7 +103,7 @@ export class GameMap { if (tileIndex) { const tileset = this.map.tilesets.find(tileset => tileset.firstgid + tileset.tilecount > (tileIndex as number)) if (tileset) { - const tileProperties = this.tileSetPropertyMap.get(tileset?.firstgid)?.get(tileIndex - tileset.firstgid) + const tileProperties = this.tileSetPropertyMap[tileset?.firstgid][tileIndex - tileset.firstgid] if (tileProperties) { for (const property of tileProperties) { if (property.value) { From 92485a02cfed2eae25de45387e7334793e28e189 Mon Sep 17 00:00:00 2001 From: jonny Date: Sat, 19 Jun 2021 15:46:32 +0200 Subject: [PATCH 4/7] tileIndex setting got merged out --- front/src/Phaser/Game/GameMap.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 4fd5b832..23a4a5a1 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -81,15 +81,19 @@ export class GameMap { const properties = new Map(); for (const layer of this.layersIterator) { - - let tileIndex: number | undefined = undefined; if (layer.type !== 'tilelayer') { continue; } - const tiles = layer.data as number[]; - if (tiles[key] == 0) { - continue; + + let tileIndex: number | undefined = undefined; + if (layer.data) { + const tiles = layer.data as number[]; + if (tiles[key] == 0) { + continue; + } + tileIndex = tiles[key] } + // There is a tile in this layer, let's embed the properties if (layer.properties !== undefined) { for (const layerProperty of layer.properties) { @@ -104,15 +108,13 @@ export class GameMap { const tileset = this.map.tilesets.find(tileset => tileset.firstgid + tileset.tilecount > (tileIndex as number)) if (tileset) { const tileProperties = this.tileSetPropertyMap[tileset?.firstgid][tileIndex - tileset.firstgid] - if (tileProperties) { - for (const property of tileProperties) { - if (property.value) { - properties.set(property.name, property.value) - } else if (properties.has(property.name)) { - properties.delete(property.name) - } + tileProperties?.forEach(property => { + if (property.value) { + properties.set(property.name, property.value) + } else if (properties.has(property.name)) { + properties.delete(property.name) } - } + }) } } From 5bf943ce7771ab53140da41080d4a353c4afa7f3 Mon Sep 17 00:00:00 2001 From: jonny Date: Sun, 20 Jun 2021 19:14:04 +0200 Subject: [PATCH 5/7] converted cache to constant lookup time --- front/src/Phaser/Game/GameMap.ts | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 23a4a5a1..ffcee287 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -12,7 +12,7 @@ export class GameMap { private lastProperties = new Map(); private callbacks = new Map>(); - private tileSetPropertyMap: { [tilset_firstgid: number]: { [tile_id: number]: Array } } = {} + private tileSetPropertyMap: { [tile_index: number]: Array } = {} public readonly layersIterator: LayersIterator; public exitUrls: Array = [] @@ -21,12 +21,9 @@ export class GameMap { this.layersIterator = new LayersIterator(map); for (const tileset of map.tilesets) { - if (!this.tileSetPropertyMap[tileset.firstgid]) { - this.tileSetPropertyMap[tileset.firstgid] = {} - } tileset?.tiles?.forEach(tile => { if (tile.properties) { - this.tileSetPropertyMap[tileset.firstgid][tile.id] = tile.properties + this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties tile.properties.forEach(prop => { if (prop.name == "exitUrl" && typeof prop.value == "string") { this.exitUrls.push(prop.value); @@ -105,18 +102,13 @@ export class GameMap { } if (tileIndex) { - const tileset = this.map.tilesets.find(tileset => tileset.firstgid + tileset.tilecount > (tileIndex as number)) - if (tileset) { - const tileProperties = this.tileSetPropertyMap[tileset?.firstgid][tileIndex - tileset.firstgid] - tileProperties?.forEach(property => { - if (property.value) { - properties.set(property.name, property.value) - } else if (properties.has(property.name)) { - properties.delete(property.name) - } - }) - } - + this.tileSetPropertyMap[tileIndex]?.forEach(property => { + if (property.value) { + properties.set(property.name, property.value) + } else if (properties.has(property.name)) { + properties.delete(property.name) + } + }) } } From 7716fe4b62631548616b163db12ca46639015451 Mon Sep 17 00:00:00 2001 From: jonny Date: Sun, 20 Jun 2021 19:26:35 +0200 Subject: [PATCH 6/7] run templater on running start script --- front/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/front/package.json b/front/package.json index 3b248076..ac9061cc 100644 --- a/front/package.json +++ b/front/package.json @@ -53,13 +53,14 @@ "standardized-audio-context": "^25.2.4" }, "scripts": { - "start": "run-p serve svelte-check-watch", + "start": "run-p templater serve svelte-check-watch", + "templater": "cross-env ./templater.sh", "serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", "build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", "test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", "lint": "node_modules/.bin/eslint src/ . --ext .ts", "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts", "svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\" --watch", - "svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\"" + "svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\"" } } From af67d9b5131667f5718339424e1d4b1ef29d3b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 23 Jun 2021 10:44:55 +0200 Subject: [PATCH 7/7] Migrating WA-Maps page to this repo and documenting the fact that tiles properties can now be used. --- CHANGELOG.md | 1 + docs/maps/wa-maps.md | 90 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 docs/maps/wa-maps.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 13335737..560365f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Enabled outlines on actionable item again (they were disabled when migrating to Phaser 3.50) #1218 - Enabled outlines on player names (when the mouse hovers on a player you can interact with) #1219 - Migrated the admin console to Svelte, and redesigned the console #1211 +- Layer properties (like `exitUrl`, `silent`, etc...) can now also used in tile properties #1210 (@jonnytest1) ## Version 1.4.1 diff --git a/docs/maps/wa-maps.md b/docs/maps/wa-maps.md new file mode 100644 index 00000000..98fdce2d --- /dev/null +++ b/docs/maps/wa-maps.md @@ -0,0 +1,90 @@ +{.section-title.accent.text-primary} +# About WorkAdventure maps + +A WorkAdventure map is a map in "JSON" format generated by [Tiled](https://www.mapeditor.org/). + +## Tiles + +A map is made of "tiles" (we can also call them "sprites"). In WorkAdventure, the tiles are small images of 32x32 pixels. + +Tiles may have transparent parts. Many tiles can be stored in a single PNG file. We call this file a "tileset". + +There are many tilesets available on the internet. Some examples of websites offering awesome tiles: + +* [itch.io](https://itch.io/) +* [opengameart.org](https://opengameart.org/) +* [deviantart.com](https://www.deviantart.com/) + +Keep in mind the size of tiles and do not forget to check the license of the tileset you are using! + + +## How to design "pixel" tiles + +You can design your own tiles as well as change existing tiles, this is usually referred to as "pixeling". You can start drawing your own tiles with [Piskel](https://www.piskelapp.com/). It is easy to use and well targeted at "pixeling". If you are getting serious about pixeling, the awesome folks at the Chaos Computer Club recommend the use of the editor [Krita](https://krita.org/). There are plenty of other editors as well. + +If you are using Krita: + +* Please double check that your tiles are 32x32 pixels in size. You can enable a grid under view -> show grid and under settings -> dockers -> grid you can select the grid size. +* Use transparency if you have to model transitions between different materials. This is more flexible and saves you time by not modeling every transition. +* You can follow the Pixel-Art Workshop by blinry: [media.ccc.de/v/34C3-jugend-hackt-1016-pixel_art_workshop](https://media.ccc.de/v/34C3-jugend-hackt-1016-pixel_art_workshop) + +## WorkAdventure Map Rules + +In order to design a map that will be readable by WorkAdventure, you will have to respect some constraints. + +In particular, you will need to: + +* set a start position for the players +* configure the "floor layer" (so that WorkAdventure can correctly display characters above the floor, but under the ceiling) +* eventually, you can place exits that link to other maps + +A few things to notice: + +* your map can have as many layers as you want +* your map MUST contain a layer named "floorLayer" of type "objectgroup" that represents the layer on which characters will be drawn. Every layer above the "floorLayer" will be displayed on top of the characters. +* the tilesets in your map MUST be embedded. You cannot refer to an external typeset in a TSX file. Click the "embed tileset" button in the tileset tab to embed tileset data. +* your map MUST be exported in JSON format. You need to use a recent version of Tiled to get JSON format export (1.3+) +* WorkAdventure doesn't support object layers and will ignore them +* If you are starting from a blank map, your map MUST be orthogonal and tiles size should be 32x32. + +
+
+ +
"floorLayer" is compulsory
+
+
+ +## Building walls and "collidable" areas + +By default, the characters can traverse any tiles. If you want to prevent your characeter from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile. + +To make a tile "collidable", you should: + +1. select the relevant tileset and switch to "edit" mode: + {.document-img} + ![](https://workadventu.re/img/docs/collides-1.png) +2. right click on a tile of the tileset to select it: + {.document-img} + ![](https://workadventu.re/img/docs/collides-2.png) +3. on the left pane in the custom properties section, right click and select "Add properties": + {.document-img} + ![](https://workadventu.re/img/docs/collides-3.png) + + Please add a `collides` property. The type of the property must be **bool**. + +4. finally, check the checkbox for the `collides` property: + {.document-img} + ![](https://workadventu.re/img/docs/collides-4.png) + +Repeat for every tile that should be "collidable". + +## Adding behaviour with properties + +In the next sections, you will see how you can add behaviour on your map by adding "properties". +You can add properties for a variety of features: putting exits, opening websites, meeting rooms, silent zones, etc... + +You can add properties either on individual tiles of a tileset OR on a complete layer. + +If you put a property on a layer, it will be triggered if your Woka walks on any tile of the layer. + +The exception is the "collides" property that can only be set on tiles, but not on a complete layer.