Here’s a demo of a tile editor I’ve been working on.
And a sample of the editor panel, which displays the tiles that are available:
Models tagged as tiles are immediately usable by the editor, allowing tiles to be created on the fly as needed (the demo shows me creating water, edge, hedge, and plant tiles). The content of each tile is monitored for changes, so modifications are immediately made visible in the panel and on the map.
Since a map refers to tiles by name, missing tiles are replaced by an error tile. For example, here’s the demo map with the tree tile missing:
Currently, the editor only has a draw tool that draws individual cells, though that alone has gotten me pretty far. But it does have options for rotating, flipping, and deleting tiles. There are also operations for shifting regions of cells, and querying/replacing cells, which I’ve used to normalize the flip and rotation of visually ambiguous tiles like grass and trees.
Maps can be saved and loaded, the data being stored in ModuleScripts. The data is serialized in a binary format by using Bitbuf, then converted to hexadecimal for easier debugging.
A low-level detail that I like: in memory, each cell is represented by a 51-bit integer composed of the position, tile, rotation, and flip components. Storing this integer in a number as a table key enables trivial manipulation of cells, while also ensuring that no cell is duplicated. Rather than using bitwise operations, which are limited to 32-bit integers, composition uses mathematical equivalents, allowing up to 53 bits to be used before precision loss causes problems:
local function cell_compose(position, id, rotation, flipx, flipz)
local c = 0
c += math.floor(position.X) * 2^00
c += math.floor(position.Y) * 2^08
c += math.floor(position.Z) * 2^16
c += math.floor(id) * 2^24
c += math.floor(rotation) * 2^48
c += math.floor(flipx) * 2^50
c += math.floor(flipz) * 2^51
return c
end
local function cell_decompose(c)
local x = math.floor(c / 2^00) % 2^08
local y = math.floor(c / 2^08) % 2^08
local z = math.floor(c / 2^16) % 2^08
local i = math.floor(c / 2^24) % 2^24
local r = math.floor(c / 2^48) % 2^02
local u = math.floor(c / 2^50) % 2^01
local v = math.floor(c / 2^51) % 2^01
return Vector3.new(x, y, z), i, r, u, v
end