Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add separate loading support for external Tilesets #455

Closed
JumpLink opened this issue Sep 4, 2023 · 1 comment · Fixed by #477
Closed

Add separate loading support for external Tilesets #455

JumpLink opened this issue Sep 4, 2023 · 1 comment · Fixed by #477

Comments

@JumpLink
Copy link
Contributor

JumpLink commented Sep 4, 2023

Context

In Tiled, tilesets can be external, which means they can be shared across multiple maps. By allowing the separate loading of these external tilesets, we can optimize asset loading by ensuring that the same tileset isn't loaded multiple times. Furthermore, for certain use-cases, there's a need to load tilesets without associating them with any map.

Proposal

Introduce a TiledTilesetResource class that would allow for the separate loading of tilesets, independent of the map they may be associated with. This would not only cater to external tilesets in Tiled but also provide more flexibility for developers who may want to load tilesets without a map.

Here's a possible implementation of the TiledTilesetResource class:

import { Resource, ImageSource, SpriteSheet } from 'excalibur';
import { TiledTileset, RawTiledTileset, parseExternalTsx, parseExternalJson } from '@excaliburjs/plugin-tiled';
import type { Loadable } from 'excalibur';

export class TiledTilesetResource implements Loadable<TiledTileset | null> {

    protected _resource: Resource<RawTiledTileset>

    /**
     * Data associated with a loadable
     */
    public data: TiledTileset | null = null;

    public image: ImageSource | null = null;

    public spriteSheet: SpriteSheet | null = null;

    get raw() {
        return this._resource.data;
    }

    constructor(public path: string, public isJson: boolean = false) {
        this._resource = new Resource<RawTiledTileset>(path, isJson ? 'json' : 'text');
    }

    /**
     * Begins loading the resource and returns a promise to be resolved on completion
     */
    public async load(): Promise<TiledTileset> {
        let rawTileSet = await this._resource.load();

        if (this.isJson) {
            this.data = parseExternalJson(rawTileSet, 0, this.path);
        } else {
            this.data = parseExternalTsx(rawTileSet as unknown as string, 0, this.path);
        }

        if(this.data.image) {
            this.image = new ImageSource(this.convertPath(this.path, this.data.image));
            await this.image.load();

            const rows = this.data.tileCount / this.data.columns;

            this.spriteSheet = SpriteSheet.fromImageSource({
                image: this.image,
                grid: {
                    rows: rows,
                    columns: this.data.columns,
                    spriteWidth: this.data.tileWidth,
                    spriteHeight: this.data.tileHeight
                },
            });
        }

        return this.data;
    }

    /**
     * Returns true if the loadable is loaded
     */
    public isLoaded(): boolean {
        return this._resource.isLoaded();
    }

    protected convertPath(originPath: string, relativePath: string) {
        // Use absolute path if specified
        if (relativePath.indexOf('/') === 0) {
           return relativePath;
        }

        const originSplit = originPath.split('/');
        const relativeSplit = relativePath.split('/');
        // if origin path is a file, remove it so it's a directory
        if (originSplit[originSplit.length - 1].includes('.')) {
           originSplit.pop();
        }
        return originSplit.concat(relativeSplit).join('/');
    }

}

With this class in place, developers can have better control over the loading of tilesets, thus enhancing the efficiency and flexibility of asset management.

@eonarheim
Copy link
Member

@JumpLink I like this idea! Let's do it!

I agree that being able to control loading external tilesets is super valuable!

eonarheim added a commit that referenced this issue Jan 6, 2024
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants