Merge branch 'develop' of github.com:thecodingmachine/workadventure into metadataScriptingApi
This commit is contained in:
commit
2a2cea2cd5
@ -6,6 +6,7 @@
|
|||||||
- Enabled outlines on actionable item again (they were disabled when migrating to Phaser 3.50) #1218
|
- 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
|
- 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
|
- 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 :
|
- New scripting API features :
|
||||||
- Use `WA.room.showLayer(): void` to show a layer
|
- Use `WA.room.showLayer(): void` to show a layer
|
||||||
- Use `WA.room.hideLayer(): void` to hide a layer
|
- Use `WA.room.hideLayer(): void` to hide a layer
|
||||||
|
90
docs/maps/wa-maps.md
Normal file
90
docs/maps/wa-maps.md
Normal file
@ -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.
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<figure class="figure">
|
||||||
|
<img src="https://workadventu.re/img/docs/tiled_screenshot_1.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
||||||
|
<figcaption class="figure-caption">"floorLayer" is compulsory</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## 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.
|
@ -54,7 +54,8 @@
|
|||||||
"standardized-audio-context": "^25.2.4"
|
"standardized-audio-context": "^25.2.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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",
|
"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",
|
"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",
|
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||||
|
@ -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 { 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";
|
||||||
@ -10,12 +10,16 @@ export type PropertyChangeCallback = (newValue: string | number | boolean | unde
|
|||||||
* It is used to handle layer properties.
|
* It is used to handle layer properties.
|
||||||
*/
|
*/
|
||||||
export class GameMap {
|
export class GameMap {
|
||||||
private key: number|undefined;
|
private key: number | undefined;
|
||||||
private lastProperties = new Map<string, string|boolean|number>();
|
private lastProperties = new Map<string, string | boolean | number>();
|
||||||
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
|
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
|
||||||
|
|
||||||
|
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapLayerProperty> } = {}
|
||||||
public readonly flatLayers: ITiledMapLayer[];
|
public readonly flatLayers: ITiledMapLayer[];
|
||||||
public readonly phaserLayers: TilemapLayer[] = [];
|
public readonly phaserLayers: TilemapLayer[] = [];
|
||||||
|
|
||||||
|
public exitUrls: Array<string> = []
|
||||||
|
|
||||||
public constructor(private map: ITiledMap, phaserMap: Phaser.Tilemaps.Tilemap, terrains: Array<Phaser.Tilemaps.Tileset>) {
|
public constructor(private map: ITiledMap, phaserMap: Phaser.Tilemaps.Tilemap, terrains: Array<Phaser.Tilemaps.Tileset>) {
|
||||||
this.flatLayers = flattenGroupLayersMap(map);
|
this.flatLayers = flattenGroupLayersMap(map);
|
||||||
let depth = -2;
|
let depth = -2;
|
||||||
@ -27,7 +31,21 @@ export class GameMap {
|
|||||||
depth = DEPTH_OVERLAY_INDEX;
|
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)
|
* Sets the position of the current player (in pixels)
|
||||||
@ -63,21 +81,27 @@ export class GameMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCurrentProperties(): Map<string, string|boolean|number> {
|
public getCurrentProperties(): Map<string, string | boolean | number> {
|
||||||
return this.lastProperties;
|
return this.lastProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getProperties(key: number): Map<string, string|boolean|number> {
|
private getProperties(key: number): Map<string, string | boolean | number> {
|
||||||
const properties = new Map<string, string|boolean|number>();
|
const properties = new Map<string, string | boolean | number>();
|
||||||
|
|
||||||
for (const layer of this.flatLayers) {
|
for (const layer of this.flatLayers) {
|
||||||
if (layer.type !== 'tilelayer') {
|
if (layer.type !== 'tilelayer') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tileIndex: number | undefined = undefined;
|
||||||
|
if (layer.data) {
|
||||||
const tiles = layer.data as number[];
|
const tiles = layer.data as number[];
|
||||||
if (tiles[key] == 0) {
|
if (tiles[key] == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
tileIndex = tiles[key]
|
||||||
|
}
|
||||||
|
|
||||||
// There is a tile in this layer, let's embed the properties
|
// There is a tile in this layer, let's embed the properties
|
||||||
if (layer.properties !== undefined) {
|
if (layer.properties !== undefined) {
|
||||||
for (const layerProperty of layer.properties) {
|
for (const layerProperty of layer.properties) {
|
||||||
@ -87,6 +111,16 @@ export class GameMap {
|
|||||||
properties.set(layerProperty.name, layerProperty.value);
|
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;
|
return properties;
|
||||||
|
@ -441,6 +441,10 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.gameMap.exitUrls.forEach(exitUrl => {
|
||||||
|
this.loadNextGame(exitUrl)
|
||||||
|
})
|
||||||
|
|
||||||
this.initStartXAndStartY();
|
this.initStartXAndStartY();
|
||||||
|
|
||||||
//add entities
|
//add entities
|
||||||
|
@ -170,7 +170,7 @@ export interface ITiledTileSet {
|
|||||||
tilewidth: number;
|
tilewidth: number;
|
||||||
transparentcolor: string;
|
transparentcolor: string;
|
||||||
terrains: ITiledMapTerrain[];
|
terrains: ITiledMapTerrain[];
|
||||||
tiles: {[key: string]: { terrain: number[] }};
|
tiles?: Array<ITile>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refers to external tileset file (should be JSON)
|
* Refers to external tileset file (should be JSON)
|
||||||
@ -178,6 +178,13 @@ export interface ITiledTileSet {
|
|||||||
source: string;
|
source: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITile {
|
||||||
|
id: number,
|
||||||
|
type?: string
|
||||||
|
|
||||||
|
properties?: Array<ITiledMapLayerProperty>
|
||||||
|
}
|
||||||
|
|
||||||
export interface ITiledMapTerrain {
|
export interface ITiledMapTerrain {
|
||||||
name: string;
|
name: string;
|
||||||
tile: number;
|
tile: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user