How to make UI Ball bounce off borders

I’m trying to make a little UI brickbreaker game as an app on the phones in my game and I’m having trouble making the ball bounce off the edges.
image
The white frames are usually invisible and the ball is supposed to bounce off of them.
How can I make the ball bounce like this off of bricks and the edges?
bounce
This is the code I have so far. (The overlap detection is a snippet I found on another DevForum post)

local ball = script.Parent.AppFrame.Ball
local paddle = script.Parent.AppFrame.Paddle
local mouseX = 0
local moveDir = UDim2.new(0.02,0,0.05,0)

function areFramesOverlapping(Frame1,Frame2)

	local F1posX,F2posX = Frame1.AbsolutePosition.X,Frame2.AbsolutePosition.X
	local F1sizeX,F2sizeX = Frame1.AbsoluteSize.X,Frame2.AbsoluteSize.X

	local Xdistance = math.abs(F1posX-F2posX) 
	local minXdistance = math.abs(F1sizeX+F2sizeX/2)

	local F1posY,F2posY = Frame1.AbsolutePosition.Y,Frame2.AbsolutePosition.Y
	local F1sizeY,F2sizeY = Frame1.AbsoluteSize.Y,Frame2.AbsoluteSize.Y

	local Ydistance = math.abs(F1posY-F2posY) 
	local minYdistance = math.abs(F1sizeY+F2sizeY/2)

	if Ydistance < minYdistance and Xdistance < minXdistance then return true end

	return false 
end

script.Parent.AppFrame.MouseMoved:Connect(function(x)

	mouseX = x 

end)

game:GetService("RunService").RenderStepped:Connect(function()

	paddle.Position = UDim2.new(0, mouseX, 0.779,0)


	ball.Position = UDim2.new(math.clamp(ball.Position.X.Scale + moveDir.X.Scale, -1, 1), 0,math.clamp(ball.Position.Y.Scale + moveDir.Y.Scale, -1, 1),0)

	if areFramesOverlapping(ball, script.Parent.AppFrame.Border3) or areFramesOverlapping(ball, script.Parent.AppFrame.Border4) then
		print("yes")
		moveDir = UDim2.new(-moveDir.X.Scale, 0, -moveDir.Y.Scale, 0)
	elseif areFramesOverlapping(ball, script.Parent.AppFrame.Border1) or areFramesOverlapping(ball, script.Parent.AppFrame.Border2) then
		print("yes")
		moveDir = UDim2.new(-moveDir.X.Scale, 0, moveDir.Y.Scale, 0)
	end
end)

1 Like

If the formula for reflection is move_direction.Unit + (2 * normal * normal:dot(move_direction.Unit) you can use this to find the new direction you want the ball to reflect from. (This also works with vector3s)

You will need to calculate where the ball hits the other object (You don’t need to do a raycast for this but it’s optional), this way you can calculate the new position based on where the ball hit the wall. (You can probably assume where the ball hit, based on how much over the boundary line the ball was, guestimating).

You’ll likely want to use the delta time part of the render stepped function to calculate speed by frame, should allow you to also easily change how fast you want it running without guessing the speed.

How would I go about using this in the code?

I’ve not done it in 2d but this is probably some format I’d go about:

local VELOCITY = 0.2 -- // Scale per second
local direction = Vector2.zero
local playConnection

local function getCollisions(ball, ...) -- // Variadics are for walls.. however it does it
    -- // assume this returns some thing like
    Return wall, normal, hit_position -- // Normals are just perpendicular to the surface of the wall
end

local function solveReflection(normal : Vector2, direction : Vector2)
    local normal = normal.Unit
    return (direction.Unit + (2 * normal * normal:Dot(direction))
end

local function update(dt)
    local position = ball
    local wall, normal, hit_position = getCollisions(ball, wall1, wall2) -- etc..
    if (wall and normal) then
        direction = solveReflection(normal, direction)
    end

    local added_position = UDim2.fromScale(direction.X * VELOCITY * dt, direction.Y * VELOCITY * dt)
    if (hit_position) then
        ball.Position = UDim2.fromScale(hit_positon.X, hit_position.Y) + added_position
    else
        ball.Position += added_position
    end

    -- // Guess you can calculate whether the ball is out of bounds and end it here
end

local function onStart()
    if (playConnection and playConnection.Connected) then playConnection:Disconnect() end
    direction = Vector2.new(0, 1) -- // Since scales are backwards, 1 is descending
    local dt = RunService.RenderStepped:Wait()
    update(dt)
    -- // I guess run some init stuff

    playConnection = RunService.RenderStepped:Connect(update)
end

There’s a lot of fill in the blanks, since I don’t want to recreate this functionality myself, but hopefully this gives a rough idea?

Okay, that makes way more sense.
One more thing though, how would I get the normal of the object the ball hit?

I don’t have an exact answer to explain how to do this mathematically but since you have 3 or 4 boundaries, you can label which or what type of normal they should have.

If it’s a vertical line, the normal would probably be 1, 0 or it would be -1, 0, left boundary being the prior and right being the latter. And horizontal being (top) 0, 1 and bottom being 0, 1. Vector normals should always be a vector length of 1.

function methods:CollidesWith(obj : GuiObject)
    -- // This does the check for whether the object passed the line
    -- // if it does, it finds the point of intersection between the two (by guessing or not) not actually important, can use previous location, or distance to line from ball ?? up to you
    if collides then
        return true
    end
    return false
end

local function setBoundary(frame : Frame, normal : vector2)
    local boundary = setmetatable({}, methods)

    boundary.normal = normal
    boundary.position = frame.Position
    boundary.Size = frame.Size

    return boundary
end

local boundary = setBoundary(boundaryFrame, Vector2.new(1, 0)) -- // Left side
local boundary2 = setBoundary(boundaryFrame2, Vector2.new(-1, 0)) -- // Right side
local boundary3 = setBoundary(boundaryFrame3, Vector2.new(0, 1)) -- // Top side

---------[Using the boundaries you declared]---------
local bool = boundaryObj:CollidesWith(ball) -- // bool being whether it does or doesn't
if (bool) then
    normal = boundaryObj.Normal
end