Collision/overlap detection while dragging: A solution

So, I’ve been working on this game where you drag objects onto a playing area which is populated with arbitrarily shaped objects (defined as complex bodies) and I wanted to implement a way of checking whether the dragged object was overlapping an object on the stage or not. In real time.

Turned out this wasn’t that easy… but I came up with a “hack” that did the job. Not very optimized or anything and I really hope there is a better way of doing it, but I’m sticking with it 'til I (or you) come up with something better. Hope it helps/inspires someone else with a similar problem.

This code assumes that you have created a display object named draggableObject and added a touch event handler to it:
[lua]function draggableObject:touch( event )
local phase = event.phase

– Some setup code etc has been removed for clarity

if phase == “moved” then
– Updates position. startX and startY are set elsewhere
self.x = event.x - self.startX
self.y = event.y - self.startY

– Here we call the method that checks for overlaps
self:checkLegalPosition()

– isPositionLegal is set to true when the object is created
– As an example I set the fill color to green or red respectively to show if the
– object is in the clear or not. You can do whatever you like here of course.
if self.isPositionLegal then
self:setFillColor(0, 255, 0)
else
self:setFillColor(255,0,0)
end

– Drop when finger is lifted
elseif phase == “ended” or phase == “cancelled” then
– Some code removed for clarity

– Check if object is in the clear.
– If so drop it on the playing area. If not remove it.
– This is just what I did. You might want to do something else here.
if self.isPositionLegal then
self:dropOnStage()
else
self:destroy() – This also destroys the collision detector if it exists
end
end

return true
end[/lua]

Here’s the method that checks for a legal position. If this seems rather brute force, it’s because it is. I tried using phase.began and phase.ended in the collision event but the “ended” phase would fire even if the object was still overlapping other object. Thus I resorted to creating a temporary object, letting it listen for a collision event and then destroying it. Every time the phase.moved fired from the touch event.
[lua]function draggableObject:checkLegalPosition()
self.isPositionLegal = true

– This is the “hack”. It creates a temporary physics object and attaches a
– collision event listener to it. If there is a collision, isPositionLegal is set to false.

if not collisionDetector then
createCollisionDetector( self )
else
destroyCollisionDetector( self )
createCollisionDetector( self )
end
end[/lua]

These are the functions that creates the temporary detector object:
[lua]function createCollisionDetector( forObject )
– The following lines are quite specific to my game but I hope you get the gist of it
collisionDetector = display.newCircle( 0, 0, 16 )
registry.currentLevelStage:insert( collisionDetector )
collisionDetector.x, collisionDetector.y = registry.currentLevelStage:contentToLocal( forObject.x, forObject.y )

– These are the important lines
physics.addBody( collisionDetector, “static”, { isSensor = true, radius = 16 })
collisionDetector:addEventListener(“collision”, forObject)
end

– This should be pretty self-explanatory
function destroyCollisionDetector( forObject )
if collisionDetector then
collisionDetector:removeEventListener(“collision”, forObject)
collisionDetector:removeSelf()
collisionDetector = nil
end
end[/lua]

And this is the actual collision event handler:
[lua]function draggableObject:collision( event )
if event.phase == “began” then
self.isPositionLegal = false;
self:setFillColor(255, 0, 0)
end

return true
end[/lua]

Hope this makes some sense to you :slight_smile: And if you come up with a better solution, please let me know! I saw that the Ansca staff was working on a (maybe) related issue according to the roadmap: “Physics: simplified collision detection for “non-physical” cases”.

Cheers
/Daniel [import]uid: 45930 topic_id: 9488 reply_id: 309488[/import]

Great stuff - thanks for sharing it :slight_smile: Many people ask about this and it seems like a solid workaround.

Nicely done!

Peach :slight_smile: [import]uid: 52491 topic_id: 9488 reply_id: 34743[/import]

Glad you like it :smiley:

I’ve seen a lot of posts asking about functionality similar to this. I hope people find this post and can make some use of it. It’s hard writing a headline that people can relate to though: similar problem -> different ways of describing it… [import]uid: 45930 topic_id: 9488 reply_id: 35127[/import]

Hi,

I’m new in Corona and still fighting agains my low programming skills. This solutions would work great for my game, but I tried to implant it into my code and it didn’t work. Could someone please post a simple code example with this solution?

Thanks very much in advance!
[import]uid: 40363 topic_id: 9488 reply_id: 35963[/import]

Is that different from the function I wrote?

http://developer.anscamobile.com/code/flashs-hittestobject-emulated-using-contentbounds [import]uid: 12108 topic_id: 9488 reply_id: 35976[/import]

