Apple Falling on Newton's Head - Simulating Physics using Guis

This tutorial covers the basics of simulating physics using Guis. I have been creating and showcasing my physics related resources and cool creations, so might as well just give you a brief idea on how I do it. Thanks!

Simulating Physics using Guis

Making Lifeless Guis feel alive


If you have been following my posts recently, you are going to encounter a lot of posts dedicated to physics simulations using Guis, be it steering behaviors, conservation and transfer of momentum or just applying forces to a gui object. This topic covers almost everything you need to know about simulating physics using guis. We’ll also be scripting our own simulation:

  • An apple falling from a tree on Newton’s head :apple:

image


General Misconception

Before we begin, I would like to address a general misconception that beginners generally have. Many jump to the conclusion of using TweenService to simulate physics. It is a bad practice. TweenService isn’t meant to simulate physics. Moreover its highly inflexible in terms of physics simulations. You’ll see why, as we get into scripting it.


Understanding Essential Terms

When scripting these simulations, you are frequently going to encounter these terms, not only in this tutorial but also physics in general, whether it be school or university. You should also be thorough with Newton’s Laws of motion! You can skip this portion if you already have a good understanding of the following:

  • Motion
  • Velocity
  • Acceleration
  • Forces
  • Mass

motion - the process of the position or orientation of an object with respect to time.

That is the simplest way we can define what motion is. Here is an example of what is in motion and what is not:

A car in motion:

unnamed (1)

A car at rest:

image

velocity - Velocity is a vector quantity. Meaning it has a magnitude and a direction. It is the rate of change of its position with respect to time. It is the Speed but with a Direction of where the object is moving.

acceleration - It is the rate of change of velocity. Rate refers to the quantity divided by time. The formula for acceleration is:

image

v is final velocity, u is the initial velocity and t is time!

force - A very common and simple definition of force is a push or a pull. The formula for force is force = mass x acceleration

image

mass - mass is a physical quantity that every entity has, it is the amount of matter an entity contains.


Creating a Physical Entity

We’ll first start by creating a physical entity in our 2D space. You can do this by multiple ways. But, personally I like to use ModuleScripts for the same.

Here’s a boilerplate example:

local Entity = {}
Entity.__index = Entity

function Entity.new(frame, v, a, m) -- v = velocity, a = acceleration, m = mass
	local self = setmetatable({
		frame = frame, 
		velocity = v,
		acceleration = a,
		mass = m,
		position = frame.AbsolutePosition
	}, Entity) 
	
	return self
end

return Entity

We’ll use these essential physical quantities. For this tutorial, We’ll take the acceleration for each object to be constant and the mass to be 1. The one thing to note is that we’ll be using Vector2 values for velocity, position and acceleration! I’ll be explaining how, further in the tutorial

The next boilerplate functions I like to make is an Update function. This function is responsible for updating the velocity, acceleration and position of the entity! Similar to p5.js’s functions. This function is called every RenderStepped from a client script to simulate continuous and smooth motion.

function Entity:Update()
    self.position += self.velocity
    self.frame.Position = UDim2.new(0, self.position.x, 0, self.position.y)
end

Our physical entity now has a basic structure, we’ll be using this structure throughout!

You might have noticed I wrote self.position += self.velocity. The reason to that is, velocity is a vector2 value in our code. Suppose the position of our entity was Vector2.new(5, 5). And we said, the velocity is constant and is Vector2.new(1,1). What this essentially is saying is - Position = Position + Velocity. Which is Position = Vector2.new(5, 5) + Vector2.new(1, 1). Running this every frame will give a smooth simulation of the entity moving diagonally to the bottom right of the screen.


Applying Forces to an Entity

We created our Entity, but, how do we move it? We’ll start by creating a simple function called :ApplyForce() which is essentially ran to apply certain force to the Entity. If you remember, f = ma. Which is force = mass * acceleration. If we are given a force and a mass, we can easily calculate a by saying a = force/mass. Force here will also be a Vector2 value.

function Entity:ApplyForce(force)
    self.acceleration = force/self.mass
end

The above method cancels out any other force being acted upon the apple, so it is recommended to use the method below, though for this tutorial, i’d use the above method:

self.acceleration += force/self.mass

or simply

self.velocity += force

