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

Added exporter for Game Maker Studio room files #1386

Merged
merged 6 commits into from Nov 14, 2016

Conversation

Projects
None yet
3 participants
@JonesBlunt
Copy link
Contributor

JonesBlunt commented Oct 27, 2016

Objects are exported into instances, they just need a name. Using an optional Depth property on both tile layers and object layers will export using that depth.

Add files via upload
Added exporter for Game Maker Studio room files.

Objects are exported into instances, they just need a name. Using an optional Depth property on both tile layers and object layers will export using that depth.
@bjorn
Copy link
Owner

bjorn left a comment

@JonesBlunt Thanks for working on this! I've yet to try it out, but for now I've provided some inline comments. One of them means almost completely rewriting it, but I think it's needed to resolve possible encoding and escaping issues.

I assume this is meant to close issue #1369? In that case please mention this at the bottom of your commit message (like: Closes #1369).

@cursedtoast If you want to try this out to see if it fits your needs, know that you can find automated installer builds for pull requests by following the link to the AppVeyor continuous integration service. Just click on Details, then choose 32-bit or 64-bit and then click to Artifacts. You'll need to get the .7z archive until this new plugin has been added to the installer.

@@ -0,0 +1,154 @@
/*
* GMX Tiled Plugin
* Copyright 2014, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>

This comment has been minimized.

@bjorn

bjorn Oct 28, 2016

Owner

Please put your name here. :-)

file.write(" <height>" + QByteArray::number(map->tileHeight() * map->height()) + "</height>\n");
file.write(" <vsnap>" + QByteArray::number(map->tileWidth()) + "</vsnap>\n");
file.write(" <hsnap>" + QByteArray::number(map->tileHeight()) + "</hsnap>\n");
file.write(" <isometric>" + QByteArray::number((map->orientation() == Tiled::Map::Orientation::Isometric ? 1 : 0)) + "</isometric>\n");

This comment has been minimized.

@bjorn

bjorn Oct 28, 2016

Owner

Since you're writing an XML file, please use QXmlStreamWriter instead. It will automatically take care of the formatting, the encoding and any necessary escaping of values.

Closes #1369
Exporter for Game Maker Studio, now using QXmlStreamWriter.
@JonesBlunt

This comment has been minimized.

Copy link
Contributor

JonesBlunt commented Oct 28, 2016

Hey @bjorn thanks for the feedback. I appreciate the advice regarding switching to QXmlStreamWriter, it's pretty useful. My latest commit hopefully addresses all your concerns, let me know what you think.

stream.writeStartElement("caption");
stream.writeEndElement();

stream.writeTextElement("width", QByteArray::number(map->tileWidth() * map->width()));

This comment has been minimized.

@bjorn

bjorn Oct 29, 2016

Owner

I'll have a closer look on Monday latest, but in the meantime, please use QString::number instead because all the QXmlStreamWriter functions take QString.

@cursedtoast

This comment has been minimized.

Copy link

cursedtoast commented Oct 30, 2016

Wow!! Thank you Jones!
I am going to give this a test ASAP.

@cursedtoast

This comment has been minimized.

Copy link

cursedtoast commented Oct 30, 2016

Hi @JonesBlunt
First off, thank you for fulfilling this. Myself and the rest of the GMS community thank you. We've needed this. So seriously, thank you. This helps a lot.

Second, maybe this is user error, but I'm running into an issue with the wrong tiles being mapped out in GMS.

1.) I draw my map in Tiled, and export it in .room format for GMS.
2.) I add a matching background as a tileset in GMS with the same name as the tileset in Tiled.
3.) I add an existing room in GMS, and choose the file Tiled exports.
4.) I see the wrong tiles but the coordinates of the tiles are correct.

Here's two screenshots:
What the map looks like in Tiled:
http://l.eaf.io/LF6KroZk.png

What it looks like in GMS:
http://l.eaf.io/fADGHdiQ.png

Am I doing things correctly or is this user error? Do you need a copy of GMS pro to test with? I can provide you with one. I can also provide you with the tileset I'm using.

Again, thanks a million for this!

Removed QByteArray::number references
Removed QByteArray::number references in QXmlStreamWriter function calls.
@JonesBlunt

This comment has been minimized.

Copy link
Contributor

JonesBlunt commented Oct 31, 2016

Hey there @cursedtoast!
Sounds like you set up the map perfectly, so that's a little odd. There's a layer related fix in the second commit, though it looks like you're probably using 1 layer.

The tileset could be useful to see, did you set any offsets by chance?

Thanks for trying it.

@cursedtoast

This comment has been minimized.

Copy link

cursedtoast commented Oct 31, 2016

Hi @JonesBlunt
A lot of people are very happy you're working on this :D

Here is the tileset I'm using, there is no offsets being used. I configure it as 32x32 tiles in GM as well as Tiled.

Tileset: http://l.eaf.io/DpxyOqSv.png

Someone on Reddit also had this issue with one of their tilesets, but one of them was fine.

Here is the one that worked fine for them:
https://www.dropbox.com/sh/cql1w0n6sa7qfvo/AAADkXducM8gqQvX2MGY6mFTa?dl=0&preview=bg_tiles.png

Here's the one that caused this issue:
https://www.dropbox.com/sh/cql1w0n6sa7qfvo/AAADkXducM8gqQvX2MGY6mFTa?dl=0&preview=spikes.png

Let me know if you're able to duplicate the problem with the tileset I'm using. Maybe you can find a correlation between both problematic sets.

Fixed tile id calculations
Mistakenly used Tileset rowCount() instead of columnCount().
@JonesBlunt

This comment has been minimized.

Copy link
Contributor

JonesBlunt commented Oct 31, 2016

Ok @cursedtoast, I managed to reproduce it. It was a simple mistake, only revealed when textures weren't power of two textures.

@cursedtoast

This comment has been minimized.

Copy link

cursedtoast commented Oct 31, 2016

Thanks @JonesBlunt I'll give it a test shortly. I appreciate your responsiveness.

Someone on Reddit wanted to know if rotations in Tiled work in GM, did you by chance code that? Just relaying the question, no huge worries if it's not supported yet - someone can get around to it eventually :D

@JonesBlunt

This comment has been minimized.

Copy link
Contributor

JonesBlunt commented Oct 31, 2016

There is currently no support for rotations. I will try to implement object rotations in the next commit, though tiled rotations would require more work considering there are no native tile rotations in GM as far as I'm aware.

@bjorn
Copy link
Owner

bjorn left a comment

Just some more comments on the code for now.

Thanks @cursedtoast for trying it out!


stream.writeStartElement("room");
stream.writeStartElement("caption");
stream.writeEndElement();

This comment has been minimized.

@bjorn

bjorn Oct 31, 2016

Owner

There is writeEmptyElement for this.

stream.setAutoFormatting(true);
stream.setAutoFormattingIndent(2);

stream.writeComment("This Document is generated by GameMaker, if you edit it by hand then you do so at your own risk!");

This comment has been minimized.

@bjorn

bjorn Oct 31, 2016

Owner

I'm sure you mean to write "Tiled" instead of "GameMaker" here. :-)

stream.writeAttribute("x", QString::number((int)(object->x())));
stream.writeAttribute("y", QString::number((int)(object->y() - object->height())));

stream.writeAttribute("id", QString::number(++objectId));

This comment has been minimized.

@bjorn

bjorn Oct 31, 2016

Owner

Is there a reason you can't use object->id() here? Does it need to be a consecutive numbering?

stream.writeStartElement("instances");

uint objectId = 0u;
foreach (const Layer *layer, map->layers()) {

This comment has been minimized.

@bjorn

bjorn Oct 31, 2016

Owner

You can use a more modern loop here:

for (const Layer *layer : map->layers()) {
stream.writeAttribute("x", QString::number((int)(x * map->tileWidth())));
stream.writeAttribute("y", QString::number((int)(y * map->tileHeight())));
stream.writeAttribute("w", QString::number(map->tileWidth()));
stream.writeAttribute("h", QString::number(map->tileHeight()));

This comment has been minimized.

@bjorn

bjorn Oct 31, 2016

Owner

The tiles do not need to be the same size as the map tile size, so you should use tile->size() here.


stream.writeAttribute("yo", QString::number((int)(tile->id() / tile->tileset()->columnCount()) * tile->tileset()->tileWidth()));

stream.writeAttribute("depth", QString::number(layer->hasProperty(QLatin1String("Depth")) ? layer->property(QLatin1String("Depth")).toInt() : currentLayer));

This comment has been minimized.

@bjorn

bjorn Oct 31, 2016

Owner

Please initialize the depth only once per layer.

stream.writeAttribute("depth", QString::number(layer->hasProperty(QLatin1String("Depth")) ? layer->property(QLatin1String("Depth")).toInt() : currentLayer));
stream.writeAttribute("id", QString::number(++tileId));
stream.writeAttribute("scaleX", QString::number(1));
stream.writeAttribute("scaleY", QString::number(1));

This comment has been minimized.

@bjorn

bjorn Oct 31, 2016

Owner

Is it necessary to write all these out? The file could be kept smaller if GameMaker defaults to 1 anyway. Using an XML element for every tile is already rather expensive. :-/

This comment has been minimized.

@JonesBlunt

JonesBlunt Nov 1, 2016

Contributor

Other than for rotations on objects which require a scaleY inverse, scaleX/Y are indeed redundant. All tiles and objects need to have a unique id attribute or the room will fail to load in GM. object->id() does work well for objects, though tile->id() will not.

result.append(completePath);

if (result.size() == 1)
result[0] = fileName;

This comment has been minimized.

@bjorn

bjorn Oct 31, 2016

Owner

What are you trying to achieve here? This function is effectively always just returning fileName, which is what the default implementation already does.

This comment has been minimized.

@JonesBlunt

JonesBlunt Nov 1, 2016

Contributor

Yes, this shouldn't be there.

@bjorn
Copy link
Owner

bjorn left a comment

Some more comments.


if (layer->layerType() != Layer::ObjectGroupType) continue;
const ObjectGroup *objectLayer = static_cast<const ObjectGroup*>(layer);

foreach(const MapObject *object, objectLayer->objects()) {
bool rotating = false;

This comment has been minimized.

@bjorn

bjorn Nov 1, 2016

Owner

Please declare this variable inside the loop.


stream.writeAttribute("id", QString::number(++objectId));
stream.writeAttribute("scaleY", QString::number(rotating ? -1 : 1));

This comment has been minimized.

@bjorn

bjorn Nov 1, 2016

Owner

What is the idea behind inverting the Y axis when an object is rotated? Doesn't that suddenly flip the entire graphic around?

This comment has been minimized.

@JonesBlunt

JonesBlunt Nov 1, 2016

Contributor

Unless I'm mistaken, the origin for Tiled rotations is always the bottom left vertex, whereas in GM it's the top left tile vertex. ScaleY did indeed flip the graphic, which appeared to be reset to it's original graphic after rotating the object, though that was just a glitch in GM's room editor. My current solution disregards scaleY but uses QTransform() for computing x/y offsets to correctly translate objects following rotation.

stream.writeAttribute("w", QString::number(map->tileWidth()));
stream.writeAttribute("h", QString::number(map->tileHeight()));
stream.writeAttribute("x", QString::number((int)(x * tile->width())));
stream.writeAttribute("y", QString::number((int)(y * tile->height())));

This comment has been minimized.

@bjorn

bjorn Nov 1, 2016

Owner

When I asked you to use the tile size, I meant just for w and h. The position of the tile depends on the map tile size, not on the size of each tile.

@bjorn
Copy link
Owner

bjorn left a comment

It looks like image layers could be exported as (non-tiling) backgrounds?

Also just a comment to add back the grid snap.

Overall it looks fine to me. I guess to be really useful, some more GM properties should be supported, like the various physics related properties and well as settings like "speed", "persistent", etc.

stream.writeTextElement("height", QString::number(map->tileHeight() * map->height()));

stream.writeTextElement("vsnap", QString::number(map->tileHeight()));
stream.writeTextElement("hsnap", QString::number(map->tileWidth()));

This comment has been minimized.

@bjorn

bjorn Nov 7, 2016

Owner

Please add back these vsnap and hsnap variables, because without them GameMaker will display empty red-colored input boxes for these fields. Writing the tile size in there seems reasonable.

@bjorn

This comment has been minimized.

Copy link
Owner

bjorn commented Nov 7, 2016

Since I was just trying it, I also noticed there is a GameMaker Studio 2 beta going now and I've shortly played around with it. They have vastly improved their tile map editor capabilities as well as changing their map format from XML to JSON (and storing tile references much more efficiently, basically like Tiled is doing it). I don't think there will be too many people still looking for a better tile map editor, but it would of course be interesting to support exporting to the new format eventually as well.

@bjorn bjorn merged commit 5172ad3 into bjorn:master Nov 14, 2016

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@bjorn

This comment has been minimized.

Copy link
Owner

bjorn commented Nov 14, 2016

Alright, I decided to merge this as-is and make some adjustments myself that I think were necessary:

  • Use QSaveFile (8814db8). In your original code you did, but you somehow took it out while moving to QXmlStreamWriter. Was there any particular reason why, @JonesBlunt?
  • Added support for several GameMaker properties (6625eff). Since I figured otherwise it would be quite annoying if you need to change these after every export. It also adds back the vsnap and hsnap attributes like I had requested.
  • Sanitize names and changed the way type and name are used (7c33041). GameMaker is quite restrictive about what can be in a name, and won't load the exported map when it contains any invalid characters in the name. Also, I thought it made more sense to map the object type to objName and the name to name (instance name).
  • Adding the plugin to the Windows installer (c2e6bb2).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment