mapeditor/tiled

JSON format improvements

Open

#3,212 建立於 2021年12月17日

在 GitHub 查看
 (5 留言) (0 反應) (0 負責人)C++ (10,413 star) (1,708 fork)batch import
help wantedrefactoring

描述

There are a number of inconsistencies and unconventional approaches in the JSON format, that make understanding and parsing it more difficult than necessary. This issue is about collecting those issues so that we may in the future fix them all at once, since in general we want to avoid breaking compatibility. It's also a place to collect feedback on the proposed changes.

Naming Style

There appear to be two common naming styles in JSON documents, which are camelCase and snake_case. However, Tiled's JSON format uses neither. It instead uses lowercasenames, which was originally chosen based on the same convention used in the TMX format.

The lowercasenames are often not very readable, and it makes it more difficult to rely on code generation tools like QuickType while still generating code that is pleasant to work with. Anybody writing a Tiled JSON parser will need to either use the same names, or rename many of them to more readable variants.

This also affects values for properties like draworder, which can be topdown for example, and that should probably become TopDown.

Suggestion: Switch all properties to camelCase.

Inconsistent Point Storage

Whereas layer objects have offsetx and offsety properties, tileset has a tileoffset property that is an object with x and y properties. I think these should always by stored as objects, with the only exception being if x and y are used without additional prefix, like for chunks.

Suggestion: Store points as objects with x and y properties, except when embedded without prefix. Alternatively, we can consider [x, y], which would be even shorter but might come at the expense of being less intuitive to parse.

Inconsistent Color Values

Colors are sometimes stored as "#AARRGGBB" and sometimes as just "#RRGGBB".

Suggestion: Consistently store colors as "#AARRGGBB" (or "0xAARRGGBB", which is likely more convenient to parse).

Inconsistent Inclusion of Defaults

Sometimes a property is left out when its value is the default, in other places it is always written. I think we need to decide on one or the other. Always writing a property has the advantage that the parser does not need to be aware of the default value. As such, maybe we should only leave out properties when their default can be trivially derived from their type (0 for numbers, false for booleans, "" for strings).

An additional problem however, is that the presence of some properties has additional meaning. For example, for template instances, whether or not they explicitly specify a value for properties like visible also tells Tiled whether the original value was overridden. Maybe it is better to explicitly store the names of overridden properties?

Suggestion: Only leave out properties when their default can be trivially derived from their type. Use a separate property to store information about overrides.

Inconvenient Defining of Object Types

In Tiled we support placing rectangles, ellipses, tiles, polygons, polylines, points and text as objects. In the JSON format, there is no single property that can be used to distinguish these different objects. Instead, we have a different property for each of them, and nothing for rectangles, since objects are rectangles by default:

  • "ellipse": true is used to turn an object into an ellipse
  • "point": true is used to turn an object into a point
  • "text": { ... } exists only when an object is a text
  • "polygon": [ ... ] exists only when an object is a polygon
  • "polyline": [ ... ] exists only when an object is a polyline
  • "gid": 123 exists only for tile objects

Unfortunately the type property is already taken since every object can have a user-defined string as its type. In C++, the various object types are distinguished by a Shape enumeration (currently with the exception of tile objects).

We could add a shape or objectType property to replace the ellipse and point properties, and allow renaming both polygon and polyline to just points. Or, to change the definition of an object such that only common properties like x, y, name, etc. are stored on the base object, and the differences move to child objects, as follows:

{
    "x": 123,
    "y": 123,
    "name": "Bob",
    "type": "NPC",
    "shape": {
        "type": "Point"
    }
}

{
    "shape": {
        "type": "Ellipse",
        "width": 10,
        "height": 10
    }
}

{
    "shape": {
        "type": "Polygon",
        "points": [ { "x": 0, "y": 0 }, { "x": 10, "y": 0 }, { "x": 0, "y": 10 } ],
    }
}

Suggestion: Introduce an object property that stores the properties relevant to each specific object type.

Layer Types

A similar approach as suggested above for objects could be used to split off the properties of the various layer types to a child property. The layers do currently have a type property, but if we change objects as above then I think we should do the same for layers.

Map Orientations

Some of the supported orientations, like staggered and hexagonal, require additional parameters. Currently these properties are stored directly on the map object, but I think the orientation property should be an object instead, and maybe it should be renamed to grid:

"grid": {
    "type": "Staggered",
    "staggerAxis": "X",
    "staggerIndex": "Odd"
}

Possibly the tilewidth and tileheight properties should be moved into that object as well (and maybe be renamed to spacingX and spacingY, because it is actually independent of the tile size, and this grid definition could be used for further custom grids and unified with the grid property on tilesets).

Embedded Tileset vs. Referenced Tileset

Currently a tileset object can be either a tileset reference, or an embedded tileset. I think it could be better to require them to be always tileset references, and use a separate property for storing embedded tilesets (if we still want to provide that option by the time we do these changes).

Tileset Image vs. Image Collection

These two are different enough that it's a bit awkward to fit them into the same object types. A tileset has a reference to an image, but if it's unset it is assumed to be a collection of images (in which case the rest of the parameters like spacing and margin are also ignored). And each tile also has a reference to an image, which is unset when it is used as part of a tileset. I think we should find a way to support both cases more elegantly.

A tileset could define a list of images, and for each image list the tiles on that image and the rectangle they occupy on that image. This also gets rid of the need to calculate this information based on the tileset parameters (which would become editor-only data, but might need to be stored on each image):

{
    "name": "Some Tileset",
    "images": [
        {
            "source": "<image-file>",
            "tiles": [
                {
                    "id": 1,
                    "x": 0,
                    "y": 0,
                    "width": 32,
                    "height": 32,
                    "properties": {}
                }
            ]
        }
    ]
}

Of course, such a change also implies changes in the editor UI and other supported formats. It is partly covered by issue #2863.

Use of Common Abbreviations

Just a note here, I would consider using common abbreviations, when done consistently, to shorten some of the property names:

  • images -> imgs
  • source -> src
  • width -> w
  • height -> h
  • properties -> props

Unified Hierarchy

This item may not really belong here, since it's a change that would affect Tiled's internals and all other formats as well. But in general, I don't like that we have a hierarchy only for layers. Objects should be able to form a hierarchy as well. And rather than introducing another hierarchy, I would suggest in this case to unify layers and objects, such that tile layers and image layers are just special kinds of objects (and this will automatically obsolete "object layers" and "group layers").

All the previous changes combined might already be such a big breakage in compatibility that it would be worth to think about making this change at the same time. Related issue is #572.

貢獻者指南