Merge pull request #158 from thecodingmachine/multiple_start_positions

Adding the ability to add several entry points
This commit is contained in:
David Négrier 2020-06-08 18:07:16 +02:00 committed by GitHub
commit ed3aedcb91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1083 additions and 967 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:
- 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.
- 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+)
- 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.
![](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.
- 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)
### 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
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",
"type":"string",
"value":"..\/Floor0\/floor0.json"
"value":"..\/Floor0\/floor0.json#down-the-stairs"
}],
"type":"tilelayer",
"visible":true,

View File

@ -21,8 +21,9 @@ export enum Textures {
Player = "male1"
}
interface GameSceneInitInterface {
initPosition: PointInterface|null
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
startLayerName: string|undefined
}
export class GameScene extends Phaser.Scene {
@ -36,8 +37,8 @@ export class GameScene extends Phaser.Scene {
Objects : Array<Phaser.Physics.Arcade.Sprite>;
mapFile: ITiledMap;
groups: Map<string, Sprite>;
startX = 704;// 22 case
startY = 32; // 1 case
startX: number;
startY: number;
circleTexture: CanvasTexture;
private initPosition: PositionInterface|null = null;
private playersPositionInterpolator = new PlayersPositionInterpolator();
@ -57,6 +58,7 @@ export class GameScene extends Phaser.Scene {
}
PositionNextScene: Array<any> = new Array<any>();
private startLayerName: string|undefined;
static createFromUrl(mapUrlFile: string, instance: string): GameScene {
let key = GameScene.getMapKeyByUrl(mapUrlFile);
@ -124,6 +126,8 @@ export class GameScene extends Phaser.Scene {
init(initData : GameSceneInitInterface) {
if (initData.initPosition !== undefined) {
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
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
let depth = -2;
this.mapFile.layers.forEach((layer : ITiledMapLayer) => {
for (let layer of this.mapFile.layers) {
if (layer.type === 'tilelayer') {
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
}
if (layer.type === 'tilelayer' && this.getExitSceneUrl(layer) !== undefined) {
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') {
depth = 10000;
}
});
}
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.');
}
// 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
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
@ -195,31 +221,30 @@ export class GameScene extends Phaser.Scene {
// Let's alter browser history
let url = new URL(this.MapUrlFile);
let path = '/_/'+this.instance+'/'+url.host+url.pathname;
if (url.hash) {
// FIXME: entry should be dictated by a property passed to init()
path += '#'+url.hash;
if (this.startLayerName) {
path += '#'+this.startLayerName;
}
window.history.pushState({}, 'WorkAdventure', path);
}
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
let properties : any = layer.properties;
if (!properties) {
return undefined;
}
let obj = properties.find((property:any) => property.name === "exitSceneUrl");
if (obj === undefined) {
return undefined;
}
return obj.value;
return this.getProperty(layer, "exitSceneUrl") as 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;
if (!properties) {
return undefined;
}
let obj = properties.find((property:any) => property.name === "exitInstance");
let obj = properties.find((property:any) => property.name === name);
if (obj === undefined) {
return undefined;
}
@ -247,14 +272,21 @@ export class GameScene extends Phaser.Scene {
let absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href;
let exitSceneKey = gameManager.loadMap(absoluteExitSceneUrl, this.scene, instance);
let tiles : any = layer.data;
tiles.forEach((objectKey : number, key: number) => {
let tiles : number[] = layer.data as number[];
for (let key=0; key < tiles.length; key++) {
let objectKey = tiles[key];
if(objectKey === 0){
return;
continue;
}
//key + 1 because the start x = 0;
let y : number = parseInt(((key + 1) / mapWidth).toString());
let x : number = key - (y * mapWidth);
let hash = new URL(exitSceneUrl, this.MapUrlFile).hash;
if (hash) {
hash = hash.substr(1);
}
//push and save switching case
// TODO: this is not efficient. We should refactor that to enable a search by key. For instance: this.PositionNextScene[y][x] = exitSceneKey
this.PositionNextScene.push({
@ -262,9 +294,10 @@ export class GameScene extends Phaser.Scene {
yStart: (y * tileWidth),
xEnd: ((x +1) * tileHeight),
yEnd: ((y + 1) * tileHeight),
key: exitSceneKey
key: exitSceneKey,
hash
})
});
}
}
/**
@ -429,7 +462,9 @@ export class GameScene extends Phaser.Scene {
if(nextSceneKey){
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.scene.remove(this.scene.key);
this.scene.start(nextSceneKey.key);
this.scene.start(nextSceneKey.key, {
startLayerName: nextSceneKey.hash
});
}
}

View File

@ -4,6 +4,7 @@ import {ClickButton} from "../Components/ClickButton";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES} from "../Entity/Character";
import {GameSceneInitInterface} from "../Game/GameScene";
//todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene";
@ -121,7 +122,9 @@ export class SelectCharacterScene extends Phaser.Scene {
if (instanceAndMapUrl !== null) {
let [mapUrl, instance] = instanceAndMapUrl;
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;
} else {
// If we do not have a map address in the URL, let's ask the server for a start map.