Collisions off screen

Hi, I have a strange issue. 

On my map at the end of a path I have an object with physics body added. Theres a issue that when the object is off screen the collision event does not trigger, if the object is the camera view then it works fine. Any ideas why this would happen? Also whats the best way of adding objects like this, should it be done through addSprite? Im not entirely sure how addObject works, theres no examples of this.

Have you tried reducing your code down to only the part which is going wrong? This can sometimes be difficult, but it can certainly help produce a piece of code which is easier to debug or can be posted here for others to look at, without posting your entire listing.

If you’re trying to pick up collisions between a physics body and offscreen map tiles, this will not work. Offscreen tiles are culled immediately after leaving the screen; their corresponding displayObjects are removed to conserve system resources. From the perspective of the physics API, outside of the visible screen area there is nothing with which to collide.

MTE has no dedicated function for controlling the size of this active region at the moment, but you can work around it by reducing your blockScale and calling mte.zoom(). For example, if your blockScale is 64 you could instead use 32, and then call mte.zoom(2, 1) after you load your map. The resulting view will look about the same size, but the active region will extend farther beyond the edges of the screen. Just keep in mind that doing this will increase the performance requirements of your game.

addObject is used to add Tiled Objects to the map. If you’re dealing in physics you probably want addSprite. 

The Million Tile Engine does not technically support physics at the moment, but I’m working on it, and the release this friday will add what Physics support I’ve finished so far.

Thanks Dyson, that makes sense. My map isnt huge so dont think it will be a problem, but doing this breaks my scroll on touch on the map. Heres an example, my block scale is 30 so i made it 15 and zoomed in

display.setStatusBar(display.HiddenStatusBar); -- lime = require("lime.lime") local mte = require ("mte") system.activate("multitouch") --LOAD MAP ----------------------------------------------------------------------------- mte.loadMap("pluto") mte.goto({locX = 1, locY = 24, blockScale = 15}) mte.constrainCameraToScreen({ edges = {true, true, true, true}, time = 50 }) mte.zoom(2, 1) local mapObj = mte.getMapObj() local scaleFactor = mte.blockScaleX / mte.worldScaleX --The ratio of the blockScale to the native size of your tiles, necessary for touch scrolling local startX, startY, currentX, currentY local isDragging -- print(scaleFactor) local onUpdate = function( event ) -- Update the map. Needed for using map:setFocus() if isDragging then mte.moveCamera((startX - currentX) / scaleFactor / mapObj.xScale, (startY - currentY) / scaleFactor / mapObj.yScale) startX = currentX startY = currentY end mte.update() mte.debug() end local function calculateDelta( previousTouches, event ) local id,touch = next( previousTouches ) if event.id == id then id,touch = next( previousTouches, id ) assert( id ~= event.id ) end local dx = touch.x - event.x local dy = touch.y - event.y return dx, dy end -- create a table listener object for the bkgd image function mapObj:touch( event ) local result = true local phase = event.phase local previousTouches = self.previousTouches local numTotalTouches = 1 if ( previousTouches ) then -- add in total from previousTouches, subtract one if event is already in the array numTotalTouches = numTotalTouches + self.numPreviousTouches if previousTouches[event.id] then numTotalTouches = numTotalTouches - 1 end end if "began" == phase then if numTotalTouches == 1 then startX = event.x startY = event.y currentX = event.x currentY = event.y isDragging = true end -- Very first "began" event if ( not self.isFocus ) then -- Subsequent touch events will target button even if they are outside the stageBounds of button display.getCurrentStage():setFocus( self ) self.isFocus = true previousTouches = {} self.previousTouches = previousTouches self.numPreviousTouches = 0 elseif ( not self.distance ) then local dx,dy if previousTouches and ( numTotalTouches ) \>= 2 then dx,dy = calculateDelta( previousTouches, event ) end -- initialize to distance between two touches if ( dx and dy ) then local d = math.sqrt( dx\*dx + dy\*dy ) if ( d \> 0 ) then self.distance = d self.xScaleOriginal = self.xScale self.yScaleOriginal = self.yScale -- print( "distance = " .. self.distance ) end end end if not previousTouches[event.id] then self.numPreviousTouches = self.numPreviousTouches + 1 end previousTouches[event.id] = event elseif self.isFocus then if "moved" == phase then if numTotalTouches == 1 then currentX = event.x currentY = event.y end if ( self.distance ) then local dx,dy if previousTouches and ( numTotalTouches ) \>= 2 then dx,dy = calculateDelta( previousTouches, event ) end if ( dx and dy ) then local newDistance = math.sqrt( dx\*dx + dy\*dy ) local scale = newDistance / self.distance -- print( "newDistance(" ..newDistance .. ") / distance(" .. self.distance .. ") = scale(".. scale ..")" ) if ( scale \> 0 and self.xScaleOriginal \* scale \<= 2 and self.xScaleOriginal \* scale \>= 1) then self.xScale = self.xScaleOriginal \* scale self.yScale = self.yScaleOriginal \* scale end end end if not previousTouches[event.id] then self.numPreviousTouches = self.numPreviousTouches + 1 end previousTouches[event.id] = event elseif "ended" == phase or "cancelled" == phase then if numTotalTouches == 1 then startX, startY, currentX, currentY = nil, nil, nil, nil isDragging = false end if previousTouches[event.id] then self.numPreviousTouches = self.numPreviousTouches - 1 previousTouches[event.id] = nil end if ( #previousTouches \> 0 ) then -- must be at least 2 touches remaining to pinch/zoom self.distance = nil else -- Allow touch events to be sent normally to the objects they "hit" display.getCurrentStage():setFocus( nil ) self.isFocus = false self.distance = nil self.xScaleOriginal = nil self.yScaleOriginal = nil -- reset array self.previousTouches = nil self.numPreviousTouches = nil end end end return result end Runtime:addEventListener( "enterFrame", onUpdate ) mapObj:addEventListener( "touch", mapObj ) --Touch listener for touch scroll and pinch zoom

I copied/pasted your code into one of my projects and it appears to be working fine on my end. Could you describe in more detail the problem you’re having? In what way is touch scroll broken?

Also, as an experiment, try commenting out the constrainCameraToScreen call and see if that fixes or changes the touch scroll behavior.

Well the scroll works but I can’t scroll to the end of the map, it only goes so far and then stops with part of the map off the screen. Remove the constraint won’t work because my map doesn’t repeat so it just ends up going off screen

I thought that might be the case. This is a known current limitation I hope to rectify in the future. A workaround for now is to use constrainCamera() instead of constrainCameraToScreen(). The function constrainCamera() accepts a loc array instead of an edges array, and you can pass it locations which lie outside of your map in order to compensate for the zoom.

I don’t know the dimensions of your map, so I’m going to use 100x100 as an example.

These two calls have basically identical effects:

mte.constrainCameraToScreen({ edges = {true, true, true, true}, time = 50 })

mte.constrainCamera({ loc = {1, 1, 100, 100}, time = 50 })

When you zoom in the constraint looks too tight; you can’t get to the edge of the map. The solution is to widen the constraints:

mte.constrainCamera({ loc = {-10, -10, 110, 110}, time = 50 })

You’ll have to experiment with the numbers to get the effective constraint to the point you want it. Let me know if you hit any snags.

Thanks for your help, I got it in position using 

mte.goto({locX = level.startx, locY = 1, blockScale = 15})

mte.constrainCamera({ loc = {2, -3, 18, 30}, time = 50 })

mte.zoom(2, 1)

My maps only 18 X 24 and the tiles are 30px x 30px

But collisions are still not happening off screen, any ideas? 

I’m stumped. If you email me your project I can dig into it and figure out just what the problem is.

Now resolved 

if object.sX + object.levelWidth \* modX \> 0 and object.sX - object.levelWidth \* modX \< displayWidth then if object.sY + object.levelHeight \* modY \> 0 and object.sY - object.levelHeight \* modY \< displayHeight then --if object is currently onscreen object.sX = screenPos.x object.sY = screenPos.y end end if screenPos.x + object.levelWidth \* modX \> 0 and screenPos.x - object.levelWidth \* modX \< displayWidth then if screenPos.y + object.levelHeight \* modY \> 0 and screenPos.y - object.levelHeight \* modY \< displayHeight then --if object should be onscreen object.sX = screenPos.x object.sY = screenPos.y end end

Comment it out or delete it and add the lines:

object.sX = screenPos.x object.sY = screenPos.y

Have you tried reducing your code down to only the part which is going wrong? This can sometimes be difficult, but it can certainly help produce a piece of code which is easier to debug or can be posted here for others to look at, without posting your entire listing.

If you’re trying to pick up collisions between a physics body and offscreen map tiles, this will not work. Offscreen tiles are culled immediately after leaving the screen; their corresponding displayObjects are removed to conserve system resources. From the perspective of the physics API, outside of the visible screen area there is nothing with which to collide.

MTE has no dedicated function for controlling the size of this active region at the moment, but you can work around it by reducing your blockScale and calling mte.zoom(). For example, if your blockScale is 64 you could instead use 32, and then call mte.zoom(2, 1) after you load your map. The resulting view will look about the same size, but the active region will extend farther beyond the edges of the screen. Just keep in mind that doing this will increase the performance requirements of your game.

addObject is used to add Tiled Objects to the map. If you’re dealing in physics you probably want addSprite. 

The Million Tile Engine does not technically support physics at the moment, but I’m working on it, and the release this friday will add what Physics support I’ve finished so far.

Thanks Dyson, that makes sense. My map isnt huge so dont think it will be a problem, but doing this breaks my scroll on touch on the map. Heres an example, my block scale is 30 so i made it 15 and zoomed in

display.setStatusBar(display.HiddenStatusBar); -- lime = require("lime.lime") local mte = require ("mte") system.activate("multitouch") --LOAD MAP ----------------------------------------------------------------------------- mte.loadMap("pluto") mte.goto({locX = 1, locY = 24, blockScale = 15}) mte.constrainCameraToScreen({ edges = {true, true, true, true}, time = 50 }) mte.zoom(2, 1) local mapObj = mte.getMapObj() local scaleFactor = mte.blockScaleX / mte.worldScaleX --The ratio of the blockScale to the native size of your tiles, necessary for touch scrolling local startX, startY, currentX, currentY local isDragging -- print(scaleFactor) local onUpdate = function( event ) -- Update the map. Needed for using map:setFocus() if isDragging then mte.moveCamera((startX - currentX) / scaleFactor / mapObj.xScale, (startY - currentY) / scaleFactor / mapObj.yScale) startX = currentX startY = currentY end mte.update() mte.debug() end local function calculateDelta( previousTouches, event ) local id,touch = next( previousTouches ) if event.id == id then id,touch = next( previousTouches, id ) assert( id ~= event.id ) end local dx = touch.x - event.x local dy = touch.y - event.y return dx, dy end -- create a table listener object for the bkgd image function mapObj:touch( event ) local result = true local phase = event.phase local previousTouches = self.previousTouches local numTotalTouches = 1 if ( previousTouches ) then -- add in total from previousTouches, subtract one if event is already in the array numTotalTouches = numTotalTouches + self.numPreviousTouches if previousTouches[event.id] then numTotalTouches = numTotalTouches - 1 end end if "began" == phase then if numTotalTouches == 1 then startX = event.x startY = event.y currentX = event.x currentY = event.y isDragging = true end -- Very first "began" event if ( not self.isFocus ) then -- Subsequent touch events will target button even if they are outside the stageBounds of button display.getCurrentStage():setFocus( self ) self.isFocus = true previousTouches = {} self.previousTouches = previousTouches self.numPreviousTouches = 0 elseif ( not self.distance ) then local dx,dy if previousTouches and ( numTotalTouches ) \>= 2 then dx,dy = calculateDelta( previousTouches, event ) end -- initialize to distance between two touches if ( dx and dy ) then local d = math.sqrt( dx\*dx + dy\*dy ) if ( d \> 0 ) then self.distance = d self.xScaleOriginal = self.xScale self.yScaleOriginal = self.yScale -- print( "distance = " .. self.distance ) end end end if not previousTouches[event.id] then self.numPreviousTouches = self.numPreviousTouches + 1 end previousTouches[event.id] = event elseif self.isFocus then if "moved" == phase then if numTotalTouches == 1 then currentX = event.x currentY = event.y end if ( self.distance ) then local dx,dy if previousTouches and ( numTotalTouches ) \>= 2 then dx,dy = calculateDelta( previousTouches, event ) end if ( dx and dy ) then local newDistance = math.sqrt( dx\*dx + dy\*dy ) local scale = newDistance / self.distance -- print( "newDistance(" ..newDistance .. ") / distance(" .. self.distance .. ") = scale(".. scale ..")" ) if ( scale \> 0 and self.xScaleOriginal \* scale \<= 2 and self.xScaleOriginal \* scale \>= 1) then self.xScale = self.xScaleOriginal \* scale self.yScale = self.yScaleOriginal \* scale end end end if not previousTouches[event.id] then self.numPreviousTouches = self.numPreviousTouches + 1 end previousTouches[event.id] = event elseif "ended" == phase or "cancelled" == phase then if numTotalTouches == 1 then startX, startY, currentX, currentY = nil, nil, nil, nil isDragging = false end if previousTouches[event.id] then self.numPreviousTouches = self.numPreviousTouches - 1 previousTouches[event.id] = nil end if ( #previousTouches \> 0 ) then -- must be at least 2 touches remaining to pinch/zoom self.distance = nil else -- Allow touch events to be sent normally to the objects they "hit" display.getCurrentStage():setFocus( nil ) self.isFocus = false self.distance = nil self.xScaleOriginal = nil self.yScaleOriginal = nil -- reset array self.previousTouches = nil self.numPreviousTouches = nil end end end return result end Runtime:addEventListener( "enterFrame", onUpdate ) mapObj:addEventListener( "touch", mapObj ) --Touch listener for touch scroll and pinch zoom

I copied/pasted your code into one of my projects and it appears to be working fine on my end. Could you describe in more detail the problem you’re having? In what way is touch scroll broken?

Also, as an experiment, try commenting out the constrainCameraToScreen call and see if that fixes or changes the touch scroll behavior.

Well the scroll works but I can’t scroll to the end of the map, it only goes so far and then stops with part of the map off the screen. Remove the constraint won’t work because my map doesn’t repeat so it just ends up going off screen

I thought that might be the case. This is a known current limitation I hope to rectify in the future. A workaround for now is to use constrainCamera() instead of constrainCameraToScreen(). The function constrainCamera() accepts a loc array instead of an edges array, and you can pass it locations which lie outside of your map in order to compensate for the zoom.

I don’t know the dimensions of your map, so I’m going to use 100x100 as an example.

These two calls have basically identical effects:

mte.constrainCameraToScreen({ edges = {true, true, true, true}, time = 50 })

mte.constrainCamera({ loc = {1, 1, 100, 100}, time = 50 })

When you zoom in the constraint looks too tight; you can’t get to the edge of the map. The solution is to widen the constraints:

mte.constrainCamera({ loc = {-10, -10, 110, 110}, time = 50 })

You’ll have to experiment with the numbers to get the effective constraint to the point you want it. Let me know if you hit any snags.

Thanks for your help, I got it in position using 

mte.goto({locX = level.startx, locY = 1, blockScale = 15})

mte.constrainCamera({ loc = {2, -3, 18, 30}, time = 50 })

mte.zoom(2, 1)

My maps only 18 X 24 and the tiles are 30px x 30px

But collisions are still not happening off screen, any ideas? 

I’m stumped. If you email me your project I can dig into it and figure out just what the problem is.

Now resolved 

if object.sX + object.levelWidth \* modX \> 0 and object.sX - object.levelWidth \* modX \< displayWidth then if object.sY + object.levelHeight \* modY \> 0 and object.sY - object.levelHeight \* modY \< displayHeight then --if object is currently onscreen object.sX = screenPos.x object.sY = screenPos.y end end if screenPos.x + object.levelWidth \* modX \> 0 and screenPos.x - object.levelWidth \* modX \< displayWidth then if screenPos.y + object.levelHeight \* modY \> 0 and screenPos.y - object.levelHeight \* modY \< displayHeight then --if object should be onscreen object.sX = screenPos.x object.sY = screenPos.y end end

Comment it out or delete it and add the lines:

object.sX = screenPos.x object.sY = screenPos.y