diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 4996a4c8..00000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Deploy to NPM & Tag -on: - workflow_dispatch: - inputs: - versionUpdateType: - description: 'For example 1.0.1, or patch -> | major | minor | patch | premajor | preminor | prepatch | prerelease' - required: true - default: patch - -env: - NPM_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} - -jobs: - deploy: - name: NPM Publish @excaliburjs/plugin-tiled - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] - steps: - - uses: actions/checkout@v2 - with: - submodules: true - fetch-depth: 100 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - run: | - echo "Deploying version ${{ github.event.inputs.versionUpdateType}} of @excaliburjs/plugin-tiled" - git config --global user.name 'Excalibur Bot' - git config --global user.email 'excaliburjs@gmail.com' - npm ci - npm run build - npm version ${{ github.event.inputs.versionUpdateType }} -m "chore: Deploy %s release" - git push - git push --tags - npm publish --access public \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..42a56e01 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: Release to NPM +on: + release: + types: [published] + +env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + +jobs: + build: + uses: ./.github/workflows/build.yml + deploy_release: + name: Publish npm package + needs: [build] + runs-on: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 100 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + registry-url: 'https://registry.npmjs.org' + cache: npm + - run: npm ci + - run: npm run build + - run: npm publish --access public \ No newline at end of file diff --git a/README.md b/README.md index 944bcc78..822c6ea5 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ game.start(loader).then(() => { ## Documentation -For information on how to use the plugin visit https://beta.excaliburjs.com/docs/plugin/tiled-plugin +For information on how to use the plugin visit https://excaliburjs.com/docs/plugin/tiled-plugin ## Contributing diff --git a/example/formats/formats.ts b/example/formats/formats.ts index 9a533e62..4df84354 100644 --- a/example/formats/formats.ts +++ b/example/formats/formats.ts @@ -101,6 +101,7 @@ const start = (mapFile: string) => { const map = new TiledResource(mapFile, { startZIndex: -2 }); + (window as any).tiledMap = map; const playercube = new ImageSource('./player-cube.png', true, ImageFiltering.Blended); const loader = new ex.Loader([map, playercube]); diff --git a/package-lock.json b/package-lock.json index 021a610c..cc780665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@excaliburjs/plugin-tiled", - "version": "0.29.0-alpha.1", + "version": "0.29.0-alpha.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@excaliburjs/plugin-tiled", - "version": "0.29.0-alpha.1", + "version": "0.29.0-alpha.2", "license": "BSD-2-Clause", "dependencies": { "compare-versions": "6.1.0", @@ -24,7 +24,7 @@ "@types/pako": "1.0.7", "@types/webpack-env": "1.18.4", "cross-env": "7.0.3", - "excalibur": "~0.28.5", + "excalibur": "~0.29.0-alpha.870", "http-server": "14.1.1", "jasmine-core": "5.1.1", "json-diff": "1.0.6", @@ -46,7 +46,7 @@ "jsdom": "^23.2.0" }, "peerDependencies": { - "excalibur": "~0.28.5" + "excalibur": "~0.29.0-alpha.870" } }, "node_modules/@asamuzakjp/dom-selector": { @@ -2212,9 +2212,9 @@ } }, "node_modules/excalibur": { - "version": "0.28.6", - "resolved": "https://registry.npmjs.org/excalibur/-/excalibur-0.28.6.tgz", - "integrity": "sha512-1EZGa7LPC1Ad+BsbvPXzz+l59DV6IkFDdat/Uc0V7bZl05rftpIZkBuJ8Mb5+juHq+0T921clg5tmlQX/bZ/BQ==", + "version": "0.29.0-alpha.870", + "resolved": "https://registry.npmjs.org/excalibur/-/excalibur-0.29.0-alpha.870.tgz", + "integrity": "sha512-Wf2yf9zSbb0i4Zdf+YCFnSsOwdMU/EiAgWosebKgSLgDMfI05NKTco6yfhUSvYdQzPpGbEfwla7F7ST7IZiA8A==", "dev": true, "dependencies": { "core-js": "3.33.3" @@ -8543,9 +8543,9 @@ } }, "excalibur": { - "version": "0.28.6", - "resolved": "https://registry.npmjs.org/excalibur/-/excalibur-0.28.6.tgz", - "integrity": "sha512-1EZGa7LPC1Ad+BsbvPXzz+l59DV6IkFDdat/Uc0V7bZl05rftpIZkBuJ8Mb5+juHq+0T921clg5tmlQX/bZ/BQ==", + "version": "0.29.0-alpha.870", + "resolved": "https://registry.npmjs.org/excalibur/-/excalibur-0.29.0-alpha.870.tgz", + "integrity": "sha512-Wf2yf9zSbb0i4Zdf+YCFnSsOwdMU/EiAgWosebKgSLgDMfI05NKTco6yfhUSvYdQzPpGbEfwla7F7ST7IZiA8A==", "dev": true, "requires": { "core-js": "3.33.3" diff --git a/package.json b/package.json index 6fcdf9c3..08719530 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@excaliburjs/plugin-tiled", - "version": "0.29.0-alpha.1", + "version": "0.29.0-alpha.2", "description": "excalibur-tiled provides Tiled map editor integration with Excalibur.js", "main": "dist/excalibur-tiled.min.js", "typings": "dist/src/index.d.ts", @@ -50,7 +50,7 @@ "@types/pako": "1.0.7", "@types/webpack-env": "1.18.4", "cross-env": "7.0.3", - "excalibur": "~0.28.5", + "excalibur": "~0.29.0-alpha.870", "http-server": "14.1.1", "jasmine-core": "5.1.1", "json-diff": "1.0.6", @@ -76,7 +76,7 @@ "zstddec": "0.1.0" }, "peerDependencies": { - "excalibur": "~0.28.5" + "excalibur": "~0.29.0-alpha.870" }, "optionalDependencies": { "jsdom": "^23.2.0" diff --git a/src/deprecated/tiled-layer-component.ts b/src/deprecated/tiled-layer-component.ts index b4895a0c..29f223fb 100644 --- a/src/deprecated/tiled-layer-component.ts +++ b/src/deprecated/tiled-layer-component.ts @@ -4,6 +4,7 @@ import { TiledLayer } from "./tiled-layer"; /** * @deprecated */ +// @ts-ignore export class TiledLayerComponent extends Component<'ex.tiledlayer'> { public readonly type = "ex.tiledlayer"; constructor(public layer: TiledLayer) { diff --git a/src/deprecated/tiled-map-resource.ts b/src/deprecated/tiled-map-resource.ts index f8712a0a..db4ed809 100644 --- a/src/deprecated/tiled-map-resource.ts +++ b/src/deprecated/tiled-map-resource.ts @@ -184,6 +184,7 @@ export class TiledMapResource implements Loadable { // FIXME no ellipse support yet for colliders in isometric actor.collider.useCircleCollider(collider.radius); } + // @ts-ignore actor.addComponent(new TiledObjectComponent(collider.tiled)); scene.add(actor); if (collider.zIndex) { @@ -238,6 +239,7 @@ export class TiledMapResource implements Loadable { label.color = Color.fromHex(text.text?.color ?? '#000000'); label.collider.set(Shape.Box(text.width ?? 0, text.height ?? 0)); label.body.collisionType = CollisionType.PreventCollision; + // @ts-ignore label.addComponent(new TiledObjectComponent(text)); label.z = this._calculateZIndex(text, objectLayer); @@ -293,6 +295,7 @@ export class TiledMapResource implements Loadable { actor.collider.clear(); actor.collider.set(new CompositeCollider(colliders)); } + // @ts-ignore actor.addComponent(new TiledObjectComponent(tile)); actor.graphics.anchor = this.isIsometric() ? vec(.5, 1) : vec(0, 1); // respect tile size on sprite @@ -710,6 +713,7 @@ export class TiledMapResource implements Loadable { columns: this.data.width, rows: this.data.height }); + // @ts-ignore tileMapLayer.addComponent(new TiledLayerComponent(layer)); if (layer.rawLayer.parallaxx || layer.rawLayer.parallaxy) { const factor = vec(layer.rawLayer.parallaxx ?? 1.0, layer.rawLayer.parallaxy ?? 1.0); diff --git a/src/deprecated/tiled-object-component.ts b/src/deprecated/tiled-object-component.ts index 90dbb5bd..94f675e6 100644 --- a/src/deprecated/tiled-object-component.ts +++ b/src/deprecated/tiled-object-component.ts @@ -4,6 +4,7 @@ import { TiledObject } from "./tiled-object"; /** * @deprecated */ +// @ts-ignore export class TiledObjectComponent extends Component<'ex.tiledobject'> { public readonly type = "ex.tiledobject"; constructor(public object: TiledObject) { diff --git a/src/resource/excalibur-properties.ts b/src/resource/excalibur-properties.ts index bc47de5d..6eacdd7a 100644 --- a/src/resource/excalibur-properties.ts +++ b/src/resource/excalibur-properties.ts @@ -3,6 +3,9 @@ * Special excalibur properties */ export const ExcaliburTiledProperties = { + TileData: { + Tiled: 'ex-tiled' + }, ZIndex: { /** * Override the default z-index based on the ordering in Tiled diff --git a/src/resource/iso-tile-layer.ts b/src/resource/iso-tile-layer.ts index d70fbb96..d50308a0 100644 --- a/src/resource/iso-tile-layer.ts +++ b/src/resource/iso-tile-layer.ts @@ -8,6 +8,7 @@ import { ExcaliburTiledProperties } from "./excalibur-properties"; import { TiledLayerDataComponent } from "./tiled-layer-component"; import { Layer } from "./layer"; import { Tile } from "./tileset"; +import { byClassCaseInsensitive, byPropertyCaseInsensitive } from "./filter-util"; export interface IsometricTileInfo { /** @@ -44,6 +45,9 @@ export class IsoTileLayer implements Layer { * Excalibur IsometricMap structure for drawing in excalibur */ isometricMap!: IsometricMap; + + private _gidToTileInfo = new Map(); + constructor(public tiledTileLayer: TiledTileLayer, public resource: TiledResource, public readonly order: number) { this.name = tiledTileLayer.name; this.class = tiledTileLayer.class; @@ -52,6 +56,52 @@ export class IsoTileLayer implements Layer { mapProps(this, tiledTileLayer.properties); } + /** + * Returns the excalibur tiles that match a tiled gid + */ + getTilesByGid(gid: number): IsometricTileInfo[] { + return this._gidToTileInfo.get(gid) ?? []; + } + + /** + * Returns the excalibur tiles that match a tiled class name + * @param className + */ + getTilesByClassName(className: string): IsometricTileInfo[] { + const tiles = this.isometricMap.tiles.filter(t => { + const maybeTiled = t.data.get(ExcaliburTiledProperties.TileData.Tiled) as Tile | undefined; + if (maybeTiled) { + return byClassCaseInsensitive(className)(maybeTiled); + } + return false; + }); + + return tiles.map(t => ({ + exTile: t, + tiledTile: t.data.get(ExcaliburTiledProperties.TileData.Tiled) + })) + } + + /** + * Returns the excalibur tiles that match a tiled property and optional value + * @param name + * @param value + */ + getTilesByProperty(name: string, value?: any): IsometricTileInfo[] { + const tiles = this.isometricMap.tiles.filter(t => { + const maybeTiled = t.data.get(ExcaliburTiledProperties.TileData.Tiled) as Tile | undefined; + if (maybeTiled) { + return byPropertyCaseInsensitive(name, value)(maybeTiled); + } + return false; + }); + + return tiles.map(t => ({ + exTile: t, + tiledTile: t.data.get(ExcaliburTiledProperties.TileData.Tiled) + })) + } + getTileByPoint(worldPos: Vector): IsometricTileInfo | null { if (!this.isometricMap) { this.logger.warn('IsometricMap has not yet been loaded! getTileByPoint() will only return null'); @@ -75,7 +125,21 @@ export class IsoTileLayer implements Layer { return null; } + private _recordTileData(gid: number, tile: IsometricTile) { + let tiles: IsometricTileInfo[] | undefined = this._gidToTileInfo.get(gid); + let tileset = this.resource.getTilesetForTileGid(gid); + let maybeTile = tileset.getTileByGid(gid); + if (!tiles) { + tiles = [{exTile: tile, tiledTile: maybeTile}]; + } else { + tiles.push({exTile: tile, tiledTile: maybeTile}); + } + this._gidToTileInfo.set(gid, tiles); + tile.data.set(ExcaliburTiledProperties.TileData.Tiled, maybeTile); + } + private updateTile(tile: IsometricTile, gid: number, hasTint: boolean, tint: Color, isSolidLayer: boolean) { + this._recordTileData(gid, tile); if (this.resource.useExcaliburWiring && isSolidLayer) { tile.solid = true; } @@ -105,7 +169,7 @@ export class IsoTileLayer implements Layer { // the whole tilemap uses a giant composite collider relative to the Tilemap // not individual tiles - const colliders = tileset.getCollidersForGid(gid, {offset}); + const colliders = tileset.getCollidersForGid(gid, { offset }); for (let collider of colliders) { tile.addCollider(collider); } @@ -168,7 +232,7 @@ export class IsoTileLayer implements Layer { if (typeof zoverride === 'number') { order = zoverride; } - + if (this.resource.map.infinite && isInfiniteLayer(this.tiledTileLayer)) { diff --git a/src/resource/object-layer.ts b/src/resource/object-layer.ts index 6f7ad553..b81d31fb 100644 --- a/src/resource/object-layer.ts +++ b/src/resource/object-layer.ts @@ -1,4 +1,4 @@ -import { Actor, CircleCollider, CollisionType, Color, Entity, GraphicsComponent, IsometricEntityComponent, Logger, PolygonCollider, Shape, Vector, toRadians, vec } from "excalibur"; +import { Actor, CollisionType, Color, Entity, GraphicsComponent, IsometricEntityComponent, Logger, Shape, Vector, toRadians, vec } from "excalibur"; import { Layer } from "./layer"; import { InsertedTile, PluginObject, TemplateObject, Text, Polygon, Rectangle, Ellipse, parseObjects } from "./objects"; import { TiledObjectLayer } from "../parser/tiled-parser"; @@ -95,6 +95,47 @@ export class ObjectLayer implements Layer { return this.objects.filter(o => o instanceof TemplateObject) as TemplateObject[]; } + /** + * Runs or re-runs a specific registered factory given a class name on this object layer + * @param className + */ + runFactory(className: string) { + const offset = vec(this.tiledObjectLayer.offsetx ?? 0, this.tiledObjectLayer.offsety ?? 0); + // create a copy of the objects to prevent editing the current collection + const objects = this.objects.slice(); + for (let object of objects) { + let objectType = object.class; + if (object instanceof TemplateObject) { + objectType = objectType ? objectType : object.template.object.class; + } + + if (className !== objectType) continue; + + let worldPos = vec((object.x ?? 0) + offset.x, (object.y ?? 0) + offset.y); + + // When isometric, Tiled positions are in isometric coordinates + if (this.resource.map.orientation === 'isometric') { + worldPos = this.resource.isometricTiledCoordToWorld(worldPos.x, worldPos.y); + } + + const factory = this.resource.factories.get(className); + if (factory) { + // TODO does this entity get added to the scene? + const entity = factory({ + worldPos, + name: object.name, + class: objectType, + layer: this, + object, + properties: object.properties + } satisfies FactoryProps); + if (entity) { + this._recordObjectEntityMapping(object, entity); + } + } + } + } + _actorFromObject(object: PluginObject, newActor: Actor, tileset?: Tileset): void { const headless = this.resource.headless; const hasTint = !!this.tiledObjectLayer.tintcolor; @@ -152,8 +193,23 @@ export class ObjectLayer implements Layer { } const colliders = tileset.getCollidersForGid(object.gid, { anchor: Vector.Zero, scale, offset }); - if (colliders) { + if (colliders.length) { newActor.collider.useCompositeCollider(colliders); + } else { + let width = object.width; + let height = object.height; + if (this.resource.map.orientation === 'isometric') { + // Isometric uses height to organize grid alignment + const dimension = object.height / 2; + width = dimension; + height = dimension; + } + // Anchor at 1,1 for isometric is a quirk of the coord transformation + let boxCollider = Shape.Box(width, height, this.resource.map.orientation === 'isometric' ? vec(1, 1) : vec(0, 1)); + if (this.resource.map.orientation === 'isometric') { + boxCollider.points = boxCollider.points.map(p => this.resource.isometricTiledCoordToWorld(p.x, p.y)); + } + newActor.collider.set(boxCollider); } } @@ -200,7 +256,6 @@ export class ObjectLayer implements Layer { async load() { const opacity = this.tiledObjectLayer.opacity; const offset = vec(this.tiledObjectLayer.offsetx ?? 0, this.tiledObjectLayer.offsety ?? 0); - const objects = parseObjects(this.tiledObjectLayer, this.resource); for (let object of objects) { diff --git a/src/resource/tile-layer.ts b/src/resource/tile-layer.ts index c76f13ce..2303d15c 100644 --- a/src/resource/tile-layer.ts +++ b/src/resource/tile-layer.ts @@ -8,6 +8,7 @@ import { ExcaliburTiledProperties } from "./excalibur-properties"; import { TiledLayerDataComponent } from "./tiled-layer-component"; import { Layer } from "./layer"; import { Tile } from "./tileset"; +import { byClassCaseInsensitive, byPropertyCaseInsensitive } from "./filter-util"; /** * Tile information for both excalibur and tiled tile representations @@ -48,6 +49,53 @@ export class TileLayer implements Layer { */ tilemap!: TileMap; + private _gidToTileInfo = new Map(); + + /** + * Returns the excalibur tiles that match a tiled gid + */ + getTilesByGid(gid: number): TileInfo[] { + return this._gidToTileInfo.get(gid) ?? []; + } + + /** + * Returns the excalibur tiles that match a tiled class name + * @param className + */ + getTilesByClassName(className: string): TileInfo[] { + const tiles = this.tilemap.tiles.filter(t => { + const maybeTiled = t.data.get(ExcaliburTiledProperties.TileData.Tiled) as Tile | undefined; + if (maybeTiled) { + return byClassCaseInsensitive(className)(maybeTiled); + } + return false; + }); + + return tiles.map(t => ({ + exTile: t, + tiledTile: t.data.get(ExcaliburTiledProperties.TileData.Tiled) + })) + } + + /** + * Returns the excalibur tiles that match a tiled property and optional value + * @param name + * @param value + */ + getTilesByProperty(name: string, value?: any): TileInfo[] { + const tiles = this.tilemap.tiles.filter(t => { + const maybeTiled = t.data.get(ExcaliburTiledProperties.TileData.Tiled) as Tile | undefined; + if (maybeTiled) { + return byPropertyCaseInsensitive(name, value)(maybeTiled); + } + return false; + }); + + return tiles.map(t => ({ + exTile: t, + tiledTile: t.data.get(ExcaliburTiledProperties.TileData.Tiled) + })) + } getTileByPoint(worldPos: Vector): TileInfo | null { if (!this.tilemap) { @@ -102,7 +150,21 @@ export class TileLayer implements Layer { mapProps(this, tiledTileLayer.properties); } + private _recordTileData(gid: number, tile: ExTile) { + let tiles: TileInfo[] | undefined = this._gidToTileInfo.get(gid); + let tileset = this.resource.getTilesetForTileGid(gid); + let maybeTile = tileset.getTileByGid(gid); + if (!tiles) { + tiles = [{exTile: tile, tiledTile: maybeTile}]; + } else { + tiles.push({exTile: tile, tiledTile: maybeTile}); + } + this._gidToTileInfo.set(gid, tiles); + tile.data.set(ExcaliburTiledProperties.TileData.Tiled, maybeTile); + } + private updateTile(tile: ExTile, gid: number, hasTint: boolean, tint: Color, isSolidLayer: boolean) { + this._recordTileData(gid, tile); if (this.resource.useExcaliburWiring && isSolidLayer) { tile.solid = true; } @@ -119,7 +181,6 @@ export class TileLayer implements Layer { tile.addGraphic(sprite, { offset: tileset.tileOffset }); } - // the whole tilemap uses a giant composite collider relative to the Tilemap // not individual tiles const colliders = tileset.getCollidersForGid(gid); diff --git a/src/resource/tiled-data-component.ts b/src/resource/tiled-data-component.ts index 3d761911..74862d33 100644 --- a/src/resource/tiled-data-component.ts +++ b/src/resource/tiled-data-component.ts @@ -4,8 +4,7 @@ import { PluginObject } from "./objects"; export interface TiledDataComponentOptions { tiledObject: PluginObject; } -export class TiledDataComponent extends Component<'ex.tiled-data'> { - public readonly type = 'ex.tiled-data'; +export class TiledDataComponent extends Component { public tiledObject: PluginObject; constructor(options: TiledDataComponentOptions){ super(); diff --git a/src/resource/tiled-layer-component.ts b/src/resource/tiled-layer-component.ts index 86573dfa..2ac649f9 100644 --- a/src/resource/tiled-layer-component.ts +++ b/src/resource/tiled-layer-component.ts @@ -5,8 +5,7 @@ export interface TiledLayerDataComponentOptions { tiledTileLayer: TiledTileLayer; } -export class TiledLayerDataComponent extends Component<'ex.tiled-layer'> { - public readonly type = 'ex.tiled-layer'; +export class TiledLayerDataComponent extends Component { public readonly tiledTileLayer: TiledTileLayer; constructor(options: TiledLayerDataComponentOptions) { super(); diff --git a/src/resource/tiled-resource.ts b/src/resource/tiled-resource.ts index 7b9b84c7..52774e1c 100644 --- a/src/resource/tiled-resource.ts +++ b/src/resource/tiled-resource.ts @@ -228,15 +228,21 @@ export class TiledResource implements Loadable { this.mapFormat = mapFormatOverride ?? (path.includes('.tmx') ? 'TMX' : 'TMJ'); } + /** + * Registers an entity factory to run on load, if added after load it will be run immediately + * @param className + * @param factory + */ registerEntityFactory(className: string, factory: (props: FactoryProps) => Entity | undefined): void { - if (this.isLoaded()) { - console.warn(`Tiled Resource has already loaded, register "${className}" factory before load has been called for it to function.`); - } - if (this.factories.has(className)) { console.warn(`Another factory has already been registered for tiled class/type "${className}", this is probably a bug.`); } this.factories.set(className, factory); + if (this.isLoaded()) { + for (let objectLayer of this.getObjectLayers()) { + objectLayer.runFactory(className); + } + } } unregisterEntityFactory(className: string) { @@ -291,11 +297,11 @@ export class TiledResource implements Loadable { } /** - * Queries ALL tilesets in the map for a specific class name (case insensitive) + * Queries ALL tilesets tile data in the map for a specific class name (case insensitive) * @param className * @returns */ - getTilesByClassName(className: string): Tile[] { + getTileMetadataByClassName(className: string): Tile[] { let results: Tile[] = []; for (let tileset of this.tilesets) { results = results.concat(tileset.tiles.filter(byClassCaseInsensitive(className))); @@ -304,12 +310,12 @@ export class TiledResource implements Loadable { } /** - * Queries ALL tilesets in the map for a specific property and an optional value (case insensitive) + * Queries ALL tilesets tile data in the map for a specific property and an optional value (case insensitive) * @param name * @param value * @returns */ - getTilesByProperty(name: string, value?: any): Tile[] { + getTileMetadataByProperty(name: string, value?: any): Tile[] { let results: Tile[] = []; for (let tileset of this.tilesets) { results = results.concat(tileset.tiles.filter(byPropertyCaseInsensitive(name, value))); @@ -317,6 +323,71 @@ export class TiledResource implements Loadable { return results; } + + /** + * Queries ALL tile layers tile instances in the map for a specific gid + * @param className + * @returns + */ + getTilesByGid(gid: number): TileInfo[] | IsometricTileInfo[] { + if (this.map.orientation === 'orthogonal') { + let results: TileInfo[] = []; + for (let layer of this.getTileLayers()) { + results = results.concat(layer.getTilesByGid(gid)); + } + return results; + } else { + let results: IsometricTileInfo[] = []; + for (let layer of this.getIsoTileLayers()) { + results = results.concat(layer.getTilesByGid(gid)); + } + return results; + } + } + + /** + * Queries ALL tile layers tile instances in the map for a specific class name (case insensitive) + * @param className + * @returns + */ + getTilesByClassName(className: string): TileInfo[] | IsometricTileInfo[] { + if (this.map.orientation === 'orthogonal') { + let results: TileInfo[] = []; + for (let layer of this.getTileLayers()) { + results = results.concat(layer.getTilesByClassName(className)); + } + return results; + } else { + let results: IsometricTileInfo[] = []; + for (let layer of this.getIsoTileLayers()) { + results = results.concat(layer.getTilesByClassName(className)); + } + return results; + } + } + + /** + * Queries ALL tile layers tile instances in the map for a specific property and an optional value (case insensitive) + * @param name + * @param value + * @returns + */ + getTilesByProperty(name: string, value?: any): TileInfo[] | IsometricTileInfo[] { + if (this.map.orientation === 'orthogonal') { + let results: TileInfo[] = []; + for (let layer of this.getTileLayers()) { + results = results.concat(layer.getTilesByProperty(name, value)); + } + return results; + } else { + let results: IsometricTileInfo[] = []; + for (let layer of this.getIsoTileLayers()) { + results = results.concat(layer.getTilesByProperty(name, value)); + } + return results; + } + } + /** * Returns a tile by the world position from a layer. (Uses the first layer name that matches case insensitive). * @param layerName @@ -339,6 +410,34 @@ export class TiledResource implements Loadable { return null; } + /** + * Returns a tile by the world position from a layer. (Uses the first layer name that matches case insensitive). + * @param layerName + * @param worldPos + * @returns + */ + getTilesByPoint(worldPos: Vector): TileInfo[] | IsometricTileInfo[] { + if (this.map.orientation === 'orthogonal') { + let results: TileInfo[] = []; + for (let layer of this.getTileLayers()) { + const maybeTile = layer.getTileByPoint(worldPos); + if (maybeTile) { + results.push(maybeTile); + } + } + return results; + } else { + let results: IsometricTileInfo[] = []; + for (let layer of this.getIsoTileLayers()) { + const maybeTile = layer.getTileByPoint(worldPos); + if (maybeTile) { + results.push(maybeTile); + } + } + return results; + } + } + /** * Queries all layers for objects that match a name (case insensitive) * @param name diff --git a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-chromium-linux.png b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-chromium-linux.png index 36679c30..7ed854cf 100644 Binary files a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-chromium-linux.png and b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-chromium-linux.png differ diff --git a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-chromium-win32.png b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-chromium-win32.png index 36679c30..7ed854cf 100644 Binary files a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-chromium-win32.png and b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-chromium-win32.png differ diff --git a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-firefox-linux.png b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-firefox-linux.png index cead1860..5de01548 100644 Binary files a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-firefox-linux.png and b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-firefox-linux.png differ diff --git a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-firefox-win32.png b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-firefox-win32.png index b57e0c08..040dc866 100644 Binary files a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-firefox-win32.png and b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-firefox-win32.png differ diff --git a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-webkit-linux.png b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-webkit-linux.png index 85ff049b..4d94145d 100644 Binary files a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-webkit-linux.png and b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-webkit-linux.png differ diff --git a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-webkit-win32.png b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-webkit-win32.png index 8f7b903a..68744f2e 100644 Binary files a/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-webkit-win32.png and b/test/integration/isometric.spec.ts-snapshots/isometric-matches-1-webkit-win32.png differ diff --git a/test/unit/tiled-resource.spec.ts b/test/unit/tiled-resource.spec.ts index 2755e383..80d21fbb 100644 --- a/test/unit/tiled-resource.spec.ts +++ b/test/unit/tiled-resource.spec.ts @@ -195,23 +195,25 @@ describe('A Tiled map resource parser', () => { await tiledMap.load(); - spyOn(console, 'warn').and.callThrough(); - // will warn if registered after load - tiledMap.registerEntityFactory('player-start', (props) => { + const lateFactorySpy = jasmine.createSpy('lateFactorySpy', (props) => { return new Actor({ pos: props.worldPos }) - }); - - expect(console.warn).toHaveBeenCalledWith('Tiled Resource has already loaded, register "player-start" factory before load has been called for it to function.'); + }).and.callThrough(); + // will construct if registered after load + tiledMap.registerEntityFactory('player-start', lateFactorySpy); expect(factorySpy).toHaveBeenCalledTimes(2); + expect(lateFactorySpy).toHaveBeenCalledTimes(1); const entities = tiledMap.getEntitiesByClassName('Collectable'); expect(entities.length).toBe(2); expect(entities[0].name).toBe('Coin'); expect(entities[1].name).toBe('Arrow'); + + const playerStart = tiledMap.getEntitiesByClassName('player-start'); + expect(playerStart).toBeDefined(); }); it('can get entities by name (case insensitive)', async () => { @@ -391,7 +393,7 @@ describe('A Tiled map resource parser', () => { await tiledMap.load(); - const tile = tiledMap.getTilesByClassName('tileclass'); + const tile = tiledMap.getTileMetadataByClassName('tileclass'); expect(tile[0].id).toBe(2); expect(tile[0].properties.get('tileprop')).toBe('someprop'); @@ -404,7 +406,7 @@ describe('A Tiled map resource parser', () => { await tiledMap.load(); - const tile = tiledMap.getTilesByProperty('tileprop'); + const tile = tiledMap.getTileMetadataByProperty('tileprop'); expect(tile[0].id).toBe(2); expect(tile[0].properties.get('tileprop')).toBe('someprop');