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

Added exporter for Game Maker Studio room files #1386

Merged
merged 6 commits into from
Nov 14, 2016

Conversation

JonesBlunt
Copy link
Contributor

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.

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.
Copy link
Member

@bjorn bjorn left a comment

Choose a reason for hiding this comment

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

@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>
Copy link
Member

Choose a reason for hiding this comment

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

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");
Copy link
Member

Choose a reason for hiding this comment

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

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.

Exporter for Game Maker Studio, now using QXmlStreamWriter.
@JonesBlunt
Copy link
Contributor Author

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()));
Copy link
Member

Choose a reason for hiding this comment

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

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
Copy link

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

@cursedtoast
Copy link

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 in QXmlStreamWriter function calls.
@JonesBlunt
Copy link
Contributor Author

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
Copy link

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.

Mistakenly used Tileset rowCount() instead of columnCount().
@JonesBlunt
Copy link
Contributor Author

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

@cursedtoast
Copy link

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
Copy link
Contributor Author

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.

Copy link
Member

@bjorn bjorn left a comment

Choose a reason for hiding this comment

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

Just some more comments on the code for now.

Thanks @cursedtoast for trying it out!


stream.writeStartElement("room");
stream.writeStartElement("caption");
stream.writeEndElement();
Copy link
Member

Choose a reason for hiding this comment

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

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!");
Copy link
Member

Choose a reason for hiding this comment

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

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));
Copy link
Member

Choose a reason for hiding this comment

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

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()) {
Copy link
Member

Choose a reason for hiding this comment

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

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()));
Copy link
Member

Choose a reason for hiding this comment

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

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));
Copy link
Member

Choose a reason for hiding this comment

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

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));
Copy link
Member

Choose a reason for hiding this comment

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

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. :-/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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;
Copy link
Member

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this shouldn't be there.

Copy link
Member

@bjorn bjorn left a comment

Choose a reason for hiding this comment

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

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;
Copy link
Member

Choose a reason for hiding this comment

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

Please declare this variable inside the loop.


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

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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())));
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member

@bjorn bjorn left a comment

Choose a reason for hiding this comment

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

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()));
Copy link
Member

Choose a reason for hiding this comment

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

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
Copy link
Member

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 mapeditor:master Nov 14, 2016
@bjorn
Copy link
Member

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
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants