Open Sourced Collision Response Simulation using Guis!

Check the reply marked as the solution for the latest update added to this topic!

Hello folks!

I am sure you might be having fun with these daily topics I create for 2D game development!

Today,
I created a simple collision response between a circle and a canvas’s boundaries, simulation and thought about making it open sourced!

(I’ll update this tomorrow and make circle ← → circle collision response using Newtons 2nd and 3rd law of motion!) done

Don’t know what collision response is? Check out this wikipedia page!



How does it work?

Well, simple! Theres an initial velocity to the circle. The velocity changes the position of the circle, at every frame, collisions between the circle and the 4 boundaries are checked, if they do collide, I multiply the velocity by -1 which causes the direction of the velocity to be reversed!

For the sake of simplicity:
mass = 1 for all objects in our 2D space
acceleration = constant constant acceleration throughout! (You can change it in the source code)

To simulate Elastic Collisions, I used newton’s 3rd Law!

image


Public and Uncopylocked place:


Source code:

Client → Physics and Circle module.

Circle Module:

local Circle = {}
Circle.__index = Circle

function Circle.new(frame, v, m)
	local self = setmetatable({
		frame = frame,
		radius = frame.AbsoluteSize.x/2,
		center = frame.AbsolutePosition + frame.AbsoluteSize/2,
		velocity = v,
		acceleration = Vector2.new(0, 0),
		position = frame.AbsolutePosition,
		mass = m
	}, Circle)
	
	return self
end

function Circle:boundaryCollisions(walls)
	local offset = Vector2.new(0, 36)
	
	local bottom = (walls.Bottom.AbsolutePosition + offset).y
	local left = (walls.Left.AbsolutePosition + offset).x
	local top = (walls.Top.AbsolutePosition + offset).y
	local right = (walls.Right.AbsolutePosition + offset).x
	
	if self.position.y + self.radius * 2 >= bottom then
		self.position = Vector2.new(self.position.x, bottom - self.radius * 2)
		self.velocity = Vector2.new(self.velocity.x, self.velocity.y * -1)
	elseif self.position.y <= top then
		self.position = Vector2.new(self.position.x, top)
		self.velocity = Vector2.new(self.velocity.x, self.velocity.y * -1)
	end
	
	if self.position.x + self.radius * 2 >= right then
		self.position = Vector2.new(right - self.radius * 2, self.position.y)
		self.velocity = Vector2.new(self.velocity.x * -1, self.velocity.y)
	elseif self.position.x <= left then
		self.position = Vector2.new(left, self.position.y)
		self.velocity = Vector2.new(self.velocity.x * -1, self.velocity.y)
	end		
end

function Circle:circleCollisions(circles)
	local colliding = false
	
	local collisions = {}
	local u1
	local u2
	
	for _, circle in ipairs(circles) do
		if circle.frame ~= self.frame then
			if ((self.frame.AbsolutePosition + self.frame.AbsoluteSize/2) - (circle.frame.AbsolutePosition + circle.frame.AbsoluteSize/2)).magnitude <= (self.radius + circle.radius) then
				colliding = true
				collisions[1] = circle
				u1 = self.velocity
				u2 = circle.velocity
			end
		end
	end
	
	if colliding then
		local circle = collisions[1]

		local v1 = ((self.mass - circle.mass)/(circle.mass + self.mass)) * u1 + ((2 * circle.mass)/(self.mass + circle.mass)) * u2
		local v2 = ((2 * self.mass)/(self.mass + circle.mass)) * u1 + ((circle.mass - self.mass)/(circle.mass + self.mass)) * u2
		
		self.velocity = v1
		circle.velocity = v2 
	end
end

function Circle:move(walls, circles)
	game:GetService("RunService").RenderStepped:Connect(function()
		self:boundaryCollisions(walls)
		self:circleCollisions(circles)
		
		self.velocity += self.acceleration 
		self.position += self.velocity
		self.frame.Position = UDim2.new(0, self.position.x, 0, self.position.y)			
	end)			
end

return Circle

Physics Simulation Module:

local Physics = {}
Physics.__index = Physics

function Physics.new(circles, walls)
	local self = setmetatable({
		circles = circles,
		walls = walls
	}, Physics)
	
	return self
end

function Physics:simulate()
	for _, circle in ipairs(self.circles) do
		circle:move(self.walls, self.circles)
	end
end

return Physics

Client handler:

local physics = require(script.Physics)
local circles = require(script.Circle)

local canvas = script.Parent.Canvas

local circleTable = {}

