Tiled Map Engine

Caleb, I will give it a shot if you want to post what you’ve got. I started working on culling, but it’s one of those things I don’t really need for my project (and it’s really hard) so I didn’t spend much time on it. If your stuff gets me half way there, maybe I can get it working.
 
Otherwise, I’ve picked up the pace on this project as of late because of some of the updates to Tiled are letting me use the Object Layers as a level builder in my latest project. So, I’m really focused on TileObjects, Sprites and the like, but I’m happy to add other features that people want.
 
If people want to help, there’s really a few things that are bugging me:
 

  1. This bit of code shows up a twenty or so times in the module…
     
    [lua]
                if properties then
                    for k, v in pairs(properties) do
                        if string.find(string.lower(k), “physics:”)~=nil then
                            layerGroup.physicsData[strRight(k, “:”)]=properties[k]
                            if layerGroup.physicsData[strRight(k, “:”)]==“true” then
                                layerGroup.physicsData[strRight(k, “:”)]=true
                            elseif layerGroup.physicsData[strRight(k, “:”)]==“false” then
                                layerGroup.physicsData[strRight(k, “:”)]=false
                            elseif isNumeric(layerGroup.physicsData[strRight(k, “:”)])==true then
                                layerGroup.physicsData[strRight(k, “:”)]=tonumber(layerGroup.physicsData[strRight(k, “:”)])
                            end
    [/lua]

…and we use this bit to turn a “physics:enabled” : “true” in the JSON into…

[lua]layerGroup.physicsData.enabled = true[/lua]

…in Corona. I would love a local function that could replace all the occurrences. It would make debugging a ton easier.

  1. Maps and Tilesets need to be in the root folder, because Tiled will include directory info with the tileset… I’m stripping it like this:

[lua]imageSheets[sets] = graphics.newImageSheet(string.match(tileSet.image,’([%w_]+%.%w%w%w)$’), options )[/lua]

…but there’s got to be a better way to look for that tileSet.image in the same directory as the JSON file.

And some big things that need to get done…

  1. Documentation. Next week I’m going to spend the time to do a screencast of how to build the demo project. Maybe someone could volunteer to transcribe that howto into the Wiki here on GitHub?

  2. Sample projects. I’d love to see a small physics-based platformer made from some open tile set. Maybe this one…

http://opengameart.org/content/updated-generic-platformer-tiles

 …it would be nice to have a larger project to come up with use cases from. An RPG too maybe… I did a little RPG in HTML5 and JavasScript that the graphics/maps could be used from…

http://michaelwilson.tv/pq/

  1. Other than that, just Fork the Repository, make some updates and send pull requests. I’m sure there’s more speed to be gained in the map loading.

So far on the melding, I’ve added the ability to render, erase, create object layers, image layers, and add tile physics to maps (the physics part still isn’t finished). I’ve got a library called “Glass” that loads a map into an easier format.

Here’s the Glass load format:

mapData

    tilesets - Not used, just a reference table

        tilewidth - Width of the tiles

        tileheight - Height of the tiles

        image - Image path

        imagewidth - Width of the image

        imageheight - Height of the image

        numCol - Number of tiles that fit from left to right across the image

        numRow - Number of tiles that fit from top to bottom across the image

    tileref - A numerical index of the tilesets, each number in it corresponds to a number in a tile layer’s “data” table and the resulting tile image can easily be found

        1-#tiles in the map - Table containing tile data

            tileset - Not used, a reference to the tileset (see above)

            sheet - The imageSheet for the tile

            frame - The frame for the tile

            data - The sequence data for the tile

            EXTRA: With the tileref, you’re able to create tiles easier by simply collecting the associated “data” number and using the tileref references

                

                local thisTile=display.newSprite(tileref[theDataNumberYouHave].sheet, tileref[theDataNumberYouHave].data)

                    thisTile:setFrame(tileref[theDataNumberYouHave].frame)

    width - Width of the map in tiles

    height - Height of the map in tiles

    tilewidth - Tile width

    tileheight - Tile height

    layoutCount - Number of tiles (total) in the map

    layout - Table containing grid positions (not exactly needed, but I use it in Quartz - it can be removed)

        [1-height][1-width] - Grid tables

            tileX, tileY - The position in tiles of the grid unit

            pixelX, pixelY - The position in pixels of the grid unit

    layers - Table containing layer data for the map

        tile - Table containing tile layer data for the map

            tilewidth - Width of the layer’s tiles

            tileheight - Height of the layer’s tiles

            data - Layer data table

            properties - Converted properties for the layer (see the “helper.convertProperties()” function)

        object - Table containing object layer data for the map

            objects - Layer object data table

        image - Table containing image layer data for the map

            image - Image path for the layer

Be warned that my code is by no means complete, nor the best that it could be - it’s just a prototype :slight_smile:

You can download it here: https://www.dropbox.com/s/s33yk4tm61f2ne5/CoronaTiled.zip

  1. I’ve created a basic function, called “convertProperties”, that arranges values into a table. Here’s what’s returned:

tileProperties

 -Physics

   -All properties preceded by “physics:”

 -Tile

   -All properties preceded by “tiles:”

 -Layer

    -All properties preceded by “layer:”

You’ll find it in the “helper.lua” file. If you can think up a good name for it, you can name it.

  1. I’m not exactly sure what you’re asking, here, but I think you’re attempting to strip the prefix of the tileset image path that Tiled adds (something like “/…/users/somebody/documents/whatever/tile.png”). Is that right?\

Big projects:

  1. Documentation (transcript) - Sure! (that is, if I figure out GitHub, which I’m getting closer to doing :))

  2. Sample projects - I’m willing to work on it/them :slight_smile: (nice RPG, by the way!)

  3. Ok - great :slight_smile:

(note the triple emoticons of eternal power)

C

Ok, so I’ve been working on the meld, and I’m doing pretty good. So far I’ve finished…

  • Rendering the tiles of a map with Quartz

  • Image layers with Quartz

  • Creating the object layers with Ceramic (your library)

  • Basic physics with Ceramic

I still have to do custom physics, though. I’m creating maps with a .load() function and then giving the user control of rendering and adding physics, instead of doing all that initially.

C

@Caleb,

Sounds good. 

I was thinking about starting to split this stuff up into the individual components like loadData(), renderMap(), etc so we can get ready to name the renders (Ceramic, Quartz, etc.)

Is that what you are working on now? I see it working something like this…

[lua]

mapData = tiled:load(“map.json”)

mapStatic = tiled:ceramic(mapData)

mapDynamic = tiled:quartz(mapData,x,y,w,h)

spawnPoint = tiled:findObject(mapData, “spawnPoint”)

[/lua]

After looking at it, I think it might be a bit much (or confusing) to name the renders individually. Maybe we should just pick one name–either Ceramic or Quartz and do the naming like this…

[lua]

mapData = ceramic:load(“map.json”)

mapStatic = ceramic:render(mapData)

mapDynamic = ceramic:draw(mapData,x,y,w,h)

spawnPoint = ceramic:findObject(mapData, “spawnPoint”)

[/lua]

I don’t know, maybe either looks fine… Any opinions?

By the way, if you can get the melding finished, I’ll be more than glad to work on culling.

C

Hey all

I’ve not read through the whole thread but I was working on a tile map that could load mega textures gigabytes in size. It was all quite complicated so I wont go into it now.

But the solution to gaps between tiles showing on movement or different screen resolutions is to load a low res image, ( 1024 x 1024, or even tile a few images together ) of the entire level and put it behind the tile map layer. This has the effect of even if gaps appear the right colours show through and it makes it a lot less noticeable.

Idk, the first doesn’t look any worse than the second…  Maybe you could use Tiled:render for ‘quartz’ and Tiled:construct for ‘ceramic’ if you decide Tiled:ceramic/Tiled:quartz is too confusing.  Anyway, hopefully I should be able to test this all out after next week, I have 3 tests in a row so I’ll be pretty bogged down for a while.  As Mike said, thanks for all your work Caleb, I can’t wait to see it all come together!  You guys have really made something great here, definitely a hidden gem as of now.

@Michael-

