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:

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
33
34
35
36
37
38
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

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

Nicely done!

Peach :)

Glad you like it :D

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...

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!

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.

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:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
------------
 
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
 
--------------------

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.

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.

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

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 :)

This is all in a module named bomb.lua

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
views:1970 update:2011/10/17 8:58:49
corona forums © 2003-2011