I usually create levels in Tiled and then export them to Defold, but sometimes I get lazy and make tweaks directly in Defold, which leaves the two out of sync. To fix this, I made an editor script that exports from Defold into a Tiled format.
Once installed, you can right-click on a tilemap and select “convert tilemap to tiled CSV.”
When activated, you’ll need to enter the tile’s width and height, since I couldn’t find a way to pull this from the editor.
Known limitations…
The editor scripting system doesn’t have a save-as dialog, so the script creates a file called “output_tiled_file.tmx” in the root project directory.
It also adds a dummy line referencing a fake tileset “tileset.tsx,” which you’ll need to replace in Tiled with your tileset or a new one created from the image used as the tilesource in Defold.
Hopefully it will be of some use to others.
local M = {}
M.tile_size_x = 0
M.tile_size_y = 0
local function active_function(opts)
-- get path
local path = editor.get(opts.selection, "path")
-- find extension
local start_index, end_index = string.find(path, ".tilemap", 1, true)
-- if found return true
if (start_index) then
return true
end
-- not found
return false
end
local function show_dialog()
local result = editor.ui.show_dialog(editor.ui.dialog({
title = "What are tiles width and height in pixels",
content = editor.ui.grid({
padding = editor.ui.PADDING.LARGE, -- add padding around dialog edges
columns = {{}, {grow = true}}, -- make 2nd column grow
children = {
{
editor.ui.label({
text = "tile width",
alignment = editor.ui.ALIGNMENT.RIGHT
}),
editor.ui.integer_field({
value = M.tile_size_x,
on_value_changed = function(number) M.tile_size_x = number end})
},
{
editor.ui.label({
text = "tile height",
alignment = editor.ui.ALIGNMENT.RIGHT
}),
editor.ui.integer_field({
value = M.tile_size_y,
on_value_changed = function(number)
M.tile_size_y = number
end})
},
}
}),
buttons = {
editor.ui.dialog_button({
text = "Cancel",
cancel = true,
result = false
}),
editor.ui.dialog_button({
text = "Perform",
default = true,
result = true
})
},
}))
return result
end
local function tile_bounds(tiles)
local min_x = 10000
local min_y = 10000
local max_x = -10000
local max_y = -10000
-- firstly calc bounds
for x, y, tile in tilemap.tiles.iterator(tiles) do
-- check x
if (x<min_x) then
min_x = x
elseif x>max_x then
max_x = x
end
-- check y
if (y<min_y) then
min_y = y
elseif y>max_y then
max_y = y
end
end
-- return results
return min_x,min_y,max_x,max_y
end
local function create_token(token,value)
return token.."=\""..value.."\""
end
local function export_tilemap(opts)
-- show dialog
if (show_dialog()==false) then
return
end
-- get path
local path = editor.get(opts.selection, "path")
-- test if layers exist
if editor.can_get(path, "layers") == false then
pprint("Not tilemap")
end
-- get layers
local layers = editor.get(path, "layers")
local min_x = math.huge
local min_y = math.huge
local max_x = -math.huge
local max_y = -math.huge
local width = 0
local height = 0
local map_width = -math.huge
local map_height = -math.huge
-- create a buffer
local tiledata = {}
local layer_height = {}
local layer_width = {}
local layer_id = {}
for layer_index = 1, #layers do
-- get layer id
layer_id[layer_index] = editor.get(layers[layer_index], "id")
-- get tiles
local tiles = editor.get(layers[layer_index], "tiles")
-- get bounds
min_x,min_y,max_x,max_y = tile_bounds(tiles)
-- check bounds
if (min_x==math.huge or min_y==math.huge or max_x==-math.huge or max_y==-math.huge) then
pprint("No tile data in layer: "..layer_id[layer_index])
return
end
-- calc width height
width = (max_x - min_x) + 1
height = (max_y - min_y) + 1
-- store height and width
layer_height[layer_index] = height
layer_width[layer_index] = width
-- calc buffer
local buffer_size = width * height
-- create nested table
tiledata[layer_index] = {}
-- initialize the buffer to 0 (Empty space)
for i = 1, buffer_size do
tiledata[layer_index][i] = 0
end
-- populate with actual tile data
for tx, ty, tile_id in tilemap.tiles.iterator(tiles) do
-- normalize coordinates so our bottom-left tile starts at 0,0
local normalized_x = tx - min_x
local normalized_y = height-(ty - min_y)-1
local index = (normalized_y * width) + normalized_x + 1
-- get the specific tile ID at this coordinate
tiledata[layer_index][index] = tile_id
end
-- update map bounds
if (width>map_width) then
map_width = width
end
if (height>map_height) then
map_height = height
end
end
-- open file
local file, error_message = io.open("output_tiled_file.tmx", "w")
if not file then
return
end
local string_data = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
file:write(string_data.."\n")
string_data = "<" .. create_token("map version","1.9")
string_data = string_data.." "..create_token("tiledversion","1.9.0")
string_data = string_data.." "..create_token("orientation","orthogonal")
string_data = string_data.." "..create_token("renderorder","right-down")
string_data = string_data.." "..create_token("width",tostring(map_width))
string_data = string_data.." "..create_token("height",tostring(map_height))
string_data = string_data.." "..create_token("tilewidth",tostring(M.tile_size_x))
string_data = string_data.." "..create_token("tileheight",tostring(M.tile_size_y))
string_data = string_data.." "..create_token("nextlayerid",tostring(#layers+1))
string_data = string_data.." "..create_token("nextobjectid",tostring(1))..">"
file:write(string_data.."\n")
-- write tile set line
string_data = "\t<tileset firstgid=\"1\" source=\"tileset.tsx\"/>"
file:write(string_data.."\n")
-- write layers
for layer_index = 1, #layers do
-- write header
string_data = "\t<"..create_token("layer id",tostring(layer_index))
string_data = string_data.." "..create_token("name",layer_id[layer_index])
string_data = string_data.." "..create_token("width",tostring(layer_width[layer_index]))
string_data = string_data.." "..create_token("height",tostring(layer_height[layer_index]))..">"
file:write(string_data.."\n")
-- add csv encoding
string_data = "\t\t<data encoding=\"csv\">"
file:write(string_data.."\n")
local data = tiledata[layer_index]
local l_width = layer_width[layer_index]
local l_height = layer_height[layer_index]
-- write data row by row for readability
for y = 0, l_height - 1 do
local row_data = {}
for x = 1, l_width do
local index = (y * l_width) + x
table.insert(row_data, tostring(data[index]))
end
-- concatenate the row with commas
local row_string = table.concat(row_data, ",")
-- add a trailing comma if it's not the very last row
if y < l_height - 1 then
row_string = row_string .. ","
end
file:write("\t\t\t" .. row_string .. "\n")
end
string_data = "\t\t</data>"
file:write(string_data.."\n")
string_data = "\t</layer>"
file:write(string_data.."\n")
end
string_data = "</map>"
file:write(string_data.."\n")
file:close()
end
function M.get_language_servers()
return {}
end
function M.get_commands()
return {
{
label = "Convert Tilemap to Tiled CSV",
locations = { "Assets" },
query = {
selection = { type = "resource", cardinality = "one" }
},
active = active_function,
run = export_tilemap,
}
}
end
function M.get_prefs_schema()
return {}
end
function M.get_http_server_routes()
return {}
end
return M
