Here is an alternate implementation of a draggable tile map which supports almost unlimited map sizes while still remaining responsive on my 3GS. (tested with 160,000 tiles)
Nice! What I wrote was just a first step for someone else to optimize further, but I didn't expect someone to step up to the plate so quickly.
Can you give a brief overview of how your approach works? I'll probably figure it out by reading the code, but pointers on what to look for would help.
ADDITION: It looks like what you are doing is instead of making sprites for every tile you only make a screenful of sprites and then change their frame as you scroll around. Is that right? I must be missing something, because you wouldn't need to set isVisible.
hi,
thanks for the contribution, this is very helpful
however... do note that this method is not really applicable when your map is partly comprised of physical objects (as Lime offers etc). Because physics objects are tied into display objects. you'd have to also destroy and recreate a physical body for every tile moving on/off screen. Maybe this is doable, but I don't know what the performance would be like. Maybe this is something someone could investigate next
jhocking.. from what I can tell the only time isVisible=false
is being set is when you reach the edges of the map.... you can currently scroll past the edges of the map, so therefore you don't want sprites there
thanks
j
p120ph37:
just one thing... you're creating an anonymous function closure every time:
map.forTilesInView(function(t, x, y)
i think this might cause slight memory/performance issues.. i changed it to this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| function map.visTest(t,x,y)
if(x >= 0 and x < map.xTiles and y >= 0 and y < map.yTiles) then
-- tile is inside the map
t.isVisible = true
t.currentFrame = map.tiles[y * map.xTiles + x + 1]
else
-- tile is off the edge of the map
t.isVisible = false
end
end
-- iterate over the visible tiles with a callback
function map.forTilesInView(f)
for y = 0, map.yTilesInView - 1 do
for x = 0, map.xTilesInView - 1 do
local tx = math.floor(map.xScroll / map.tileWidth) + x;
local ty = math.floor(map.yScroll / map.tileHeight) + y;
f(map[map.xTilesInView * y + x + 1], tx, ty)
end
end
end
-- align and update the display object grid based on the current scroll position
function map.updateDisplay()
-- align the display object grid
map.x = -(map.xScroll % map.tileWidth)
map.y = -(map.yScroll % map.tileHeight)
-- update the tile contents
map.forTilesInView(map.visTest)
end |
from what I can tell the only time isVisible=false is being set is when you reach the edges of the map
oh that makes sense
re: isVisible - bingo. You could also designate some special tile as your edge-of-the-world filler, or you could stop scrolling when you reach the edge of the map.
re: closure function - yes, I create an anonymous function every drag event. One anonymous function per drag event is not enough delay to add up to much, but abstracting the that out into a named function of its own isn't a bad idea, especially since it might be useful in other cases too.
re: tiles with physics - yes, physics properties of an object cannot be changed once it is instantiated, so you would need to modify the updateDisplay method to check if the current tile has changed physics properties and delete and recreate it if it has rather than just flipping the sprite frame.
I would try to separate the physics objects from other display objects. In most games a lot of the background graphics don't have physics properties, so his scrolling approach would work great for those. Then because the display is so optimized you could take a different approach (I haven't thought about this part yet) for the physics objects.
If you wanted a different approach specifically targeted at a sparsely-populated grid as you might expect in a physics layer, you could do something like this:
- Create an array (technically a table indexed numerically) with one position per grid location as with the tile ids in my previous example.
- Only populate those indexes which actually contain a physics object. (most indexes will be nil)
- Whenever the map scrolls, use similar math (perhaps with a slightly larger window) to get the list of indexes in this physics object array which fall within or near the current viewport.
- Compare this list of physics objects to the list of physics objects from last frame.
- If any are newly missing (moved out of range), remove them from the display.
- If any are newly present (moved into range), add them to the display.
- Go through all displayed physics objects and set their .x and .y properties.
- Save this list of objects for calculating the delta next frame.
Though slightly less efficient than my original map scrolling implementation, this would be a more general solution, allowing for not only the scrolling tiles, but also for physics objects, special objects with user interaction features (touch events), non-standard shapes for objects (a tree which overhangs several nearby terrain tiles), objects which are not entirely grid-aligned (could specify an offset from the grid location where the object "lives"), multiple objects in the same grid square, etc.
If I feel terribly inspired I may write this...
hi,
please note my other post as well
http://developer.anscamobile.com/forum/2010/11/22/scrolling-large-non-tiled-world-objects
one potential issue is physics objects that are created/destroyed dynamically but need to maintain state (if the user returns to that area) and also respond in a way that makes sense even if they are off screen. I know a lot of games will probably reset properties of an object once it falls outside of a certain area, especially larger games or there would be a lot of constant data to maintain.
however i'm still just wondering if suddenly changing a physics object onscreen might lead to unexpected collision behaviour.
thanks for your input. I hope you can share some more examples, or maybe even contribute to what Graham is working on with Lime, since it allows for modular functionality
regards
j.
@p120ph37: Are you a paid subscriber? That's required for Code Exchange, and your tile-map approach would be a great addition there.
@jhocking
I am not yet - I've been working on a couple of projects in Corona and will pay the fee when I'm ready to publish the first. I did look into posting to the Code Exchange page a while ago for my bitmap font stuff, and when I do subscribe, that will probably be the first one I post.
oh you did the bitmap font code, that was pretty good too
I did the Glyph Designer compatible one, not the TextCandy one.
The bitmap code is actually working really well for me in one of my projects. I don't have any projects using the tile map code yet.
How would you go about doing this if you wanted to use single image tiles instead of a tilemap, AND in addition to that use the output data from the Physicseditor for each of the tiles?
I am trying to put together a maze level using single tiles (one tile for a left corner, one tile for a right corner, one tile for a left wall etc), then using the data from the Physics editor, and putting together the main menu, level 1, level 2 etc using Director.
Could someone please give me an example for just a few tiles, to get me started. Right now programming 28x28 tiles just as is with Corona is out of the question, my iPhone is about to die when I load the level.