Jhocking, thanks for the reply, but your function would not work for me. The above functions, if I am not wrong, use the physics collision to test overlapping. As I will be working with polylines, your function will not work, as the polyline border is very complex.

But thanks for the response. [import]uid: 40363 topic_id: 9488 reply_id: 36035[/import]

Hello,

I have prepared this simple code with the detection functions, but it doesn’t work. I wanted to test if the overlapping of the created circle (it is created once the background is touched), but no way. I asume I am too new wih Corona. There might be something that I miss with the layers (I suppose somehow I need to detect collision only with the square, not with the background) and something else that I miss with the overlapping detection functions.
Anyone could help me, please? It wold be much apreciated.

Here is the code:

[lua]------------

local screenW, screenH = display.contentWidth, display.contentHeight

local background = display.newRect(0, 0, screenW, screenH)
background:setFillColor(100,100,100)

local objectToTouch = display.newRect(200, 200, 50, 50)
objectToTouch:setFillColor(200,50,50)

function CreateCircle( event )
local phase = event.phase
if “began” == phase then
circle = display.newCircle(event.x, event.y, 10)
circle:setFillColor(0,255,255)

circle:checkLegalPosition()
if circle.isPositionLegal then
circle:setFillColor(0, 255, 0)
else
circle:setFillColor(255,0,0)
end

Runtime:addEventListener(“touch”, MoveCircle)
end

if “ended” == phase then
circle:removeSelf()
end
end

function MoveCircle( event )
print (event.name…“occured”)
circle.x = event.x
circle.y = event.y

end

background:addEventListener(“touch”, CreateCircle)

function circle:checkLegalPosition()

self.isPositionLegal = true

if not collisionDetector then
createCollisionDetector( self )
else
destroyCollisionDetector( self )
createCollisionDetector( self )
end
end

function createCollisionDetector( forObject )
collisionDetector = display.newCircle( 0, 0, 10 )
registry.currentLevelStage:insert( collisionDetector )
collisionDetector.x, collisionDetector.y = registry.currentLevelStage:contentToLocal( forObject.x, forObject.y )

physics.addBody( collisionDetector, “static”, { isSensor = true, radius = 16 })
collisionDetector:addEventListener(“collision”, forObject)
end

function destroyCollisionDetector( forObject )
if collisionDetector then
collisionDetector:removeEventListener(“collision”, forObject)
collisionDetector:removeSelf()
collisionDetector = nil
end
end

function circle:collision( event )
if event.phase == “began” then
self.isPositionLegal = false;
self:setFillColor(255, 0, 0)
end

return true
end

--------------------[/lua]

Thank you

*EDITED: Please refer to the rules about use of lua tags when pasting your code. http://developer.anscamobile.com/forum/2011/05/05/forum-rules-and-guidelines [import]uid: 40363 topic_id: 9488 reply_id: 36036[/import]

The above functions, if I am not wrong, use the physics collision to test overlapping. As I will be working with polylines, your function will not work, as the polyline border is very complex.

ah I see, that wasn’t clear from your thread title. [import]uid: 12108 topic_id: 9488 reply_id: 36092[/import]

Thanks for this post Daniel, this seems like a great work around. However I can’t seem to get a working example running. Is there anyway you would be willing to post a simple working version of this in the code exchange section, or here in this thread? If you could, I and the corona community would greatly appreciate it. [import]uid: 9484 topic_id: 9488 reply_id: 47492[/import]

Hi all and thanks for the response. Unfortunately my iOS game dev endeavors have been in hibernation of late so my memory isn’t all that fresh on the subject, but give me some time and I’ll post an example.

Cheers [import]uid: 45930 topic_id: 9488 reply_id: 47506[/import]

Ok, still haven’t been doing any Corona stuff but I’ll post the code in it’s entirety. This is totally unreviewed and provided as-is. Hope you can make use of it :slight_smile:

This is all in a module named bomb.lua

[lua]module (…, package.seeall)

local physics = require( “physics” )
local physicsData = (require “shapeDefs”).physicsData(1.0)
local cos = math.cos
local sin = math.sin
local ui = require(“ui”)

local collisionDetector = nil

function new()
local bomb = display.newCircle( 0, 0, 16 )

– Fields
bomb.startX = 0
bomb.startY = 0
bomb.isPositionLegal = true

function bomb:init()
self:setFillColor( 0, 200, 0 );
self:addEventListener( “touch”, self )
end

function bomb:collision( event )
if event.phase == “began” then
self.isPositionLegal = false;
self:setFillColor(255, 0, 0)
end

