Tiled Editor map and tileset integration with Godot 3.2

Hello,

TLDR:

Should I work on the Godot Import Plugin from Tiled or make a custom Tiled Export Plugin for .scene and .tres files (.tmx and .tsx accordingly)?

This question arose because the existing plugin has performance issues and crashes the editor.

For more details about the situation please read below till the end.


I’ve used Tiled through the years for small web games mainly with Phaser.

Over the last 2-3 months I’ve been playing with and loving Godot 3 so instinctively I went for Tiled as a tilemap and a level editor.

An issue came up with importing the .tmx and .tsx files in the Godot editor.

The existing plugin for TMX import to Godot is: https://github.com/vnen/godot-tiled-importer.

It works but…

  1. It’s made some time ago and after import, it creates tilesets with Single Tiles - this results in tilesets with 500+ tiles for a regular 16x16px sprite sheet (704x196 sized image) which makes the Godot editor crash on import.

This can be avoided by setting the project settings in Godot:

Message Queue -> Max Size Kb-> 102400 (or other big value by default it’s 1024)

But we all know that this is a workaround and not a solution.

  1. The existing plugin parses the .tmx / .tsx files and creates Scene files and Tilemap nodes in Single Tile mode. At some point during the Godot Engine development, a new Tileset mode was added - Atlas mode which has far better performance.

At the moment, if you try to import a tilemap through the plugin with a size bigger than 500 x 500 tiles, the Godot editor crashes with no error or log message…I suppose it just runs out of memory.

I’ve made some performance tests with a Tilemap made in Godot Engine with Atlas mode and a map of 2300x1000 tiles and it doesn’t even sweat running on 60fps. The same experiment with the imported .tmx file and Single Tiles dropped the frame rate to 20fps with a map of 812x215 tiles (both maps are 16x16px tiles) so surprise surprise - Atlas is faster.

The Tiled Editor does a far better job of drawing, layering and editing maps. Maybe in the future Godot will have all the cool features built-in but for the time being it doesn’t. Building complex tilemaps with several layers is cumbersome.

So to fix my problem I see two paths:

  1. I can try to edit the Godot import plugin and make the changes to import the .tmx file with the new Atlas mode.

  2. To write an export plugin using the new JS API for Tiled 3.2 which will export the tilesets to .tres files which Godot uses, and the .tmx to .scene files.

Both Godot files are text-based formats. More info about the .tscn: https://docs.godotengine.org/en/3.2/development/file_formats/tscn.html

So as I already looked through the Godot plugin code and all the XML equilibristics (500+ rows of code) I’m more inclined to make a Tiled export plugin since it looks simpler and less error-prone.

As I see the first things should be:

Exporting a tileset as .tres with all the tiles with their position and collision and reference to the image sprite sheet.

Exporting a .scene file with Tilemap node and the Tilemap position of each tile and its collision polygon if such a polygon exists.

For each layer form Tiled, a new sub-scene will be created

Other features may come later one by one once the basics are working.

Features like custom properties, navigation maps and so on.

This long post was born after more than a month of struggle with Godot TileMaps.

This topic is published as-is in the Godot Forum/Reddit as well because I will really appreciate any recommendations regarding the process of creating 2D maps and levels, and the general support of both Godot and Tiled communities.

2 Likes

Hello @Mihail_Ilinov! Thanks for bringing up this topic here. I had no idea the existing Godot importer was suffering such performance issues and it’s great to hear you want to do something about it!

Personally, I think it’s preferable to write an exporter for Godot than to have an importer plugin. Whether to write this exporter in C++ or JavaScript would depend on what you feel most comfortable with. Either way I’d be there to help you with the project and I would be eager to ship a good working Godot extension along with Tiled.

Of course you may want to consider the resulting workflow differences between an importer and an exporter. How many steps would be required to see a change in the game after making it in Tiled?

In any case, please keep us updated and let me know if I can help with anything!

Hi @bjorn,

So it’s been a while and now I have enough free time to progress with the export plugin task.

I have two questions since today:

  1. When I do console.log(tileset) or tiled.log(tileset) in write: function(tileset, fileName) { … }
    What I see in the console is:
    Tiled::EditableTileset(0x9dbb40)

I’ve tried JSON.stringify but this crashes the Tiled editor.

  1. Also when I call tileset.image what is returned is the absolute path…
    But as I check the .tsx and the .json exports the path to the tileset image is relative and
    a relative path is what I’m looking for too since Godot uses a res:// path which is the current project root folder and not absolute path like c:/something/something

Other than the two questions above.
I managed to export hard coded .tres and .tscn files which (not surprisingly) work in Godot.

And about your question compared to the import plugin which exists there will be no difference in steps.
After you export a map or tileset the engine reloads them on the fly.

If you edit something from the Godot editor and then export again… well you overwrite your changes.
This is understandable so maybe after all the export work is done I could make a read function which will parse and load .tscn files but this looks like a lot more work and I’m not sure if it’s really needed.

