Merge pull request #158 from thecodingmachine/multiple_start_positions
Adding the ability to add several entry points
This commit is contained in:
commit
ed3aedcb91
30
README.md
30
README.md
@ -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
@ -43,7 +43,7 @@
|
||||
{
|
||||
"name":"exitSceneUrl",
|
||||
"type":"string",
|
||||
"value":"..\/Floor0\/floor0.json"
|
||||
"value":"..\/Floor0\/floor0.json#down-the-stairs"
|
||||
}],
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user