return true
end

– Handle touch events
function bomb:touch( event )
local phase = event.phase

if “began” == phase then
– Make target the top-most object
local parent = ui
self.x, self.y = parent:contentToLocal( event.x, event.y )
parent:insert( self )
display.getCurrentStage():setFocus( self )

self.hasFocus = true

self.startX = event.x - self.x
self.startY = event.y - self.y
elseif self.hasFocus then
– Follow finger
if phase == “moved” then
self.x = event.x - self.startX
self.y = event.y - self.startY

self:checkLegalPosition()

if self.isPositionLegal then
self:setFillColor(0, 255, 0)
else
self:setFillColor(255,0,0)
end

– Drop when finger is lifted
elseif phase == “ended” or phase == “cancelled” then
display.getCurrentStage():setFocus( nil )
self.hasFocus = false

– check if bomb is in legal position ie somewhwere on the playing field.
– If so drop it there. If not remove it.
if self.isPositionLegal then
self:dropOnStage()
else
self:destroy()
end
end
end

– Returning true keeps the event from propagating
return true
end

function bomb:checkLegalPosition()
self.isPositionLegal = true

local testX, testY = registry.currentLevelStage:contentToLocal( self.x, self.y )
local halfWidth, halfHeight = self.width * .5, self.height * .5

– check if inside playing field
if testX < halfWidth or testX > registry.playingFieldBounds.width - halfWidth then
self.isPositionLegal = false
end
if testY < halfHeight or testY > registry.playingFieldBounds.height - halfHeight then
self.isPositionLegal = false
end

– check if overlapping other objects
if not collisionDetector then
createCollisionDetector( self )
else
destroyCollisionDetector( self )
createCollisionDetector( self )
end

end

function bomb:dropOnStage()
destroyCollisionDetector( self )
registry.activeBombs:insert( self )
self.x, self.y = registry.currentLevelStage:contentToLocal( self.x, self.y )
registry.currentLevelStage:insert( self )
end

function bomb:destroy()
if self then
destroyCollisionDetector( self )
self:removeEventListener( “touch”, self )
self:removeSelf()
self = nil
end
end

bomb:init();

return bomb
end

function createCollisionDetector( forBomb )
collisionDetector = display.newCircle( 0, 0, 16 )
registry.currentLevelStage:insert( collisionDetector )
collisionDetector.x, collisionDetector.y = registry.currentLevelStage:contentToLocal( forBomb.x, forBomb.y )
physics.addBody( collisionDetector, “static”, physicsData:get( “bomb” ))
collisionDetector:addEventListener(“collision”, forBomb)
end

function destroyCollisionDetector( forBomb )
if collisionDetector then
collisionDetector:removeEventListener(“collision”, forBomb)
collisionDetector:removeSelf()
collisionDetector = nil
end
end[/lua] [import]uid: 45930 topic_id: 9488 reply_id: 57843[/import]

Sweet Dan! I never realized that you posted the code for this, found it combing google for a solution. I really appreciate it, as I’ve been pulling my hair out trying to figure out how to do something similar. I’m trying to just run your code atm, but I run into errors on “registry” and cannot find anything on google about it. I assume this is something specific to your program that is not shown in the code provided? [import]uid: 9484 topic_id: 9488 reply_id: 70834[/import]

Aaw, glad the code could be of help :slight_smile: as I said it’s been a while since I worked on that code but the registry is simply a global variable in the main file used to store references to various stuff such as the current level etc. you could easily substitute it for whatever you want to put the bomb sprite in. Such as your main sprite container.

in my main.lua I created the registry object:

[lua]registry = require( “Registry” )[/lua]

In that game I used a main graphics container on each level like this:

[lua]local mainStage = display.newGroup()[/lua]

Then I stored a reference to that in the registry ( which is just a global object that holds references to stuff ):

[lua]registry.currentLevelStage = mainStage[/lua]

The registry itself is stored in a separate lua-file:

[lua]module (…, package.seeall)

currentState = nil;
activeBombs = {};
activeBombs.insert = table.insert;
currentLevelStage = nil;
playingFieldBounds = { width = 2048, height = 1536 }[/lua]

Hope that helps. The main gist is, you don’t need a registry… [import]uid: 45930 topic_id: 9488 reply_id: 70842[/import]

Ok,I have successfully implemented and customized this for my app now, great work around man. I still have yet to test the performance, but thats something ill have to deal with as this is a critical aspect of my app. Mad props to Dan. [import]uid: 9484 topic_id: 9488 reply_id: 71292[/import]