GUI Collision Tutorial

From Legacy Roblox Wiki
Jump to navigationJump to search

Introduction

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:

A basic 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

r1
r2

So now, we can write code like this, which represents the diagram on the right.

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

But I digress. 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:

r1
r2
i
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) 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!"

See Also