Awesome, haha I had actually programmed in my own cruddy version of the find object function.  Now I don’t have to worry about reprogramming it back in when downloading the merged project!

I’ve got it working like this:

[lua]

local myMap=CoronaTiled.load(“demo.json”)

myMap.draw([layer], x1,y1, x2,y2)

–myMap.drawAll()

–myMap.erase([layer], x1,y1, x2,y2)

–myMap.eraseAll([layer])

myMap.tilePhysics([layer]) – Updates tile physics for the map, or for a single layer; physics properties are added via Tiled

myMap.objPhysics([layer]) – Updates obj physics for the map, or for a single layer; physics properties are added via Tiled

[/lua]

And then I have a new type of object - “listener” - that you create by making a 0x0 rectangle in Tiled, giving it a “listener” attribute, and loading the map. Then you can do things like this:

[lua]

–You have a “listener” object with “listener” attribute == “playerSpawned”

local function spawnPlayer(event)

  --Values for the event include “event.time”, “event.x”, “event.y”, “event.name” and I’ll probably add any properties the user adds in Tiled

  --spawn player at event.x,event.y

end

Runtime:addEventListener(“playerSpawned”, spawnPlayer)

myMap.executeListener(“playerSpawned”)

–myMap.executeListeners() – Executes any listeners you’ve created

[/lua]

Sound good?

C

Sure… I’m not trying to force any particular methodology, but I am planning on splitting the load() from the render() function.
 
[lua]
local myMapData=tiled.load(“demo.json”)
local myMap=tiled.render(myMapData)
[/lua]

Here’s why. In my current project I’m using Tiled to layout positions of enemies and imageObjects in the x,y space of the screen, but I’m not using the tileLayers, so I’d like to get at that data without rendering a map.

This also future-looking as Tiled will support image scaling and rotation in an upcoming release, so you could use Tiled to build your game menus or “Limbo” style levels out of placed images.

https://github.com/bjorn/tiled/issues/396

The load thingy that I’m doing is simply loading the map - not rendering or drawing or physics or anything. Then you use the .draw to render some of it, or the .drawAll to render all of it, or .tilePhysics to update tile physics.

So that’s about the same as what you’re doing, only it does it all internally.

C

No worries, I just didn’t want to reinvent the wheel if you were doing it… Also, I’m not looking forward to merging this all together :)  

You should really spend a few hours getting to know gitHub… It makes merging code so much easier.

I’m working on merging it… Slowly but steadily.

I’ll check GitHub out again, maybe I’ll figure it out :smiley:

C

Keeping an eye on the developments here - but just wanted to chime in to emphasise using GitHub or some other form of collaborative system. This would certainly eradicate any potential hiccups, and also make the whole process a lot more efficient. On my first project I tried collaborating with other devs manually and after a short time it became a nightmare trying to merge code, doing the same bits, etc… 

@SegaBoy

Agreed. The gitHub project is here…

https://github.com/superqix/CoronaTiled

…and I plan on continuing to keep it updated even if code needs to be merged outside of gitHub. But it’s not as powerful unless people put issues there, fork and add pull requests.

I’m guilty of that as well, in fact I’m going to add my bug list to the issues right now.

Pushed a few small update this morning.

“Image Objects” now keep a copy of the image they represent in the MapData. It’s hard to explain, but Tiled is going to support arbitrary images that you can place in map space. So basically it is evolving to be a level editor as well as a tile editor. (In fact, the daily builds already support image rotation…) So to prepare for this an Imgage Object now has it’s own image property that includes the corona display object. Confused yet?

Also, tiledMap:findObject() now returns an array of objects so you can find all the objects that match a name. So you can do something like this…

[lua]local found = tiled:findObject(“findme”,map.data)
for i = 1, #found do
found[i].image:setLinearVelocity( 0, 16 )
group:insert(found[i].image)
end[/lua]

In the above example we are finding all the image objects named “findme” and adding them to a display group called “group”

One last thing, you can now set the collisonBits and maskBits of an object or in the case below a whole layer of objects.

Enjoy…

@Matthew