for i = 0, 4 do
	local newcircle = game:GetService("ReplicatedStorage").Circle:Clone()
	newcircle.Position = UDim2.new(0, math.random(canvas.Walls.Left.AbsolutePosition.X, canvas.Walls.Right.AbsolutePosition.X), 0, math.random(canvas.Walls.Top.AbsolutePosition.Y, canvas.Walls.Bottom.AbsolutePosition.Y))
	newcircle.Size = UDim2.new(0, 30, 0, 30)
	newcircle.Parent = canvas
	
	local circle = circles.new(newcircle, Vector2.new(math.random(-2, 2), math.random(-2, 2)), 1)
	table.insert(circleTable, circle)
end

local init = physics.new(circleTable, {
	Top = canvas.Walls.Top,
	Bottom = canvas.Walls.Bottom,
	Left = canvas.Walls.Left,
	Right = canvas.Walls.Right
})

init:simulate()

StarterGui:

image


That’s all for today! I hope you enjoy this stuff, I am currently developing a 2D Gui Based physics engine for roblox, see ya if not tomorrow but soon with another post!

20 Likes

Would you like a tutorial on making a Pong game using Guis? That consists of the same concept of Collision responses?

  • Yes
  • No
  • That’s easy to make!

0 voters

1 Like

This it’s totally amazing, I hope Roblox someday introduce an official API like this or an 2D workspace with its 2D instances to avoid the use of StaterGui :wink:

3 Likes

You’re gonna love this one!

Remember this? I did it!

Its quite a simple concept! The same stuff applied earlier is used here, but with a few additions! Firstly, I created a new functions that checks for collisions between the balls (the more the balls, the costlier it is to run collision detections). Collisions between circles are fairly simple, all we do is check if the distance between the center point of the circle is less than or equal to the sum of radiuses of both circles.

C1 - C2 <= r1 + r2, if this is true, both circles are colliding.

Then comes the concept of elastic collisions! With the help of two formulas:

image

image

This calculates the velocity of both balls after collision (elastic collision), using initial velocities and mass (1) of both objects! This may not be the best solution to it, since say, 3 balls collide with each other at the same time, its obviously going to break!

image

I have updated the source code and the uncopylocked game!

4 Likes

Update

Added velocity readings and directing lines that display velocities of the circles for better visualization. Velocity readings can be toggled by pressing Space on your keyboard.

Source code for the line formation taken from my RayCast module:

local function draw(hyp, origin, thickness, parent, l) 
	local line = l or Instance.new("Frame")
	line.Name = "Velocity"
	line.AnchorPoint = Vector2.new(.5, .5)
	line.Size = UDim2.new(0, hyp, 0, thickness or 1)
	line.BackgroundColor3 = Color3.new(0, 1, 1)
	line.BorderSizePixel = 0
	line.Position = UDim2.fromOffset(origin.x, origin.y)
	line.Parent = parent

	return line
end

return function (originx, originy, endpointx, endpointy, parent, thickness, l)
	local origin = {
		x = originx,
		y = originy
	}
	
	local endpoint = {
		x = endpointx,
		y = endpointy
	}
	
	local adj = (Vector2.new(endpoint.x, origin.y) - Vector2.new(origin.x, origin.y)).magnitude
	local opp = (Vector2.new(endpoint.x, origin.y) - Vector2.new(endpoint.x, endpoint.y)).magnitude
	local hyp = math.sqrt(adj^2 + opp^2)
	
	local theta = math.deg(math.acos(adj/hyp))
	
	local line = l and draw(hyp, origin, thickness, parent, l) or draw(hyp, origin, thickness, parent)
	
	local pos = line.AbsolutePosition
	local size = line.AbsoluteSize
	
	if (endpoint.x == origin.x and endpoint.y > origin.y) or (endpoint.x == origin.x and endpoint.y <= origin.y) then
		theta = 90 -- y axis
	elseif (endpoint.x < origin.x and endpoint.y == origin.y) or (endpoint.x >= origin.x and endpoint.y == origin.y) then
		theta = 0 -- x axis
	elseif endpoint.x >= origin.x and endpoint.y <= origin.y then
		theta = -theta-- quad 1
	elseif endpoint.x <= origin.x and endpoint.y <= origin.y then
		theta = theta + 180 -- quad 2
	elseif endpoint.x <= origin.x and endpoint.y >= origin.y then
		theta = -theta -- quad 3
	elseif endpoint.x >= origin.x and endpoint.y >= origin.x then
		theta = math.abs(theta) -- quad 4
	end
	
	local mid = Vector2.new((origin.x + endpoint.x)/2, (origin.y + endpoint.y)/2)
		
	line.Position = UDim2.fromOffset(mid.x, mid.y)
	line.Rotation = theta
	
	return line
end
3 Likes