Weird behavior when creating custom spring

Hey all! I’m working on a custom spring component (yes, I know there’s a built in spring component, I just thought it would be cool to make my own).

All the issues I’m running into are because I am not the best at physics.

THE ISSUES:
Any physics object attached to my custom spring acts normally at first (and when I say normally, I mean predictably), however, once I start moving any object that the other end of the spring is connected to, my physics object will act sporadically and won’t slow down.

Here’s a demonstration of what I mean:
ScreenRecording2025-01-27194922-ezgif.com-video-to-gif-converter

I think I maybe messed up Hooke’s Law? The resources I looked at were for a singular connection point, while I’m trying to make one object be suspended my multiple springs.

My end goal is to hopefully be able to use these springs in a softbody simulation, but who knows.

Here is my code (implementation of Hooke’s Law starts at line 44):

System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Spring : MonoBehaviour
{
    public List<GameObject> connections;


    [SerializeField] float springConstant = 25f;
    [SerializeField] float dampingValue = 25f;

    Rigidbody self;

    Vector3 equalibriumPos;
    Vector3 originalPos;

    Vector3 averageConnectionPoint;

    float originalDist;

    private void Start()
    {
        self = GetComponent<Rigidbody>();
        originalPos = transform.position;


        foreach (GameObject obj in connections)
        {
            originalDist += Vector3.Distance(originalPos, obj.transform.position);
        }
        originalDist = originalDist / connections.Count();



        equalibriumPos = originalPos;
    }

    private void Update()
    {
        UpdateAverageConnectionPoint();
    }

    private void FixedUpdate()
    {
        //EQUATION FOR SPRING: FORCE = -velocity -SPRING CONSTANT * DISTANCE OF OBJ FROM EQUALIBRIUM
        equalibriumPos = GetEqualibrium(); 

        float distance = transform.position.y - equalibriumPos.y;
        float hooksLawForce = (-dampingValue * self.linearVelocity.magnitude) + (-springConstant * distance);

        Vector3 forceDirection = (averageConnectionPoint - transform.position).normalized;

        self.AddForce(forceDirection * hooksLawForce);
    }

    private Vector3 GetEqualibrium()
    {
        Vector3 direction = transform.position - averageConnectionPoint; //Vector that points from our averagePoint to the position of our object
        direction = direction.normalized; //Normalize the vector to only get direction
        Vector3 sizedDirection = direction * originalDist;

        // GET ENDPOINT OF DIRECTION VECTOR: endpoint = startingPoint + vector; 
        Vector3 endPoint = averageConnectionPoint + sizedDirection;

        return endPoint;
    }

    private void UpdateAverageConnectionPoint()
    {
        averageConnectionPoint = Vector3.zero;
        foreach (GameObject obj in connections)
        {
            averageConnectionPoint += obj.transform.position;
        }
        averageConnectionPoint = averageConnectionPoint / connections.Count();
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.blue;
        Gizmos.DrawSphere(equalibriumPos, 0.25f);
        Gizmos.DrawLine(equalibriumPos, averageConnectionPoint);

        Gizmos.color = Color.green;
        foreach (GameObject obj in connections)
        {
            Gizmos.DrawLine(obj.transform.position, transform.position);
        }

        Gizmos.color = Color.red;
        Gizmos.DrawLine(transform.position, equalibriumPos);
        Gizmos.DrawSphere(averageConnectionPoint, 0.25f);

        Gizmos.color = Color.blue;
        Gizmos.DrawWireSphere(transform.position, 0.5f);

    }
}

The spring constant I’m using is 50, and the Damping value is 0.25.

As the code performs part of the calculations in Update, the simulation will vary depending on the frame rate. Always use FixedUpdate for all your physics-related calculations, so the code has a consolidated physics state it can work with.

As per the results you’re observing, some spring setups might require higher simulation rates to reach stability. If this is the case, then increasing the physics simulation rate may improve the stability. The simulation rate is configured in Project Settings > Time > Fixed Timestep, which is 0.02 by default (50 Hz). You may set the timestep to 0.01 to reach 100 Hz, 0.005 for 200 Hz, and so on (rate = 1 / timestep).

1 Like

Okay, I put everything in fixed update, and set the time step to 0.005. I still feel like something is a bit off with my equation. The damping still feels a bit off.

Any suggestions for the equation I’m using?
Either way, thank you so much for the time step example!

I don’t have experience in this kind of setups, but I don’t think the the damping should use the rigidbody’s velocity. I’d use the rate of change of the distance to the equilibrium point.

If the issues persist, then I’d try with the most simple setups possible first, for example a single spring and a fixed, well-defined equilibrium position. When that is working as expected, then I’d add another layer of complexity on top of it (i.e. two springs), check if it’s working and expected, figure out why otherwise, and keep iterating like that.