Adding the ability to add several entry points

We can now have several start layers and choose an entry point using a # in the URL
This commit is contained in:
David Négrier 2020-06-07 13:23:32 +02:00
parent d630106ff0
commit db3ef81842
5 changed files with 1067 additions and 961 deletions

View file

@ -38,16 +38,24 @@ If you want to design your own map, you can use [Tiled](https://www.mapeditor.or
A few things to notice: A few things to notice:
- your map can have as many layers as your want - 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. - your map MUST contain a layer named "floorLayer" of type "objectgroup" that represents the layer on which characters will be drawn.
- the tilesets in your map MUST be embedded. You can refer to an external typeset in a TSX file. Click the "embed tileset" button in the tileset tab to embed tileset data. - 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+) - 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 - 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. - If you are starting from a blank map, your map MUST be orthogonal and tiles size should be 32x32.
![](doc/images/tiled_screenshot_1.png) ![](doc/images/tiled_screenshot_1.png)
In order to place an on your scene that leads to another scene: ### Defining a default entry point
In order to define a default start position, you MUST create a layer named "start" on your map.
This layer MUST contain at least one tile. The players will start on the tile of this layer.
If the layer contains many tiles selected, the players will start randomly on one of those tiles.
### Defining exits
In order to place an exit on your scene that leads to another scene:
- You must create a specific layer. When a character reaches ANY tile of that layer, it will exit the scene. - You must create a specific layer. When a character reaches ANY tile of that layer, it will exit the scene.
- In layer properties, you MUST add "exitSceneUrl" property. It represents the map URL of the next scene. For example : `/<map folder>/<map>.json`. Be careful, if you want the next map to be correctly loaded, you must check that the map files are in folder `back/src/Assets/Maps/<your map folder>`. The files will be accessible by url `<HOST>/map/files/<your map folder>/...`. - In layer properties, you MUST add "exitSceneUrl" property. It represents the map URL of the next scene. For example : `/<map folder>/<map>.json`. Be careful, if you want the next map to be correctly loaded, you must check that the map files are in folder `back/src/Assets/Maps/<your map folder>`. The files will be accessible by url `<HOST>/map/files/<your map folder>/...`.
@ -56,6 +64,22 @@ In order to place an on your scene that leads to another scene:
![](doc/images/exit_layer_map.png) ![](doc/images/exit_layer_map.png)
### Defining several entry points
Often your map will have several exits, and therefore, several entry points. For instance, if there
is an exit by a door that leads to the garden map, when you come back from the garden you expect to
come back by the same door. Therefore, a map can have several entry points.
Those entry points are "named" (they have a name).
In order to create a named entry point:
- You must create a specific layer. When a character enters the map by this entry point, it will enter the map randomly on ANY tile of that layer.
- In layer properties, you MUST add a boolean "startLayer" property. It should be set to true.
- The name of the entry point is the name of the layer
- To enter via this entry point, simply add a hash with the entry point name to the URL ("#[*startLayerName*]"). For instance: "https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point".
- You can of course use the "#" notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL)
### MacOS developers, your environment with Vagrant ### MacOS developers, your environment with Vagrant
If you are using MacOS, you can increase Docker performance using Vagrant. If you want more explanations, you can read [this medium article](https://medium.com/better-programming/vagrant-to-increase-docker-performance-with-macos-25b354b0c65c). If you are using MacOS, you can increase Docker performance using Vagrant. If you want more explanations, you can read [this medium article](https://medium.com/better-programming/vagrant-to-increase-docker-performance-with-macos-25b354b0c65c).

File diff suppressed because one or more lines are too long

View file

@ -43,7 +43,7 @@
{ {
"name":"exitSceneUrl", "name":"exitSceneUrl",
"type":"string", "type":"string",
"value":"..\/Floor0\/floor0.json" "value":"..\/Floor0\/floor0.json#down-the-stairs"
}], }],
"type":"tilelayer", "type":"tilelayer",
"visible":true, "visible":true,

View file

