-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Conversation
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.
There was a problem hiding this 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> |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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.
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())); |
There was a problem hiding this comment.
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
.
Wow!! Thank you Jones! |
Hi @JonesBlunt 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. Here's two screenshots: What it looks like in GMS: 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.
Hey there @cursedtoast! The tileset could be useful to see, did you set any offsets by chance? Thanks for trying it. |
Hi @JonesBlunt 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: Here's the one that caused this issue: 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().
Ok @cursedtoast, I managed to reproduce it. It was a simple mistake, only revealed when textures weren't power of two textures. |
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 |
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. |
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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!"); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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())); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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. :-/
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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; |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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()))); |
There was a problem hiding this comment.
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.
There was a problem hiding this 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())); |
There was a problem hiding this comment.
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.
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. |
Alright, I decided to merge this as-is and make some adjustments myself that I think were necessary:
|
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.