So I have gone ahead and made a method which accomplishes this for you:
local function createFrameCollider(frame)
-- get info about frame
local frame_absoluteSize = frame.AbsoluteSize
local frame_absolutePosition = frame.AbsolutePosition
local frame_rotation = frame.Rotation
local frame_height = frame_absoluteSize.Y
local frame_width = frame_absoluteSize.X
local frame_area = frame_width*frame_height
local frame_center = frame_absolutePosition + frame_absoluteSize/2
-- 1. roblox rotation is backwards from mathematical rotation :: negate it
-- 2. modulus rotation by 180 to limit it to being 1 -> 180
local frame_theta = (-frame_rotation)%180
-- if rotation is negative, determine where it is by subtracting the absolute value of it from 180 (we're working with rectangles)
-- now we have a rotation between 0 and 179.999...
frame_theta = frame_theta < 0 and 180 - math.abs(frame_theta) or frame_theta
-- if rotation >= 90, make the height the width and the width the height, then subtract 90 from the rotation
-- now we have a rotation between 0 and 89.999..., which makes this very easy
if frame_theta >= 90 then
frame_height = frame_width
frame_width = frame_absoluteSize.Y
frame_theta = frame_theta - 90
end
-- determine the shifts we need with the frame to go from the center to an edge
local frame_theta_rad = math.rad(frame_theta)
local shift_y = math.sin(frame_theta_rad)*frame_width/2
local shift_x = math.cos(frame_theta_rad)*frame_width/2
-- determine the offsets we need with the frame to go from an edge to a corner
local offset_y = math.cos(frame_theta_rad)*frame_height/2
local offset_x = math.sin(frame_theta_rad)*frame_height/2
-- get edges
local edge_rightX = frame_center.X - shift_x
local edge_rightY = frame_center.Y + shift_y
local edge_leftX = frame_center.X + shift_x
local edge_leftY = frame_center.Y - shift_y
-- get corners
local a = Vector2.new(edge_leftX - offset_x, edge_leftY - offset_y)
local b = Vector2.new(edge_rightX - offset_x, edge_rightY - offset_y)
local c = Vector2.new(edge_rightX + offset_x, edge_rightY + offset_y)
local d = Vector2.new(edge_leftX + offset_x, edge_leftY + offset_y)
local corners = {a,b,c,d}
-- return function which determines if a non-rotated guiObject is intersecting this rotated guiObject
-- uses SAT
return function(guiObject)
local obj_pos = guiObject.AbsolutePosition
local obj_size = guiObject.AbsoluteSize
local size_x = obj_size.X
local size_y = obj_size.Y
local a = obj_pos
local b = obj_pos + Vector2.new(size_x,0)
local c = obj_pos + Vector2.new(size_x,size_y)
local d = obj_pos + Vector2.new(0,size_y)
local corners2 = {a,b,c,d}
for _, points in ipairs({corners, corners2}) do
for i = 1, 4 do
local j = i%4+1
local p1 = points[i]
local p2 = points[j]
local normal = Vector2.new(p2.Y - p1.Y, p1.X - p2.X)
local minA, maxA, minB, maxB
for _, p in pairs(corners) do
local projected = normal.X * p.X + normal.Y * p.Y
minA = (minA == nil and projected) or (projected < minA and projected) or minA
maxA = (maxA == nil and projected) or (projected > maxA and projected) or maxA
end
for _, p in pairs(corners2) do
local projected = normal.X * p.X + normal.Y * p.Y
minB = (minB == nil and projected) or (projected < minB and projected) or minB
maxB = (maxB == nil and projected) or (projected > maxB and projected) or maxB
end
if maxA < minB or maxB < minA then
return false
end
end
end
return true
end
end
The createFrameCollider
method returns a method which can be called to check if a given unrotated GuiObject
intersects with the given frame. This accomplishes this task using SAT (see: https://youtu.be/IELWpIGtjRg as @AstroCode said). It determines the corners/vertices of the frame using sin/cos along with the absolute size/position of the frame.
So you would use this by doing something like this:
local collider = ROTATED_FRAME
local character = UNROTATED_FRAME
local detector = createFrameCollider(collider)
-- check if character collides every renderstep: make green if it does not,
-- make red if it does
game:GetService("RunService").RenderStepped:Connect(function(dt)
local collides = collisionDetector(character)
if collides then
character.BackgroundColor3 = Color3.new(1,0.1,0.1)
else
character.BackgroundColor3 = Color3.new(0.2,1,0.2)
end
end)
You can also check out this demo place: https://www.roblox.com/games/5806405990/2d-collision
It is open source.
Note: every time you update the frame position you must create a new frame collider, as it uses a “cached” version of the frame to increase speed. Pretty much you need to update it any time the AbsoluteSize
or AbsolutePosition
of the frame changes. You can either do this manually, or wrap the method I created to do this for you automatically. The wrapped version would look something like this:
local function wrappedCollisionDetectorCreator(frame)
local absolutePosition = frame.AbsolutePosition
local absoluteSize = frame.AbsoluteSize
local collisionDetector = createFrameCollider(frame)
return function(guiObject)
if frame.AbsolutePosition ~= absolutePosition or frame.absoluteSize ~= absoluteSize then
absolutePosition = frame.AbsolutePosition
absoluteSize = frame.AbsoluteSize
collisionDetector = createFrameCollider(frame)
end
return collisionDetector(guiObject)
end
end
Note: I have not tested this wrapper, but in theory it should work… but might need some tweaks. You would then call wrappedCollisionDetectorCreator()
instead of createFrameCollider()
. Naming could be better though. Edit it as you please.
Hope this helps, if you need any further explanation let me know.