Search Unity

Spring float value

Discussion in 'Scripting' started by petey, Feb 13, 2017.

  1. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hi All,

    Every now and then I've wanted to be able to add a springyness to a float but I've never found a nice way to do it. I'd like to have a 'target' float that the springy one aims to reach like the blue line in this pic.

    I don't really want to use a tweener script as they tend to be locked to a preset (springy) curve, I'd like something more dynamic.
    Any ideas where I could start with that?

    Thanks,
    Pete
     
  2. AndyGainey

    AndyGainey

    Joined:
    Dec 2, 2015
    Posts:
    216
    You can use Unity's built-in AnimationCurve class directly for that; in particular, the Evaluate() function. Doesn't matter if you actually end up using the value for an animation, the class is more generically useful than its name would imply.

    And if you make it a public field on a MonoBehaviour, it'll show up in the inspector and let you edit the curve visually, so you can tweak it to get the exact springiness you're looking for.
     
  3. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Thanks Andy,
    I was looking for something that could work more dynamically than the animation curve (or a tween system).
    I'm trying to get my value to 'chase' the target vale and overshoot dynamically as the target changes.
     
  4. AndyGainey

    AndyGainey

    Joined:
    Dec 2, 2015
    Posts:
    216
    Ah, that kind of more dynamic, I see. You might have success by simply implementing the math of an ideal spring with some damping; it's not too complicated. You'll need to track the current position of the float value and its current velocity. Then during each (fixed) frame, you reduce the current velocity by multiplying it by the damping factor (something a bit less than 1). You further alter the velocity further by adding a force proportional to the current value subtracted from the target value (stronger force if the current value is further away from the target value, weaker if it's close). Update the current value based on this new velocity.

    You can of course use higher quality integration techniques to keep the physics clean, but the simplest form might be good enough to get a passable result, especially if you take steps to detect that the velocity is sufficiently small and current value sufficiently close to the target value such that you simply set velocity to zero and the current value to the target value.

    Here's an untested shot at some sample code. Mass has been removed from the equations, since you're not necessarily dealing with literally physical systems, and stiffness as a tweakable parameter should be enough to get the same effect anyway.
    Code (CSharp):
    1. float currentValue;
    2. float currentVelocity;
    3. float targetValue;
    4. float stiffness = 1f; // value highly dependent on use case
    5. float damping = 0.1f; // 0 is no damping, 1 is a lot, I think
    6. float valueThreshold = 0.01f;
    7. float velocityThreshold = 0.01f;
    8.  
    9. void FixedUpdate()
    10. {
    11.     float dampingFactor = Mathf.Max(0, 1 - damping * Time.fixedDeltaTime);
    12.     float acceleration = (targetValue - currentValue) * stiffness * Time.fixedDeltaTime;
    13.     currentVelocity = currentVelocity * dampingFactor + acceleration;
    14.     currentValue += currentVelocity * Time.fixedDeltaTime;
    15.  
    16.     if (Mathf.Abs(currentValue - targetValue) < valueThreshold && Mathf.Abs(currentVelocity) < velocityThreshold)
    17.     {
    18.         currentValue = targetValue;
    19.         currentVelocity = 0f;
    20.     }
    21. }
     
    petey and Vashchuk like this.
  5. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey thanks Andy, that's just the kind of thing I was looking for!
    P.
     
  6. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey @AndyGainey,

    I've been using this quite a bit and it's really great! There is one instance though where I'd like to use it outside of FixedUpdate because the time is scaled to 0. Just wondering what should I multiply by to keep things consistent as Time.fixedDeltaTime wont work in that instance? Say if it was in Update would Time.UnscaledDeltaTime be okay?

    Thanks!
    Pete