GUI Collisions

Jun 14 2019, 2:18 PM PST 5 min

This tutorial will teach you one way to detect whether two GUIs are “touching”.

Rectangles

Determining if two GUIs touch is the same as determining whether two rectangles intersect. Let’s start off by defining a rectangle:

Basic GUI Rectangle Class

```local Rectangle = {}

function Rectangle.new(...)
local args = {...}

-- Four argument constructor - individual coordinates
if #args == 4 then
return constructFromCoords(...)

-- Two argument constructor - position and size Vector2s
elseif #args == 2 then
return constructFromPositionAndSize(...)

-- No argument constructor - return an uninitialized rectangle
else
return rawConstruct()
end

-- Internally construct a rectangle. top, right,
-- bottom, and left are hard to use by hand
function rawConstruct(top, right, bottom, left)
return setmetatable({
top = top,
right = right,
bottom = bottom,
left = left
}, {__index = Rectangle});
end

-- for construction from coordinates: orders
-- points accordingly
function constructFromCoords(x1, y1, x2, y2)
if x1 > x2 then x1, x2 = x2, x1 end
if y1 > y2 then y1, y2 = y2, y1 end
return rawConstruct(y2, x2, y1, x1)
end

-- for construction from position and size
function constructFromPositionAndSize(pos, size)
return constructFromCoords(pos.x, pos.y, pos.x + size.x, pos.y + size.y);
end
end```

Basic Rectangle Methods

We can now add some methods:

``````function Rectangle:width()    return self.right - self.left                   end
function Rectangle:height()   return self.top   - self.bottom                 end

function Rectangle:position() return Vector2.new(self.left   , self.bottom  ) end
function Rectangle:size()     return Vector2.new(self:width(), self:height()) end

function Rectangle:area()     return self:width() * self:height()             end
function Rectangle:center()   return self:position() + self:size()/2          end
``````

That’s pretty much all you could want to know about a rectangle, right? Well, except for intersections. We’ll get to that.

Using the rectangles

So now, we can write code like this, which represents the image below the code snippet.

``````local r1 = Rectangle.new( -- A 20x20 square at (20, 10)
Vector2.new(20, 10),
Vector2.new(20, 20)
)
print(r1:width())  -- 20
print(r1:height()) -- 20

local r2 = Rectangle.new(30, 50, 60, 90) -- A 30x40 rectangle, at (30, 50)
print(r2:area())   -- 1200
print(r2:center()) -- 45, 70
`````` Intersection

To determine whether two rectangles intersect, you simply check if they intersect horizontally, and whether they intersect vertically. Horizontally, the rectangles intersect if:

``````r1.left < r2.right and r2.left < r1.right
``````

And vertically:

``````r1.bottom < r2.top and r2.bottom < r1.top
``````

Using the above rectangles as an example:

`````` Horizontally: 20 < 60 and 30 < 40 == true
Vertically:   10 < 90 and 50 < 30 == false
``````

So the rectangles overlap horizontally, but not vertically. Great! Looking at the picture, you can see there is horizontal overlap.

So now we can add one more method to our rectangle object:

``````function Rectangle:intersects(other)
return self.left < other.right and other.left < self.right and
self.bottom < other.top and other.bottom < self.top
end
``````

Which lets us write:

``````print(r1:intersects(r2)) -- false
``````

Getting the intersecting rectangle

We can go one stage further though. Lets not only check whether there’s an intersection, but find the size of this intersection. Lets start by defining two rectangles that do intersect:

``````local r1 = Rectangle.new( -- A 40x80 rectangle at (20, 10)
Vector2.new(20, 10),
Vector2.new(40, 80)
)
local r2 = Rectangle.new( -- A 60x40 rectangle at (40, 30)
Vector2.new(40, 30),
Vector2.new(60, 40)
)
print(r1:intersects(r2)) -- true
`````` We want to get the rectangle `i`, the intersection of r1 and r2. Here is the code to do that:

``````function Rectangle:intersection(other)
if not self:intersects(other) then return nil end

local intersection = Rectangle:new()

intersection.top    = math.min(self.top,    other.top)
intersection.right  = math.min(self.right,  other.right)
intersection.bottom = math.max(self.bottom, other.bottom)
intersection.left   = math.max(self.left,   other.left)

return intersection
end
``````

A more complex case

``````local red = Rectangle.new(
Vector2.new(20, 10),
Vector2.new(40, 40)
)
local blue = Rectangle.new(
Vector2.new(30, 40),
Vector2.new(40, 40)
)
local green = Rectangle.new(
Vector2.new(50, 20),
Vector2.new(40, 40)
)
local yellow  = red:intersection(green)
local cyan    = green:intersection(blue)
local magenta = blue:intersection(red)

local white = red:intersection(green):intersection(blue)
`````` Using Rectangles with GUIs

Great, so we can do rectangle intersection! But what about GUIs? Well, that’s easy: GUIs are rectangles! We just need a way of converting:

``````function Rectangle.fromGUI(gui)
return Rectangle.new(gui.AbsolutePosition, gui.AbsoluteSize)
end
``````

Now we just do this:

``````local r1, r2 = Rectangle.fromGUI(gui1), Rectangle.fromGUI(gui2)
if r1:intersects(r2) then
print("The GUIs intersect!")
else
print("The GUIs do not intersect!");
end
``````

This will either print “The GUIs intersect!” or “The GUIs do not intersect!”

Tags:
• gui
• collisions
• coding