Btw if I would get better debugging and code completion if I go the C++ way for the export please tell me.
Cause although I’ve used JS more I really want to have an easier debugging environment.

And one more strange thing that happens.

After I export a map or tileset from Tiled and even thou the content of the file is valid.
I cant load them in Godot - like the file is missing or not there.
I can load them in Sublime or any other text and everything is fine.

Then after i close Tiled editor (i suspect that it keeps some reference to the file node) i can load
the same files in Godot with no problem and everything works fine.

So the problem is after export Tiled do something and need to be closed before i can use the exported files in Godot.

edit 2:

And one more question before I go to sleep.

I see the example export script loops trough layer:

for (var i = 0; i < map.layerCount; ++i) {
    var layer = map.layerAt(i);
    if (layer.isTileLayer) {
        for (y = 0; y < layer.height; ++y) {
            for (x = 0; x < layer.width; ++x) {

I test it with an infinite map and the code above loops only positive tiles (with coordinates like [0,0] [0,1] [1,1] [1,0] and so one but all tiles which are [-1,0][0,-1][-1,-1] are not detected and then miss in the export.

So how do I traverse infinite maps?

Other than that the example script exports even blank tiles so I’ve added this check:
layer.cellAt(x, y).tileId !== -1
So only tiles with a reference to the tileset are exported all the others are considered blank.

I’ll look into making that not crash, but what do you expect to happen in this case?

Right, relative paths are resolved to absolute paths on load and turned into relative paths again on save. We probably need to add some script API that helps with converting between the two. On the C++ side, we have QDir::relativeFilePath for this.

If you develop in C++ you have the regular debugging support of your IDE. I personally use Qt Creator, which supports gdb, lldb and cdb debuggers, but you should be able to use the Visual Studio IDE as well if you prefer (see README.md).

If C++ is easier for you then by all means write a C++ plugin (you can take the existing plugins as example). In this case we should distribute the plugin alongside Tiled for binary compatibility reasons (just open a pull request).

So, first thing I’m wondering, does your script call file.close() at the end?

From the script there appears to be no efficient way to iterate only non-empty chunks at the moment, but you can iterate the bounding box of the layer’s contents using layer.region().boundingRect (this will include contents at negative coordinates).

Hi, thanks for the quick answers.

About the JSON. stringify I expect to see some JSON with the object and its properties or some string representation that can point me of the content.

@bjorn:
We probably need to add some script API that helps with converting between the two. On the C++ side, we have QDir::relativeFilePath for this.

I’ll look forward to the relative path and for now, I think I’ll try to get the map folder if there is a way and subtract it from the other absolute path so I can have the relative as a result. Since I don’t see any other way to get it to work in Godot.

I’ll try to finish the export plugin in JS but if C++ offers me more API functions well it won’t be a hard choice :slight_smile: And to be really honest it’ll be kinda fun to write some C++ in a quite controlled environment :slight_smile:

It works now :slight_smile: Dunno why I thought file.commit will also close it. That’s very nice now cause I don’t need to close Tiled after export and Godot detects the changes after scene reload.

So at the moment, I can export a basic map and basic tileset, I’ll try the infinite one and maybe till the weekend, I’ll have a beta version I could share.

After that, we could think of converting it to C++ plugin would be the better way.

1 Like

Actually I guess it should, since there’s nothing you can do with the file after the commit. It’s a bit silly to require the manual close in that particular case.

Also, I just noticed that I’m not calling close() in the example in the docs either. :stuck_out_tongue:

Yeah, you could definitely do some string manipulation in JavaScript. The asset.fileName property gives you the full absolute file path.

Ok I have just tested it, there’s only a small difference:

let rect = layer.region()
let rect_json = JSON.stringify(rect);

console.log(rect);
console.log(rect_json );

results in:

qml: Region(…)
qml: {“boundingRect”:{“x”:-6,“y”:-6,“width”:32,“height”:20,“left”:-6,“right”:25,“top”:-6,“bottom”:13}}

And the inner property is boundingRect - with the x,y and width and height I think it’s enough to loop trough all tiles. And also the id check (!== -1) shouldn’t hurt the performance too much.

Other than that I downloaded and tried to install Qt with Qt Creator but it stuck on the installation on 4% I’ll try again later on. Btw I’m using JetBrains IDE’s so I’ll have to check if CLion configuration.

Right, typo on my side, sorry. I’ve edited my post to avoid confusion for new readers. :slight_smile:

Yeah, it seems their servers are having problems at the moment? I couldn’t run the updater on my laptop just now either, it was timing out a lot and getting stuck as well.

Would be nice if you can get CLion to work, but right now there is no CMake project file for Tiled, just a qmake and a Qbs project, and I recommend using Qbs. If CLion can work with Makefiles you can try generating them using qbs generate -g makefile.

I see. I’ll go with the Qt Creator when the installation completes …it’s at 12% after an hour.

I’ve made the infinite map to work with Godot.
And also tried logging map, layer or anything more complex object which makes the editor crash (something with the console maybe). That’s make it kinda hard to see for example what’s in map.tilesets).

Besides, that didn’t quite get your proposition for the asset.fileName.
From which asset should I try to get the root folder of the .tmx map?
From the map reference? and then I should remove the map.tmx form the end of the string to get the root folder?

Try using Object.keys() to inspect objects, and of course you can read the scripting API reference, which should be pretty complete.

I’ve tried using JSON.stringify on a few objects, include a tileset, but I’m consistently getting a “TypeError: Type error” instead of a crash. If you have a script with which you can reproduce this crash, please open an issue about it on GitHub.

Yes, just find the last / character and cut the file name off.

To test it also just make a new empty map and then export as Custom Map Format.
Here I’ve made a sample you can run and inspect:

Other than that sometimes on some objects, I got the type error too.

Also what’s more strange is although the map inherits the asset
the map reference I get in write doesn’t have value in fileName or fileName at all.

// Both return empty string
console.log(map.asset.fileName);
console.log(map.fileName);

Hello, @bjorn

I couldn’t quite run the source code in the Qt Creator. When I click Run it wants an executable and i tried several things but with no success … I’ll keep on reading the Qt docs.

Edit:
OK after some trial and error I think I’ve realized my mistake.
When installing Qt framework I choose MinGW but only on the dev tools.
Maybe adding some screenshots to the readme/guide for contributing will help others to get started.

Now the Debug build is working (although i have some warnings about missing .dll everything seems to work and Tiled is launched after build and works in the latest 1.3.3 version).

Qt Setup:

The warnings:

Got something more :slight_smile:

I’ve updated the GitHub issue with some more information about the logging and crashing.

The problem with a console.log or JSON.stringify is the circular reference:
tileset.tiles[0].tileset.tiles[0].tileset… and so on…
More on that:


and the workaround is using this small package:

Then you can use it like:

    console.log("--- tileset start ---");
    console.log(Flatted.stringify(tileset));
    console.log("--- tileset end ---");

That way the console in the Editor shows a text representation of the whole object.
And to my surprise, everything is there and works.
So tileset provides everything needed.

1 Like

One month later I’m ready and releasing the extensions for Tiled.

It can be download them from GitHub:

Also, I’ve made a short video tutorial:

The versions I used in the video are Godot 3.2.1, Tiled 1.3.3
Cheers.

2 Likes

That is some amazing work, @Mihail_Ilinov! I’m excited to see whether others will pick up this exporter as well and it’s a great demonstration of the JavaScript extension system. I’ve added it to the README of the tiled-extensions repository.

Btw, in your video Tiled ran into an assertion failure. Would be great if you knew how to reproduce that since it looks like something needs fixing. Might be a little hard because it was while detaching some tiles… :confused:

Thanks for the feedback and for the fast contributions in GitHub.
Yesterday was a bit late and didn’t turned much attention to the crash but if it happens again I’ll report what i found.

I received the first issue today :slight_smile:

leemayn wrote on reddit:

I messed with it a bit this morning, and ran into a small issue when exporting tilesets. Godot wasn’t reading them properly, and when I looked at the generated tscn file, the region rect width and height were set to “undefined” or something similar. I tested it with several tilesets. They’re 256x256 pixels with 16x16 sized tiles. Godot won’t open the .tres file and throws an error: Expected float in constructor. I manually add the width and height using a text editor and then Godot opens them without issue
I’m using Windows 10 and I have the newest stable version of both Tiled and Godot. (I believe that’s Tiled 1.3.3 and Godot 3.2.1, but I’m not at my computer at the moment.)

And I wrote:

Ок. I’ve just downloaded again Tiled 1.3.3 on different laptop and I can confirm that tileset is missing those two properties: imageWidth and imageHeight. So to fix this we will have to wait until Thorbjørn release the next patch version 1.3.4 - the harder option is to setup Qt and build the current dev version…but if I were you i would wait and put it manually till then :wink:

So @bjorn the quick question is … Is there a planned release for the 1.3.4 :stuck_out_tongue:

Congratulations! :slight_smile:

Hmm, actually Tiled 1.3.4 would only fix a few bugs. The Tileset.imageWidth and Tileset.imageHeight properties were added back in January, but they will be in Tiled 1.4.

In the meantime though, they’re already available in the development snapshots, so I’d suggest you add a note to the README that users should use the latest snapshot.

Edit: In the end I decided to backport these two properties to Tiled 1.3.4, which is now released.

1 Like

Hello!

I made this account only to say thank you for this exporter! I’ve followed your progress since your initial reddit post, you have no idea how useful this’ll be for my project :smiley:

2 Likes