diff --git a/CHANGELOG.md b/CHANGELOG.md index fb4f6d27..41f72510 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) - New scripting API features : - Use `WA.room.showLayer(): void` to show a layer - Use `WA.room.hideLayer(): void` to hide a layer 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. diff --git a/front/package.json b/front/package.json index 74edf9d7..8652ba83 100644 --- a/front/package.json +++ b/front/package.json @@ -54,7 +54,8 @@ "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", diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 873b6062..fba410d9 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -1,4 +1,4 @@ -import type { ITiledMap, ITiledMapLayer } from "../Map/ITiledMap"; +import type {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty} from "../Map/ITiledMap"; import { flattenGroupLayersMap } from "../Map/LayersFlattener"; import TilemapLayer = Phaser.Tilemaps.TilemapLayer; import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; @@ -10,12 +10,16 @@ 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>(); + + private tileSetPropertyMap: { [tile_index: number]: Array } = {} public readonly flatLayers: ITiledMapLayer[]; public readonly phaserLayers: TilemapLayer[] = []; + public exitUrls: Array = [] + public constructor(private map: ITiledMap, phaserMap: Phaser.Tilemaps.Tilemap, terrains: Array) { this.flatLayers = flattenGroupLayersMap(map); let depth = -2; @@ -27,8 +31,22 @@ export class GameMap { depth = DEPTH_OVERLAY_INDEX; } } + for (const tileset of map.tilesets) { + tileset?.tiles?.forEach(tile => { + if (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); + } + }) + } + }) + } } + + /** * Sets the position of the current player (in pixels) * This will trigger events if properties are changing. @@ -63,21 +81,27 @@ 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.flatLayers) { 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) { @@ -87,6 +111,16 @@ export class GameMap { properties.set(layerProperty.name, layerProperty.value); } } + + if (tileIndex) { + this.tileSetPropertyMap[tileIndex]?.forEach(property => { + 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 52d678ad..7c07f187 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -441,6 +441,10 @@ export class GameScene extends DirtyScene implements CenterListener { } } + this.gameMap.exitUrls.forEach(exitUrl => { + this.loadNextGame(exitUrl) + }) + this.initStartXAndStartY(); //add entities diff --git a/front/src/Phaser/Map/ITiledMap.ts b/front/src/Phaser/Map/ITiledMap.ts index d381e9d4..8ea9baf1 100644 --- a/front/src/Phaser/Map/ITiledMap.ts +++ b/front/src/Phaser/Map/ITiledMap.ts @@ -170,7 +170,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) @@ -178,6 +178,13 @@ export interface ITiledTileSet { source: string; } +export interface ITile { + id: number, + type?: string + + properties?: Array +} + export interface ITiledMapTerrain { name: string; tile: number;