Skip to content

Commit

Permalink
feat: Upgrade Tiled plugin & replace flawed XML parser (#477)
Browse files Browse the repository at this point in the history
This PR will remove the old xml parser/logic and replace it with a hand crafted parser developed here https://github.com/eonarheim/tiled-xml-parser 

Features in this update
* Parser supports parsing _all_ tiled properties, however the plugin doesn't support rendering all of them in Excalibur
* New file mapping to work with various bundlers
* Tiled Template Support
* External Separate Tileset loading Closes #455
* Infinite Tile Maps!!!
* Actor Factory to provide your own implementations based on Tiled class  
 - Get props passed to objects excaliburjs/Excalibur#2847 
 - Global class identification Closes #451


Fixes
* New Parser - Closes #391
* New Loader - Closes  #387

In addition to replacing the parser this PR also updates the API to be more supportable and friendlier to use.  The old `TiledMapResource` type will be marked deprecated

* [x] Headless mode - Closes #478
* [x] Optional file loader implementation - Closes #478
* [x] Isometric
* [x] Integration tests 
* [x] Unit tests
* [x] New Documentation
* [ ] Update Samples
  • Loading branch information
eonarheim committed Jan 6, 2024
1 parent 52396f7 commit 4459806
Show file tree
Hide file tree
Showing 493 changed files with 24,894 additions and 9,809 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ jobs:
${{ runner.os }}-node-
- run: npm ci
- run: npm run build
- run: npx playwright install
- run: npm run test
27 changes: 27 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ actual-*.png
diff-*.png
coverage/
!files.d.ts
!karma.conf.js
!karma.conf.js
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
*.tiled-session
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ coverage
example
node_cache/*
test
test-results
playwright-report
readme/*
!dist/*
**/webpack.config.js
**/webpack.config.test.js
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16.20.2
18.14.1
298 changes: 34 additions & 264 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,291 +1,41 @@
# Tiled Plugin for Excalibur.js

This extension adds support for tile maps from all [Tiled map editor](http://mapeditor.org) files in Excalibur. Use the `TiledMapResource` to load and interact with Tiled based maps!
Tiled is a super useful tool for building game levels across the industry. The Tiled plugin for Excalibur offers support for both Orthogonal (standard) and Isometric maps!

![](./readme/example.gif)

## Quickstart

Install using [npm](http://npmjs.org):

```
> npm install @excaliburjs/plugin-tiled
```

## ES2015 (TS/JS)
The current Tiled plugin aims to support *parsing all data* in the Map (.tmx/.tmj), Tileset (.tsx, .tsj.) and Template files (.tx, tj). The plugin however does not support rendering all map types, currently hexagons and isometric staggered are not supported.

The ES2015 `import` syntax is the recommended way to use Excalibur with Excalibur Tiled and is supported through a module loader like [webpack](https://github.com/excaliburjs/example-ts-webpack) or [Parcel](https://parceljs.org) with TypeScript or Babel:
The plugin officially supports the latest version of Tiled that has been published and will warn if you are using an older version. This is because there have been many breaking changes to the Tiled map format over time that are difficult to reconcile.

```ts
import * as ex from 'excalibur';
import { TiledMapResource } from '@excaliburjs/plugin-tiled';

// Create tiled map resource, pointing to static asset path
const tiledMap = new TiledMapResource("/assets/map.tmx");

// Create a loader and reference the map
const loader = new ex.Loader([tiledMap]);

// Start the game (starts the loader)
game.start(loader).then(function() {

console.log("Game loaded");
tiledMap.addTiledMapToScene(game.currentScene);

});
```

For reference, see this [CodeSandbox sample](https://codesandbox.io/s/excalibur-tiled-example-4f83x?fontsize=14) for a Parcel-based game.

## Features

* Parse default Tiled tmx files
- Supports all Tiled compressions zlib, gzip, and zstd
* Parse Tiled exported json files
* Supports external tilesets `.tsx` and `json`
* New TypeScript based object model for working with Tiled data
* Query for layers by property
* Query for objects by property
* Easy helpers to locate Polygons, Polylines, and Text
* Automatic Excalibur wiring for certain Tiled properties and objects:
* Camera
* Colliders
* Solid TileMap Layers
* Tiled Text
* Inserted Tiled Tiles

### Excalibur Wiring
![](./readme/example.gif)

You may opt-in to the Excalibur wiring by calling `addTiledMapToScene(someScene)`
## Installation

```typescript
// After loading tiledMapResource
tiledMapResource.addTiledMapToScene(game.currentScene);
```sh
npm install --save-exact @excaliburjs/plugin-tiled@next
```

* **To exclude object layers `"excalibur-exclude"=true`**. - Objects are included by default and can be retrieved with the following

```typescript
const objects: TiledObjectGroup[] = tiledMapResource.getObjects();
```

* **Camera Object Position & Zoom** - You may set the starting camera position and zoom

![](./readme/camera.png)
- In an object layer with a custom property "excalibur"=true
- **Note** Only the first Camera in the first "excalibur"=true layer will be used
- Create a Tiled "Point" with the Tiled Class "Camera"
- Optionally, to set zoom other than the default of 1.0, create a custom property named "Zoom" with a numeric value

* **Solid layers** - You can mark a particular layers tiles as solid in Tiled

![](./readme/solid.png)
- In the Tiled layer properties, add a custom property named "Solid" with a boolean value `true`
- The presence of a tile in this layer indicates that space is solid, the absence of a tile means it is not solid

* **Colliders Objects** - You may position Excalibur colliders within Tiled
![](./readme/collider.png)
- In an object layer with a custom property "excalibur"=true
- Create a "Circle" (ellipses are not supported) or "Rectangle"
- Set the Tiled Class to "BoxCollider" or "CircleCollider"
- Optionally, to set an Excalibur collision type specify a custom property named "CollisionType" with the value
- "Fixed" (default for colliders) - non-movable object
- "Passive" - triggers events, does not participate in collision
- "Active" - participates in collision and can be pushed around
- "PreventCollision" - all collisions are ignored

* **Tile Custom Colliders** - You can leverage custom colliders in Tiled and they will be pulled into excalibur
![Add a tile collider](./readme/tile-collider.png)
- Must be in a layer marked with a custom property named "solid" with a value `true`
- Colliders are "Fixed"

* **Text** - You may insert excalibur labels within Tiled
![Example of Tiled text](./readme/text.png)
- In an object layer with a custom property "excalibur"=true
- Create a Tiled Text object
- Optionally, you can set the "ZIndex" as a float custom tiled property
- **⚠ A word of caution around fonts ⚠** - fonts are different on every operating system (some may not be available to your user unless you explicitly load them into the page with a font loader). See [here for some detail](https://erikonarheim.com/posts/dont-test-fonts/)

* **Inserted Tile Objects** - You may insert tiles on or off grid in Tiled with inserted tiles
![Example of an inserted Tile](./readme/insertedtile.png)
- In an object layer with a custom property "excalibur"=true
- Create a Tiled inserted Tile
- Optionally, you can set the "ZIndex" as a float custom tiled property
- Optionally, to set an Excalibur collision type specify a custom property named "CollisionType" with the value
- "Fixed" non-movable object
- "Passive" (default for inserted tiles) - triggers events, does not participate in collision
- "Active" - participates in collision and can be pushed around
- "PreventCollision" - all collisions are ignored


* **Tile Animations** - You can leverage tile animations in Tiled and they will be pulled into excalibur
![Tiled Tile Animation Editor](./readme/animations.png)

* **Isometric Tile Maps** - Tiled isometric maps now work without any additional configuration!
![Tiled Isometric](./readme/isometric.png)

* **Parallax Layers** - Tiled parallax layers are now supported
![Tiled Layer Parallax Factor](./readme/parallax.png)

* **Layer offsets** -
![Tiled Layer Offset](./readme/offset.png)

## Not Yet Supported Out of the Box

* Currently Hexagonal maps are not directly supported by Excalibur TileMaps, however the data is still parsed by this plugin and can be used manually by accessing the `RawTiledMap` in `TiledMapResource.data.rawMap` after loading.

* Excalibur Text is limited at the moment and doesn't support Tiled word wrapping or Tiled text alignment other than the default "Left" horizontal, "Top" vertical alignments.

* [Layer tinting](https://doc.mapeditor.org/en/latest/manual/layers/#tinting-layers) is not yet supported

* Image Layers - Tiled image layers are not yet fully supported, but do show up in the `RawTiledMap` so can be used that way. Using inserted [Tile Objects](https://doc.mapeditor.org/fr/latest/manual/layers/#image-layers) is a way to achieve the same effect in a fully supported way.

* Group Layers - Tiled group layers are not yet supported at all, currently layers in a group do not load. Maps with group layers will load all other layers fine.

* Infinite maps - Tiled infinite maps are not yet supported, but do show up in the `RawTiledMap`.

* `RawTiledMap` fully types the Tiled 1.4.3 api, this can be used to write custom code for anything this plugin doesn't yet support.
Create your resource, load it, then add it to your scene!

```typescript
const game = new ex.Engine({...});

import * as ex from 'excalibur';
import { TiledMapResource } from '@excaliburjs/plugin-tiled';

// Create tiled map resource, pointing to static asset path
const tiledMap = new TiledMapResource("/assets/map.tmx");
const tiledMap = new TiledResource('./path/to/map.tmx');

// Create a loader and reference the map
const loader = new ex.Loader([tiledMap]);

game.start(loader).then(function() {

// Access raw data
const rawMap = tiledMap.data.rawMap;

game.start(loader).then(() => {
tiledMap.addToScene(game.currentScene);
});

```



## Webpack Configuration

You will need to modify your webpack configuration to load Tiled JSON files using `file-loader` and then ensure any tilemap images are copied to the same output directory as your bundle, see [this example-ts-webpack branch](https://github.com/excaliburjs/example-ts-webpack/tree/feature/excalibur-tiled-with-webpack) for an example.

## Standalone Script File (JS)

In your HTML file, add a reference **dist/excalibur-tiled.min.js** in your page:

```html
<script type="text/javascript" src="node_modules/excalibur/dist/excalibur.min.js"></script>
<script type="text/javascript" src="node_modules/@excaliburjs/excalibur-tiled/dist/excalibur-tiled.min.js"></script>
```

and then you can use it like this:

```js

// New game
const game = new ex.Engine({ width: 500, height: 400, canvasElementId: "game" });

// Create a new TiledMapResource loadable
const tiledMap = new ex.Plugin.Tiled.TiledMapResource("test.tmx");

// Create a loader and reference the map
const loader = new ex.Loader([tiledMap]);

// Start the game (starts the loader)
game.start(loader).then(function() {

console.log("Game loaded");

tiledMap.addTiledMapToScene(game.currentScene);

});
```

The dist uses a UMD build and will attach itself to the `ex.Plugin.Tiled` global if running in the browser standalone.

## Documentation

The `TiledMapResource` loadable will load the map file you specify along with any referenced tile set assets (images).

### Handling Tiled Paths

The image paths and external tileset paths loaded will be relative to where the exported file was saved.

For example, let's say this is your source working directory structure when you make your Tiled map:

```
work/
- map.tmx
- map.png
- map.tsx
```

The tileset image and/or source are stored next to the TMX file.

So when you export to JSON, say to **map.json**, Tiled will save the paths like this:

```js
{
"tilesets": [
{
"image": "map.png"
},
{
"source": "map.tsx"
}
]
}
```

But for your game, your file structure looks like this:

```
assets/
- maps/map.json
- tx/map.png
- tsx/map.tsx
```

When your game loads and the extension loads your map file (`/assets/maps/map.tmx`), the paths will be loaded **relative** to the map tmx or any tsx file, so they will return 404 responses:

```
GET /assets/maps/map.png -> 404 Not Found
GET /assets/maps/map.tsx -> 404 Not Found
```

If you need to override this behavior, you can set `convertPath` to a custom function that takes two parameters: `originPath` and `relativePath` data.

`originPath` is the path of the original source file (for example the `map.tmx`), and `relativePath` is referenced external fil (for example the `map.tsx`)

```js
// Create a new TiledResource loadable
var map = new ex.Plugin.Tiled.TiledMapResource("map.tmx");

map.convertPath = function (originPath, relativePath) {
return "/assets/tx/" + path;
}
```

That will fix the paths:

```
GET /assets/tx/map.png -> 200 OK
GET /assets/tsx/map.tsx -> 200 OK
```

### Supported Formats

Supports all currently supported Tiled 1.4.3 formats!

* TMX - CSV, Base64 + Compressed (`zlib`, `gzip`, and `zstd`)
* JSON Tiled Export
For information on how to use the plugin visit https://beta.excaliburjs.com/docs/plugin/tiled-plugin

## Contributing

- Built with webpack 4
- Built with webpack 5
- Uses webpack-dev-server

To start development server:
Expand All @@ -302,4 +52,24 @@ To compile only:

To run tests:

npx playwright install
npm test

To update snapshots

* Windows

```powershell
npx playwright test --update-snapshots
```

* Linux for CI

```powershell
docker run --rm --network host -v C:\projects\excalibur-tiled:/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.40.0-jammy /bin/bash
npm install
npx playwright test --update-snapshots
```



Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
File renamed without changes
Binary file added example/formats/assets/isometric/Spritesheet.png
File renamed without changes
Loading

0 comments on commit 4459806

Please sign in to comment.