(cross-post from the subscribers area because I figure others might want to see this)
Alright I finally got around to testing out the tilemap performance in Corona and writing my own visibility culling sample. I’ve been meaning to do this for almost a month when I first saw some people chirping about this problem.
In summation, I made a map with 1600 tiles at 64x64 pixels per tile, and I optimized the code so that the map scrolls around easily on my iPhone 3Gs.
–
First off, I tried displaying a big tilemap (like 40x40) without making any attempt to optimize things just to see how it performs; I hadn’t tried that before so I wanted to see for myself. As you all are pointing out, it performs like a pig. At first I was confused what the complaints were about because it seemed fine in the simulator, but then I tried it on device and it was terrible.
So then I whipped up a modified version of my hitTestObjects function from Code Exchange to use for visibility culling. The modification was to simply check the screen boundaries instead of the boundaries of any specific display object.
Then I tried looping through all the tiles to check visibility. That helped a little but it was still chugging badly, so I needed to optimize the code further.
I split up the map into 8x8 quadrants and checked visibility for those quadrants instead of the individual tiles. That really boosted performance and I got up to 1600 tiles (40x40) with performance that was still pretty good. The reason this ran faster is that instead of calling isVisible() 1600 times I only had to call it 25 times.
If I wanted an even bigger map I could optimize further, probably by nesting quadrants within quadrants to reduce the number of calls to isVisible(), but 1600 tiles was already plenty for me to demonstrate this approach. Setting up the quadrants procedurally for a tool like Lime could be tricky, but the basic idea isn’t that complicated.
Here’s the code and tileset for my sample (incidentally, if you want to see how it performs without the culling, just delete the first dash in the line “—visibility culling optimized w/quadrants”):
local function isVisible(obj)
local screen = display.getCurrentStage().contentBounds
local bounds = obj.contentBounds
local left = bounds.xMin \<= screen.xMin and bounds.xMax \> screen.xMin
local right = bounds.xMin \>= screen.xMin and bounds.xMin \<= screen.xMax
local up = bounds.yMin \<= screen.yMin and bounds.yMax \>= screen.yMin
local down = bounds.yMin \>= screen.yMin and bounds.yMin \<= screen.yMax
return (left or right) and (up or down)
end
--tiles are 64x64 in 576x1216 image for 9x19=171 tiles
local sprite = require("sprite")
local tileSheet = sprite.newSpriteSheet("tiles.png", 64, 64)
local tileSet = sprite.newSpriteSet(tileSheet, 1, 171)
--init random function
local randomseed = math.randomseed
local random = math.random
local time = os.time
randomseed(time())
random() --bug in first use
--setup tile map w/quadrants
local map = display.newGroup()
for i = -2, 2 do
for j = -2, 2 do
local quad = display.newGroup()
quad.x = i \* 512
quad.y = j \* 512
map:insert(quad)
--tiles in quadrant
for i = 0, 7 do
for j = 0, 7 do
local tile = sprite.newSprite(tileSet)
tile.currentFrame = random(171)
tile.x = i \* 64
tile.y = j \* 64
quad:insert(tile)
end
end
end
end
--move tile map
local prevX, prevY
local function drag(event)
if event.phase == "began" then
prevX = event.x
prevY = event.y
elseif event.phase == "moved" then
local dX = event.x - prevX
local dY = event.y - prevY
map.x = map.x + dX
map.y = map.y + dY
prevX = event.x
prevY = event.y
---[[visibility culling optimized w/quadrants
for i = 1, map.numChildren do
if isVisible(map[i]) then
map[i].isVisible = true
else
map[i].isVisible = false
end
end
--]]
end
end
map:addEventListener("touch", drag)

ADDITION: In another thread jmp noticed that the image got downsized when I posted it, so the tiles you need to use are 41x41 and the quadrant is (41*8) rather than 512 (64*8) [import]uid: 12108 topic_id: 5681 reply_id: 26394[/import]