Now we need to apply this acceleration to the velocity. In the update function:

function Entity:Update()
	self.velocity += self.acceleration
	self.position += self.velocity
	self.frame.Position = UDim2.new(0, self.position.x, 0, self.position.y)
end

We say add that acceleration to the velocity. This gradually increases the velocity of the entity every frame to simulate smooth movement.

We have this code so far:

local Entity = {}
Entity.__index = Entity

function Entity.new(frame, v, a, m)
	local self = setmetatable({
		frame = frame, 
		velocity = v,
		acceleration = a,
		mass = m,
		position = frame.AbsolutePosition
	}, Entity) 
	
	return self
end

function Entity:ApplyForce(force)
	self.acceleration = force/self.mass
end

function Entity:Update()
	self.velocity += self.acceleration
	self.position += self.velocity
	self.frame.Position = UDim2.new(0, self.position.x, 0, self.position.y)
end

return Entity

Lets actually make an entity move! I have a canvas which has a White ball. We’ll be moving this ball!

In a client script I say:

local EntityModule = require(script.Entity)

local NewEntity = EntityModule.new(script.Parent.Canvas.Ball, Vector2.new(0, 0), Vector2.new(0, 0), 1)

task.wait(5)

game:GetService("RunService").RenderStepped:Connect(function()
	NewEntity:ApplyForce(Vector2.new(.2, .1)) -- applying the force
	NewEntity:Update()
end)

In the above example, we took velocity and acceleration to be 0, 0 at the start and mass to be 1, You can play around with these values to the see magic! After 5 seconds, the ball starts to move to the bottom right corner infinitely! Essentially, its job is to increment the position of the entity.


Simulating an apple falling on Newton’s head.

You must be waiting patiently for this fictional story we are going to simulate. It is said that Newton discovered what gravity is when an apple fell on his head from a tree he was sitting under.

We’ll make the apple fall on newton’s head, bounce off and fall down on the ground! The bouncing part is a bit complex, but we’ll crack it down.

Firstly, lets make our props, I have this ready with me, you can take your time to create yours:

Excuse the mediocre designing skills.

Jokes aside, lets get serious. Lets code Gravity!

Since we have our previous entity code. All we need to do is apply force to the apple!

local EntityModule = require(Entity.module)

local Apple = EntityModule.new(path.to.apple, Vector2.new(0, 0), Vector2.new(0, 0), 1)

game:GetService("RunService").RenderStepped:Connect(function()
    local gravity = Vector2.new(0, .5)
	Apple:ApplyForce(gravity)
	Apple:Update()
end)

Gravity is the Force (Incrementation vector in our specific case) that is being apply to the apple at all times. But, you’ll notice that when you run this code. The apply falls endlessly. To stop this from happening. We’ll have to prevent the apple from crossing the green colored frame aka the ground.

In the Entity Module, we can create a new function called, :CollideWithGround().

function Entity:CollideWithGround(ground)
	local BottomRightCorner = self.position + self.frame.AbsoluteSize

	if BottomRightCorner.y > ground.AbsolutePosition.y then
		self.position = Vector2.new(self.position.x, ground.AbsolutePosition.y - self.frame.AbsoluteSize.y)
		self.velocity = Vector2.new(self.velocity.x, -self.velocity.y)
	end
end

This checks if the apple goes past the ground, position it correctly and also reverse the direction of the velocity!

The next step would be to damp the velocity. Damping means to deplete a part of something. We’ll multiply velocity by 0.95 which reduces the velocity by around 5% every frame!

To do that, just edit the Update function to say:

function Entity:Update()
	self.velocity += self.acceleration
	self.velocity = self.velocity * .95
	self.position += self.velocity
	self.frame.Position = UDim2.new(0, self.position.x, 0, self.position.y + 36)
end

You can adjust the damping value to be what ever you want! The higher it is, the more it will bounce.

Now in our client script, we add the :CollideWithGround() function that takes in the green colored ground frame as the argument.

local EntityModule = require(Entity.module)

local Apple = EntityModule.new(path.to.Apple, Vector2.new(0, 0), Vector2.new(0, 0), 1)

