Skip to content

Tiled: tile classes, resolved properties and a TMXObject type - #1763

Open
obiot wants to merge 2 commits into
masterfrom
feat/tiled-tile-class-properties
Open

obiot wants to merge 2 commits into
masterfrom
feat/tiled-tile-class-properties

Conversation

@obiot

@obiot obiot commented Oct 23, 2026

Copy link
Copy Markdown
Member

Three generic additions that make melonJS read what Tiled shows a tile object inheriting from its tile, without changing how any existing map loads or any existing TypeScript code compiles.

A tile object is matched by its tile's class

In Tiled, a tile object with no class takes its tile's class (MapObject::effectiveClassName(): the object's own, else its template's, else its tile's). melonJS now does the same for dispatch: give the chest tile the class "Chest" in the tileset editor, and every chest placed in the map is built by registerTiledObjectClass("Chest", Chest).

To keep existing maps loading as before:

  • it comes after the object's own class and its registered name, before the structural fallbacks;
  • it only selects a class or factory the game registered itself: a tile classed like a structural type (shape, text, tile) or an engine built-in (Sprite, Trigger, Collectable...) stays a plain tile;
  • settings.class is left as it was.

TMXTileset#getTileClass(gid) returns the class, read from type (Tiled 1.10 and older) or class (1.9).

resolvedProperty() / resolvedProperties()

Tiled shows a tile's custom properties on every object placed from it. TMXObject#resolvedProperty(name) and resolvedProperties() return the object's custom properties merged over its tile's, named after the methods that do the same in Tiled's scripting API.

The tile's properties are deliberately not set on the object by name. The built-in tile factory hands the whole object to the sprite as its settings, so a tile property called flipX or tint would have changed how existing maps draw (a test pins this: flattening them flips the sprite). The object's own properties are still set on it by name, exactly as before; they are now parsed once into a private bag first, so an eval: property is still evaluated only once.

TMXObject type

TMXObject was internal and stripped from the declarations, so every TypeScript game redeclared the settings its Tiled classes receive. It is now exported as a type and documented, with tile: Tile | undefined and gid: number | null. Declare custom properties by extending it:

interface DoorSettings extends TMXObject {
    locked?: boolean;
}
class Door extends Sprite {
    constructor(x: number, y: number, settings: DoorSettings) { ... }
}

Both registration functions are now generic over the settings, defaulting to TMXObject: an unannotated factory's settings is typed, and a class or factory annotated with its own settings type keeps compiling. A non-generic signature broke exactly that, which the type spec pins.

Docs

JSDoc on TMXObject (with an example), the new methods, getTileClass and both registration functions; the tilemaps skill gets a section on classes and properties set on tiles, the matching order, and two symptom rows.

Verification

  • New tests in tmxtilemap.spec.js (18), tmxtileset.spec.js (5) and a type-level tiled-object-types.spec.ts (7, checked by tsc): both class spellings, flipped gids, precedence, built-ins left alone, resolved properties (precedence, fresh object, fields excluded, non-tile objects), a TMX file end to end, and the two backward-compatibility guards.
  • Mutation testing: 18 of 19 mutations caught (dispatch removed or reordered, raw gid, built-ins allowed, class mutated, precedence flipped, the tileset's object handed out, flattening, typings reverted or made non-generic). The one missed, dropping the JSDoc type on tile, is equivalent: tsc infers the same type.
  • Full suite: 8,334 passed, 10 skipped. Lint, declaration build (consumer fixture) and docs build clean; the examples' registration calls type-check unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx

A tile object takes what Tiled shows it inheriting from its tile, without
changing how existing maps load:

- a tile object with no class of its own is built by the class registered
  under its tile's class (Tiled's effective class: object, then tile). It
  comes after the object's own class and registered name, and only for a
  name the game registered itself: a tile classed like a structural type
  ("shape", "text", "tile") or an engine built-in ("Sprite", "Trigger"...)
  stays a plain tile. settings.class is left as it was
- TMXObject#resolvedProperty(name) and resolvedProperties() return the
  object's custom properties merged over its tile's, as the methods of the
  same names in Tiled's scripting API do. The tile's properties are NOT set
  on the object by name: the built-in tile factory hands the whole object
  to the sprite as its settings, so a tile property named like a sprite
  setting (flipX...) would have changed how existing maps draw. Own
  properties are parsed once into a private bag and set on the object as
  before (no eval: property is evaluated twice)
- TMXTileset#getTileClass(gid) returns a tile's class, read from `type`
  (Tiled 1.10 and older) or `class` (1.9)

Typings: TMXObject is exported as a type and documented (it was internal
and stripped), with tile: Tile | undefined and gid: number | null. Both
registration functions are generic over the settings type, defaulting to
TMXObject: an unannotated factory gets TMXObject, and a class or factory
typed with its own settings still compiles. A non-generic signature broke
exactly that (mutation T2).

Tests: tile classes in both spellings, flipped gids, precedence (own
class, registered name), built-ins left alone, resolved properties
(precedence, copies, fields excluded, non-tile objects), a TMX file end
to end, and two backward-compatibility guards (no tile property on the
object, none reaching a plain tile sprite). A type-level spec checks the
generic registrations, including the legacy annotations. 18 of 19
mutations caught; the one missed (dropping the JSDoc type on `tile`) is
equivalent, as tsc infers the same type. Full suite 8,334 passed; the
examples' registration calls type-check unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx
Copilot AI balanced review requested due to automatic review settings October 23, 2026 07:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

A class that is not a Sprite itself (a Container holding the tile and
something else) gets the tile's sprite from settings.tile.getRenderable(),
as a plain tile object does, instead of building region names by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx
Copilot AI balanced review requested due to automatic review settings October 23, 2026 08:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
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 this pull request may close these issues.

2 participants