Coding a Physics-Based Spring

Introduction


Hello there!

In this forum post, I’ll show you the inner workings of a physics-based spring and how to make your own! But hold on, what are the uses of such a thing?

For example, FPS games use springs for gun recoil, sway, walk cycle, and much more. And there are a ton more use-cases that extend beyond FPS games.

The base code in this post is from this tutorial made by The Step Event. All I did was port it into Roblox as a module script (and tweak a few stuff). So, massive credits to him!

So without further ado, let’s begin!

Start


Before we start doing anything, let’s create a module script. Here’s the code:

  • Target - spring’s resting position
  • Tension - aggresiveness of wobble
  • Dampening - fadeout of spring
local spring = {}
spring.__index = spring

function spring.new(target: any)
	local self = {}
	
	self.Target = target
	self.Position = target
	self.Velocity = 0
	
	self.Tension = .025
	self.Dampening = .3
	
	return setmetatable(self, spring)
end

function spring:Update()
end

function spring:Impulse(num: number)
end

return spring

And yes, there are empty functions. We’ll get back to them later.

A spring has two states, a resting state and an active (extension/contraction) state. If you stretch a spring upwards, it bounces up and down and slows down until it reaches its resting state. So how will we mimic this behavior? Let’s first start with Hooke’s Law.

Hooke’s Law equates to the force needed to push the spring back into it’s rest state. This is why we negate k in the equation.

F = -k * x
F = -tension * displacement

And to actually simulate the spring’s movement, we’ll need Newton’s Second Law.

F = m * a
F = mass * acceleration

Combining Hooke’s law with Newton’s second law, we get this.

F = (-k * m) / x
F = (-tension * mass) / displacement

For simplicity’s sake, let’s assume our spring has a mass of 1.

F = -k * x
F = -tension * displacement

Then for the loss of energy (or dampening), we can use this formula.

F = d * v
F = dampening * velocity

And in combining these formulas, we get this.

F = (-k * x) - (d * v)
F = (-tension * displacement) - (dampening * velocity)

Okay, okay, that’s nice. Time to convert it into code. Remember that an object has a position, velocity changes the position, and acceleration changes velocity.

function spring:Update()
	self.Velocity += (-self.Tension * (self.Position - self.Target)) - (self.Dampening * self.Velocity)
	self.Position += self.Velocity
end

And of course, we can’t forget our impulse function.

function spring:Impulse(num: number)
	self.Velocity += num
end

Everything should look like this now:

--[[
Description:
	A physics-based spring, useful for many applications like 
	gun recoil, viewmodel arms sway, and etc..
	
Explanation:
	Basics of Motion:
		An object has position,
		Velocity changes position,
		Acceleration changes velocity.

	Hooke's Law (force of spring):
		F = -tension * displacement

	Newton's Second Law (acceleration of spring):
		F = mass * acceleration
		(let mass be 1)
		F = acceleration

	Dampening (damper of spring):
		F = dampening * velocity

	Combination:
		F = (-tension * displacement) - (dampening * velocity)
]]

local spring = {}
spring.__index = spring

function spring.new(target: any)
	local self = {}
	
	self.Target = target
	self.Position = target
	self.Velocity = 0
	
	self.Tension = .025
	self.Dampening = .3
	
	return setmetatable(self, spring)
end

function spring:Update()
	self.Velocity += (-self.Tension * (self.Position - self.Target)) - (self.Dampening * self.Velocity)
	self.Position += self.Velocity
end

function spring:Impulse(num: number)
	self.Velocity += num
end

return spring

And that’s practically it, you can now go ham with your new physics-based spring module. Thanks for reading!

Example


Moving Part

local spring = require(script.Spring).new(5)
spring.Dampening = .1
spring.Tension = .1
local random = Random.new()

RNS.Heartbeat:Connect(function()	
	spring:Update()
	workspace.Test.Position = Vector3.new(-8.025, spring.Position, 44.45)
end)

while true do
	spring:Impulse(2)
	task.wait(2)
	
	spring:Impulse(-2)
	task.wait(2)
end

Bouncy Pad

local RNS = game:GetService("RunService")

local part = script.Parent
local spring = require(script.Spring).new(part.Position.Y)
spring.Dampening = .1
spring.Tension = .5
local deb = false

RNS.Heartbeat:Connect(function(delta)	
	spring:Update(delta)
	part.Position = Vector3.new(part.Position.X, spring.Position, part.Position.Z)
end)

part.Touched:Connect(function()
	if deb then 
		return
	end
	
	deb = true
	
	part.BrickColor = BrickColor.Red()
	spring:Impulse(-1)
	task.wait(3)
	part.BrickColor = BrickColor.Green()
	
	deb = false
end)

33 Likes

wow this is very cool. is there any way to make this work for cameras?

2 Likes

The spring module does not directly change parts, it just stores values. So yes, you can use the values to move the Camera CFrame.

2 Likes

I’ve a question. Why did you set the value of velocity to 0 * target? Wouldn’t it just set the value to 0?

1 Like

Oh yeah right, didn’t notice that. I changed the code now.

1 Like

For those of you trying to fine tune your spring values, here’s an approach I follow:

variables:
d = damping constant
m = mass constant (in the code above, they use 1 for mass)
k = spring constant (tension)

Case 1: Overdamped Spring

d^2 > 4mk

Case 2: Critically Damped Spring

d^2 = 4mk

Case 3: Underdamped Spring

d^2 < 4mk

Based on the type of spring you want, adjust the variables so that they fit the specific case above.

8 Likes

attempt to perform arithmetic (sub) on Vector3 and number :kissing_heart:

1 Like

Please state the line and script on which this error occured. Also provide the code you used to produce this error.

1 Like

Heya there!

This is a pretty old-ish thread so I haven’t really thought about it but, unfortunately as of now, the code I supplied doesn’t support Vectors. The examples I gave “uses Vectors” but if you look closely, the only values I’m feeding are numbers.

You could probably modify the code itself to make it work with Vectors, but that’s all I can tell you- (I’m a bit busy atm)

1 Like