@ -21,8 +21,9 @@ export enum Textures {
Player = "male1" Player = "male1"
} }
interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface|null initPosition: PointInterface|null,
startLayerName: string|undefined
} }
export class GameScene extends Phaser.Scene { export class GameScene extends Phaser.Scene {
@ -36,8 +37,8 @@ export class GameScene extends Phaser.Scene {
Objects : Array<Phaser.Physics.Arcade.Sprite>; Objects : Array<Phaser.Physics.Arcade.Sprite>;
mapFile: ITiledMap; mapFile: ITiledMap;
groups: Map<string, Sprite>; groups: Map<string, Sprite>;
startX = 704;// 22 case startX: number;
startY = 32; // 1 case startY: number;
circleTexture: CanvasTexture; circleTexture: CanvasTexture;
private initPosition: PositionInterface|null = null; private initPosition: PositionInterface|null = null;
private playersPositionInterpolator = new PlayersPositionInterpolator(); private playersPositionInterpolator = new PlayersPositionInterpolator();
@ -57,6 +58,7 @@ export class GameScene extends Phaser.Scene {
} }
PositionNextScene: Array<any> = new Array<any>(); PositionNextScene: Array<any> = new Array<any>();
private startLayerName: string|undefined;
static createFromUrl(mapUrlFile: string, instance: string): GameScene { static createFromUrl(mapUrlFile: string, instance: string): GameScene {
let key = GameScene.getMapKeyByUrl(mapUrlFile); let key = GameScene.getMapKeyByUrl(mapUrlFile);
@ -124,6 +126,8 @@ export class GameScene extends Phaser.Scene {
init(initData : GameSceneInitInterface) { init(initData : GameSceneInitInterface) {
if (initData.initPosition !== undefined) { if (initData.initPosition !== undefined) {
this.initPosition = initData.initPosition; this.initPosition = initData.initPosition;
} else if (initData.startLayerName !== undefined) {
this.startLayerName = initData.startLayerName;
} }
} }
@ -141,27 +145,49 @@ export class GameScene extends Phaser.Scene {
//add layer on map //add layer on map
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>(); this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
let depth = -2; let depth = -2;
this.mapFile.layers.forEach((layer : ITiledMapLayer) => { for (let layer of this.mapFile.layers) {
if (layer.type === 'tilelayer') { if (layer.type === 'tilelayer') {
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth)); this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
} }
if (layer.type === 'tilelayer' && this.getExitSceneUrl(layer) !== undefined) { if (layer.type === 'tilelayer' && this.getExitSceneUrl(layer) !== undefined) {
this.loadNextGame(layer, this.mapFile.width, this.mapFile.tilewidth, this.mapFile.tileheight); this.loadNextGame(layer, this.mapFile.width, this.mapFile.tilewidth, this.mapFile.tileheight);
} }
if (layer.type === 'tilelayer' && layer.name === "start") {
let startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') { if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
depth = 10000; depth = 10000;
} }
}); };
if (depth === -2) { 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.'); throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at.');
} }
// Now, let's find the start layer
if (this.startLayerName) {
for (let layer of this.mapFile.layers) {
if (this.startLayerName === layer.name && layer.type === 'tilelayer' && this.isStartLayer(layer)) {
let startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
}
}
if (this.startX === undefined) {
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
for (let layer of this.mapFile.layers) {
if (layer.type === 'tilelayer' && layer.name === "start") {
let startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
}
}
// Still no start position? Something is wrong with the map, we need a "start" layer.
if (this.startX === undefined) {
console.warn('This map is missing a layer named "start" that contains the available default start positions.');
// Let's start in the middle of the map
this.startX = this.mapFile.width * 16;
this.startY = this.mapFile.height * 16;
}
//add entities //add entities
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>(); this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
@ -195,31 +221,30 @@ export class GameScene extends Phaser.Scene {
// Let's alter browser history // Let's alter browser history
let url = new URL(this.MapUrlFile); let url = new URL(this.MapUrlFile);
let path = '/_/'+this.instance+'/'+url.host+url.pathname; let path = '/_/'+this.instance+'/'+url.host+url.pathname;
if (url.hash) { if (this.startLayerName) {
// FIXME: entry should be dictated by a property passed to init() path += '#'+this.startLayerName;
path += '#'+url.hash;
} }
window.history.pushState({}, 'WorkAdventure', path); window.history.pushState({}, 'WorkAdventure', path);
} }
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined { private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
let properties : any = layer.properties; return this.getProperty(layer, "exitSceneUrl") as string|undefined;
if (!properties) {
return undefined;
}
let obj = properties.find((property:any) => property.name === "exitSceneUrl");
if (obj === undefined) {
return undefined;
}
return obj.value;
} }
private getExitSceneInstance(layer: ITiledMapLayer): string|undefined { private getExitSceneInstance(layer: ITiledMapLayer): string|undefined {
return this.getProperty(layer, "exitInstance") as string|undefined;
}
private isStartLayer(layer: ITiledMapLayer): boolean {
return this.getProperty(layer, "startLayer") == true;
}
private getProperty(layer: ITiledMapLayer, name: string): string|boolean|number|undefined {
let properties : any = layer.properties; let properties : any = layer.properties;
if (!properties) { if (!properties) {
return undefined; return undefined;
} }
let obj = properties.find((property:any) => property.name === "exitInstance"); let obj = properties.find((property:any) => property.name === name);
if (obj === undefined) { if (obj === undefined) {
return undefined; return undefined;
} }

View file

@ -4,6 +4,7 @@ import {ClickButton} from "../Components/ClickButton";
import Image = Phaser.GameObjects.Image; import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle; import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES} from "../Entity/Character"; import {PLAYER_RESOURCES} from "../Entity/Character";
import {GameSceneInitInterface} from "../Game/GameScene";
//todo: put this constants in a dedicated file //todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene"; export const SelectCharacterSceneName = "SelectCharacterScene";
@ -121,7 +122,9 @@ export class SelectCharacterScene extends Phaser.Scene {
if (instanceAndMapUrl !== null) { if (instanceAndMapUrl !== null) {
let [mapUrl, instance] = instanceAndMapUrl; let [mapUrl, instance] = instanceAndMapUrl;
let key = gameManager.loadMap(mapUrl, this.scene, instance); let key = gameManager.loadMap(mapUrl, this.scene, instance);
this.scene.start(key); this.scene.start(key, {
startLayerName: window.location.hash ? window.location.hash.substr(1) : undefined
} as GameSceneInitInterface);
return mapUrl; return mapUrl;
} else { } else {
// If we do not have a map address in the URL, let's ask the server for a start map. // If we do not have a map address in the URL, let's ask the server for a start map.