Thanks for the advice. I’ve been looking at making the tiles 0.5 units bigger to help cover those seams too, but in my projects I start at the max res (2048-by-1536) and scale my way down, which seems to work also.

I know it’s been quiet here, but there has been some progress. I’m making my way through the code @CalebP posted, and I’m trying to rework the loader to make the whole thing more modular.

Also, I had an interesting idea for the GUI in my current project…

This seems to work pretty well with the “type” of the object corresponding to the Widget 2.0 object and 9-slice tiles acting as the borders. I HATE hand coding menus and the like, but this makes it a little more fun… Like building a level…

I don’t know if this will just be an example with the current build, or a separate Module.

@Caleb, regarding the gaps on iPad.

We were using our own implementation of a tiled map reader before (moved to tiled.lua since we found this thread…).

In both implementations there was this same issue. We have found the problem, not sure it’s been mentioned on the thread (I went through it but it’s quite long…):

The gaps appear when the tile width/height are not a multiple of display.contentScaleX/Y. And the reason is that when the tiles are converted from Corona “points” to actual device pixels there are fractions involved.

Here is an example:

  1. We are using “letterbox” mode in config.lua, this implies that display.contentScaleX and display.contentScaleY are identical so I’ll refer to their value as “content scale”.

  2. We have a tileset with 32x32 tiles.

  3. When the map is loaded Corona needs to convert 32x32 images to real device pixels. On iPhone 4 it’s converted to 64x64 which is nice a tidy (the content scale is 0.5).

  4. On iPad the content scale is ~0.46. this means that after conversion to device pixels the images are 69.56. Of course device pixels are actual hardware pixels and therefore cannot be expressed with a fraction. This means Corona is forced to round it up or down which causes gaps of 1 pixel. The pattern of the gaps changes depending on the tile size and the content scale.

In our case we did not really care that much about the EXACT size of a tile so our solution was to slightly adjust the size Corona loads the images (we haven’t touched the actual tile size in the imagesheet) so that the size of the image in real device pixels would be a round number while in the size in Corona “points” can be a fraction

We have just added the following lines to tiled.lua:

local pixelWidth, pixelHeight = tileWidth/display.contentScaleX, tileHeight/display.contentScaleY tileWidth, tileHeight = math.floor(pixelWidth)\*display.contentScaleX, math.floor(pixelHeight)\*display.contentScaleY  

This solved the issue but made the tiles a bit larger on iPad which we don’t really mind…

Another solution would be to have a dynamic config.lua like in this post:

http://www.coronalabs.com/blog/2012/12/04/the-ultimate-config-lua-file/

In this manner you can dynamically set the content width and height (we use the default 480x320) depending on the device model or actual resolution (display.pixelWidth/Height). The idea is to modify the width and height in such a way that the content scale would device nicely with your tile size…

The downside of using this method is that your content size is not constant and it’s harder to maintain a consistant layout of the UI across different devices with different screen ratios.

PS, the problem is much more severe once you release to Android because there the fragmentation is huge and you need to be able to support a wide range of different content scales.

@qtt
 
Interesting. It wasn’t the scaling in my project that was covering the gaps it was  the GL_Nearest scaling…
 
[lua]
display.setDefault( “magTextureFilter”, “nearest” )

display.setDefault( “minTextureFilter”, “nearest” )
[/lua]

I went ahead and included a version of your “tile scaling” into the project. I also needed to scale all the other objects by the same percentage in the map so any object-based physics still line up. (I spent an hour trying to scale the objects up, but it always seemed to produce gaps, so closing the gaps is by far the best bet.

I should push out the 0.6 version shortly–it also includes some of the GUI stuff, but that’s not quite ready for primetime yet. :slight_smile:

That was fast :slight_smile:

Can’t wait to test out 0.6, we are working on a fresh new educational plane shooter game and we are using your project to build the terrain map. works great.

In our game the plane is flying up (in portrait mode) and enemies come in from the top flying down. Would you recommend using spawn points to define where and what kind of enemy planes are generated?

Currently the “sky” object that contains all the physics objects is actually totally seperated from the tile map, and all the objects are defined in code and not in Tiled.