game:GetService("RunService").RenderStepped:Connect(function()
	local gravity = Vector2.new(0, .5)
	Apple:CollideWithGround(path.to.ground)
	Apple:ApplyForce(gravity)
	Apple:Update()
end)

And, lets run this…

Cool! We have got gravity working. Now for the bouncing off of newton’s head part. We’ll create a circular head hitbox for newton.

image

The white circular frame is the head’s hitbox. Set its BackgroundTransparency to 1! Now we’ll declare this hitbox to be an entity in our 2D World! To do this:

local HitboxEntity = EntityModule.new(path.to.headhitbox, Vector2.new(0, 0), Vector2.new(0, 0), 2)

We’ll not move this entity, so there’s no need to call any functions for this entity.

To check whether the apple and the headhitbox, collide, we can make a function :CheckHeadCollision() in the Entity module.

function Entity:CheckHeadCollision(head)
	local centerOfApple = self.position + self.frame.AbsoluteSize/2
	local centerOfHead = head.AbsolutePosition + head.AbsoluteSize/2
	
	local appleRadius = self.frame.AbsoluteSize.x/2
	local headRadius = head.AbsoluteSize.x/2
	
	if (centerOfHead - centerOfApple).magnitude < appleRadius + headRadius then
		-- they are colliding
	end
end

We check if the distance between the apple and the head is less than the sum of radiuses of both the head and the apple, if so, then the apple and the head are colliding! Note, that this only works if the apple and the head are circles! I used circles to make your job easier!

Now we need to adjust the velocity such that, the apple bounces on Newton’s head and then falls in front of him!

function Entity:CheckHeadCollision(head)
	local centerOfApple = self.position + self.frame.AbsoluteSize/2
	local centerOfHead = head.frame.AbsolutePosition + head.frame.AbsoluteSize/2
	
	local appleRadius = self.frame.AbsoluteSize.x/2
	local headRadius = head.frame.AbsoluteSize.x/2
	
	if (centerOfApple - centerOfHead).magnitude < appleRadius + headRadius then
		-- they are colliding
		
		self.velocity = Vector2.new(self.velocity.x, -self.velocity.y)
		self.velocity += Vector2.new(4, 0)
	end
end

To bounce the ball on newton’s head, we reverse the direction of the velocity and then move the apple in front of him by adjusting the velocity!

In our client script, we can say:

local EntityModule = require(Entity.module)

local Apple = EntityModule.new(path.to.Apple, Vector2.new(0, 0), Vector2.new(0, 0), 1)
local HitboxEntity = EntityModule.new(path.to.HeadHitbox, Vector2.new(0, 0), Vector2.new(0, 0), 3)

task.wait(5)

game:GetService("RunService").RenderStepped:Connect(function()
	local gravity = Vector2.new(0, .5)
	Apple:CollideWithGround(path.to.ground)
	Apple:CheckHeadCollision(HitboxEntity)
	Apple:ApplyForce(gravity)
	Apple:Update()
end)

Here’s the result:

Perfect! You can play around with the velocity to bounce the apple off of his head as you desire!


Conclusion

This short note concludes this tutorial. Bouncing the apple off of his head can also be done using Elastic Collisions! To keep it beginner friendly. I took the easier way! Physics can be simulated using various different ways, I shared the way I use frequently! You do you! I hope you learnt something new, thanks, have fun!

64 Likes

Very well documented in detail tutorial, great job!

2 Likes

only thing i have to say is that it’s more like a bouncy ball then an apple. great job on this

1 Like

Haha, true, The reason why went for a circle instead of an image of an apple is because of Rotations, i didn’t want to over complicate things for people because me myself find it hard to rotate objects according to where they collide with another object etc, it would have been a stiff apple falling from the tree! :grin:

2 Likes

i am no scripter, but is there no way to make it less bouncy? liek it just hits the head then rolls off?

1 Like

Yes!

There’s a damping value thats being multiplied to the velocity to decrease its bouncing rate. Keep on decreasing this number to reduce the bounces!

Another way would be to reduce the x value of this vector:

Example:

Ideally the 2nd option is better for more natural simulations

You can also damp the velocity, only when it hits the head!

4 Likes

Greatest physics tutorial maker in roblox ever! Nice detail on man face newton I learnt a lot from this!

3 Likes

Could I use projectile calculation with this? If so how would I exactly?