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