-
-
Save FleshMobProductions/7b523b81d7595e685410be11b24aac3f to your computer and use it in GitHub Desktop.
using UnityEngine; | |
namespace FMPUtils.Extensions | |
{ | |
public static class SpringMotion | |
{ | |
// Reference video: | |
// https://www.youtube.com/watch?v=bFOAipGJGA0 | |
// Instant "Game Feel" Tutorial - Secrets of Springs Explained (by Toyful Games) | |
// The channel LlamAcademy also made an adaption of the concept for Unity, | |
// "Add JUICE to Your Game with Springs | Unity Tutorial" - you can watch it here: | |
// https://www.youtube.com/watch?v=6mR7NSsi91Y | |
// Copyright notice of the original source: | |
/****************************************************************************** | |
Copyright (c) 2008-2012 Ryan Juckett | |
http://www.ryanjuckett.com/ | |
This software is provided 'as-is', without any express or implied | |
warranty. In no event will the authors be held liable for any damages | |
arising from the use of this software. | |
Permission is granted to anyone to use this software for any purpose, | |
including commercial applications, and to alter it and redistribute it | |
freely, subject to the following restrictions: | |
1. The origin of this software must not be misrepresented; you must not | |
claim that you wrote the original software. If you use this software | |
in a product, an acknowledgment in the product documentation would be | |
appreciated but is not required. | |
2. Altered source versions must be plainly marked as such, and must not be | |
misrepresented as being the original software. | |
3. This notice may not be removed or altered from any source | |
distribution. | |
******************************************************************************/ | |
// This file uses altered parts from the source: Comments are kept from the | |
// original article (for the most part) while the code has been translated from | |
// c++ to C# using UnityEngine Mathf methods. The original source can be found | |
// in this article: https://www.ryanjuckett.com/damped-springs/ | |
// Permissions and conditions from the original license are retained. | |
//****************************************************************************** | |
// Cached set of motion parameters that can be used to efficiently update | |
// multiple springs using the same time step, angular frequency and damping | |
// ratio. | |
//****************************************************************************** | |
public struct DampedSpringMotionParams | |
{ | |
// newPos = posPosCoef*oldPos + posVelCoef*oldVel | |
public float posPosCoef, posVelCoef; | |
// newVel = velPosCoef*oldPos + velVelCoef*oldVel | |
public float velPosCoef, velVelCoef; | |
}; | |
//****************************************************************************** | |
// This function will compute the parameters needed to simulate a damped spring | |
// over a given period of time. | |
// - An angular frequency is given to control how fast the spring oscillates. | |
// - A damping ratio is given to control how fast the motion decays. | |
// damping ratio > 1: over damped | |
// damping ratio = 1: critically damped | |
// damping ratio < 1: under damped | |
//****************************************************************************** | |
private static DampedSpringMotionParams CalcDampedSpringMotionParams( | |
float deltaTime, // time step to advance | |
float angularFrequency, // angular frequency of motion | |
float dampingRatio) // damping ratio of motion | |
{ | |
const float epsilon = 0.0001f; | |
DampedSpringMotionParams pOutParams; | |
// force values into legal range | |
if (dampingRatio < 0.0f) dampingRatio = 0.0f; | |
if (angularFrequency < 0.0f) angularFrequency = 0.0f; | |
// if there is no angular frequency, the spring will not move and we can | |
// return identity | |
if (angularFrequency < epsilon) | |
{ | |
pOutParams.posPosCoef = 1.0f; pOutParams.posVelCoef = 0.0f; | |
pOutParams.velPosCoef = 0.0f; pOutParams.velVelCoef = 1.0f; | |
return pOutParams; | |
} | |
if (dampingRatio > 1.0f + epsilon) | |
{ | |
// over-damped | |
float za = -angularFrequency * dampingRatio; | |
float zb = angularFrequency * Mathf.Sqrt(dampingRatio * dampingRatio - 1.0f); | |
float z1 = za - zb; | |
float z2 = za + zb; | |
// Value e (2.7) raised to a specific power | |
float e1 = Mathf.Exp(z1 * deltaTime); | |
float e2 = Mathf.Exp(z2 * deltaTime); | |
float invTwoZb = 1.0f / (2.0f * zb); // = 1 / (z2 - z1) | |
float e1_Over_TwoZb = e1 * invTwoZb; | |
float e2_Over_TwoZb = e2 * invTwoZb; | |
float z1e1_Over_TwoZb = z1 * e1_Over_TwoZb; | |
float z2e2_Over_TwoZb = z2 * e2_Over_TwoZb; | |
pOutParams.posPosCoef = e1_Over_TwoZb * z2 - z2e2_Over_TwoZb + e2; | |
pOutParams.posVelCoef = -e1_Over_TwoZb + e2_Over_TwoZb; | |
pOutParams.velPosCoef = (z1e1_Over_TwoZb - z2e2_Over_TwoZb + e2) * z2; | |
pOutParams.velVelCoef = -z1e1_Over_TwoZb + z2e2_Over_TwoZb; | |
} | |
else if (dampingRatio < 1.0f - epsilon) | |
{ | |
// under-damped | |
float omegaZeta = angularFrequency * dampingRatio; | |
float alpha = angularFrequency * Mathf.Sqrt(1.0f - dampingRatio * dampingRatio); | |
float expTerm = Mathf.Exp(-omegaZeta * deltaTime); | |
float cosTerm = Mathf.Cos(alpha * deltaTime); | |
float sinTerm = Mathf.Sin(alpha * deltaTime); | |
float invAlpha = 1.0f / alpha; | |
float expSin = expTerm * sinTerm; | |
float expCos = expTerm * cosTerm; | |
float expOmegaZetaSin_Over_Alpha = expTerm * omegaZeta * sinTerm * invAlpha; | |
pOutParams.posPosCoef = expCos + expOmegaZetaSin_Over_Alpha; | |
pOutParams.posVelCoef = expSin * invAlpha; | |
pOutParams.velPosCoef = -expSin * alpha - omegaZeta * expOmegaZetaSin_Over_Alpha; | |
pOutParams.velVelCoef = expCos - expOmegaZetaSin_Over_Alpha; | |
} | |
else | |
{ | |
// critically damped | |
float expTerm = Mathf.Exp(-angularFrequency * deltaTime); | |
float timeExp = deltaTime * expTerm; | |
float timeExpFreq = timeExp * angularFrequency; | |
pOutParams.posPosCoef = timeExpFreq + expTerm; | |
pOutParams.posVelCoef = timeExp; | |
pOutParams.velPosCoef = -angularFrequency * timeExpFreq; | |
pOutParams.velVelCoef = -timeExpFreq + expTerm; | |
} | |
return pOutParams; | |
} | |
//****************************************************************************** | |
// This function will update the supplied position and velocity values over | |
// according to the motion parameters. | |
//****************************************************************************** | |
private static void UpdateDampedSpringMotion( | |
ref float pPos, // position value to update | |
ref float pVel, // velocity value to update | |
float equilibriumPos, // position to approach | |
DampedSpringMotionParams parameters) // motion parameters to use | |
{ | |
float oldPos = pPos - equilibriumPos; // update in equilibrium relative space | |
float oldVel = pVel; | |
pPos = oldPos * parameters.posPosCoef + oldVel * parameters.posVelCoef + equilibriumPos; | |
pVel = oldPos * parameters.velPosCoef + oldVel * parameters.velVelCoef; | |
} | |
/// <summary> | |
/// Calculate a spring motion development for a given deltaTime | |
/// </summary> | |
/// <param name="position">"Live" position value</param> | |
/// <param name="velocity">"Live" velocity value</param> | |
/// <param name="equilibriumPosition">Goal (or rest) position</param> | |
/// <param name="deltaTime">Time to update over</param> | |
/// <param name="angularFrequency">Angular frequency of motion</param> | |
/// <param name="dampingRatio">Damping ratio of motion</param> | |
public static void CalcDampedSimpleHarmonicMotion(ref float position, ref float velocity, | |
float equilibriumPosition, float deltaTime, float angularFrequency, float dampingRatio) | |
{ | |
var motionParams = CalcDampedSpringMotionParams(deltaTime, angularFrequency, dampingRatio); | |
UpdateDampedSpringMotion(ref position, ref velocity, equilibriumPosition, motionParams); | |
} | |
/// <summary> | |
/// Calculate a spring motion development for a given deltaTime | |
/// </summary> | |
/// <param name="position">"Live" position value</param> | |
/// <param name="velocity">"Live" velocity value</param> | |
/// <param name="equilibriumPosition">Goal (or rest) position</param> | |
/// <param name="deltaTime">Time to update over</param> | |
/// <param name="angularFrequency">Angular frequency of motion</param> | |
/// <param name="dampingRatio">Damping ratio of motion</param> | |
public static void CalcDampedSimpleHarmonicMotion(ref Vector2 position, ref Vector2 velocity, | |
Vector2 equilibriumPosition, float deltaTime, float angularFrequency, float dampingRatio) | |
{ | |
var motionParams = CalcDampedSpringMotionParams(deltaTime, angularFrequency, dampingRatio); | |
UpdateDampedSpringMotion(ref position.x, ref velocity.x, equilibriumPosition.x, motionParams); | |
UpdateDampedSpringMotion(ref position.y, ref velocity.y, equilibriumPosition.y, motionParams); | |
} | |
/// <summary> | |
/// Calculate a spring motion development for a given deltaTime | |
/// </summary> | |
/// <param name="position">"Live" position value</param> | |
/// <param name="velocity">"Live" velocity value</param> | |
/// <param name="equilibriumPosition">Goal (or rest) position</param> | |
/// <param name="deltaTime">Time to update over</param> | |
/// <param name="angularFrequency">Angular frequency of motion</param> | |
/// <param name="dampingRatio">Damping ratio of motion</param> | |
public static void CalcDampedSimpleHarmonicMotion(ref Vector3 position, ref Vector3 velocity, | |
Vector3 equilibriumPosition, float deltaTime, float angularFrequency, float dampingRatio) | |
{ | |
var motionParams = CalcDampedSpringMotionParams(deltaTime, angularFrequency, dampingRatio); | |
UpdateDampedSpringMotion(ref position.x, ref velocity.x, equilibriumPosition.x, motionParams); | |
UpdateDampedSpringMotion(ref position.y, ref velocity.y, equilibriumPosition.y, motionParams); | |
UpdateDampedSpringMotion(ref position.z, ref velocity.z, equilibriumPosition.z, motionParams); | |
} | |
/// <summary> | |
/// Calculate a spring motion development for a given deltaTime quickly without | |
/// considering corner cases for dampingRatio or angularFrequency | |
/// </summary> | |
/// <param name="position">"Live" position value</param> | |
/// <param name="velocity">"Live" velocity value</param> | |
/// <param name="equilibriumPosition">Goal (or rest) position</param> | |
/// <param name="deltaTime">Time to update over</param> | |
/// <param name="angularFrequency">Angular frequency of motion</param> | |
/// <param name="dampingRatio">Damping ratio of motion</param> | |
public static void CalcDampedSimpleHarmonicMotionFast(ref float position, ref float velocity, | |
float equilibriumPosition, float deltaTime, float angularFrequency, float dampingRatio) | |
{ | |
float x = position - equilibriumPosition; | |
velocity += (-dampingRatio * velocity) - (angularFrequency * x); | |
position += velocity * deltaTime; | |
} | |
/// <summary> | |
/// Calculate a spring motion development for a given deltaTime quickly without | |
/// considering corner cases for dampingRatio or angularFrequency | |
/// </summary> | |
/// <param name="position">"Live" position value</param> | |
/// <param name="velocity">"Live" velocity value</param> | |
/// <param name="equilibriumPosition">Goal (or rest) position</param> | |
/// <param name="deltaTime">Time to update over</param> | |
/// <param name="angularFrequency">Angular frequency of motion</param> | |
/// <param name="dampingRatio">Damping ratio of motion</param> | |
public static void CalcDampedSimpleHarmonicMotionFast(ref Vector2 position, ref Vector2 velocity, | |
Vector2 equilibriumPosition, float deltaTime, float angularFrequency, float dampingRatio) | |
{ | |
Vector2 x = position - equilibriumPosition; | |
velocity += (-dampingRatio * velocity) - (angularFrequency * x); | |
position += velocity * deltaTime; | |
} | |
/// <summary> | |
/// Calculate a spring motion development for a given deltaTime quickly without | |
/// considering corner cases for dampingRatio or angularFrequency | |
/// </summary> | |
/// <param name="position">"Live" position value</param> | |
/// <param name="velocity">"Live" velocity value</param> | |
/// <param name="equilibriumPosition">Goal (or rest) position</param> | |
/// <param name="deltaTime">Time to update over</param> | |
/// <param name="angularFrequency">Angular frequency of motion</param> | |
/// <param name="dampingRatio">Damping ratio of motion</param> | |
public static void CalcDampedSimpleHarmonicMotionFast(ref Vector3 position, ref Vector3 velocity, | |
Vector3 equilibriumPosition, float deltaTime, float angularFrequency, float dampingRatio) | |
{ | |
Vector3 x = position - equilibriumPosition; | |
velocity += (-dampingRatio * velocity) - (angularFrequency * x); | |
position += velocity * deltaTime; | |
} | |
} | |
} |
Why don't we use something simple like this?
public static void CalcDampedSimpleHarmonicMotion (
ref float value,
ref float velocity,
float equilibriumPosition,
float deltaTime,
float angularFrequency,
float dampingRatio
)
{
float x = value - equilibriumPosition;
velocity += (-dampingRatio * velocity) - (angularFrequency * x);
value += velocity * deltaTime ;
}
It's quite suitable for similar animations
Unity_HRLjrO2fDz.mp4
I initially just ported the original code, but this looks pretty efficient, thanks for sharing and including an example video! I'll add your method to the script above
Hello, sorry if this seems like a trivial question to ask, but could you kindly share an example on how to use this? I can't seem to be able to get it to work. Thanks.
void Update()
{
if (Input.GetMouseButton(0))
{
Vector3 localPos = new Vector3(2f, 2f, 2f);
SpringMotion.CalcDampedSimpleHarmonicMotionFast(ref localPos, ref velocity, originalPos, Time.deltaTime * 20f, frequency, damping);
}
}
The code above is my attempt at using it. Kindly let me know if I am doing anything wrong. Thanks.
Hello, sorry if this seems like a trivial question to ask, but could you kindly share an example on how to use this? I can't seem to be able to get it to work. Thanks.
protected float value;
protected float velocity;
public float targetValue; //Set in inspector
private void Update()
{
float deltaTime = Time.deltaTime;
CodeExtensions.SpringMotion.CalcDampedSimpleHarmonicMotion(ref value,
ref velocity,
targetValue,
deltaTime,
angularFrequency,
dampingRatio);
SetValue(value);
}
protected override void SetValue(float value)
{
text.fontSize = value;
}
Ignore the access modifiers, I have an abstract class for different types of Sprint motion
Hello, sorry if this seems like a trivial question to ask, but could you kindly share an example on how to use this? I can't seem to be able to get it to work. Thanks.
protected float value; protected float velocity; public float targetValue; //Set in inspector private void Update() { float deltaTime = Time.deltaTime; CodeExtensions.SpringMotion.CalcDampedSimpleHarmonicMotion(ref value, ref velocity, targetValue, deltaTime, angularFrequency, dampingRatio); SetValue(value); } protected override void SetValue(float value) { text.fontSize = value; }Ignore the access modifiers, I have an abstract class for different types of Sprint motion
Thanks a lot. It works now! I truly appreciate your help. Have a nice day!!
Can anyone show me an example by this I can move my player smoothly in the x direction
some like this transform.position = new Vector3 (transform.position.x + 4f,transform.position.y,transform.position.z);
Thanks in advance!
some like this
transform.position = new Vector3 (transform.position.x + 4f,transform.position.y,transform.p
If you want to move your character more smoothly, you don't have to use SpringMotion, personally I would use it for movement last
First of all you don't use Time.deltaTime in your implementation;
transform.position = new Vector3 (transform.position.x + 4f * Time.deltaTime,transform.position.y,transform.position.z);
But i wana to switch line on single btn click here is the code please see
#region ChangeLines
[Space(30)] [Header("Change Lines")]
[SerializeField] private List<Vector3> lines;
[Tooltip("Input Delay in Second")] [Range(0,1f)]
[SerializeField] private float inputDelay = 0.2f;
[SerializeField] private int initialLine = 0;
[SerializeField] private float lineChangeSpeed = 3f;
[SerializeField] private float lineChangeSpeedSmooth = 0.2f;
[SerializeField] private AnimationCurve lineChangeCurve;
[SerializeField] private AnimationCurve lineChangeSmoothCurve;
private float _inputDelay;
private Vector3 _currentTargetedLine;
public int CurrentLineIdx {get; private set;}
private bool _canMoveLR;
private Transform mesh;
private bool _overRideQuaternion;
private bool _isPressingBtn;
/// <summary>
/// Jump Initial SetUp on Awake
/// </summary>
private void InitChangeLines()
{
Utilities.QuickSort(0, lines.Count - 1, ref lines, (lhs, rhs) => lhs.x < rhs.x );
CurrentLineIdx = initialLine;
_inputDelay = inputDelay;
_canMoveLR = true;
mesh = transform.GetChild(0);
_overRideQuaternion = false;
}
private void ChangeLines ()
{
int flag;
#if UNITY_EDITOR
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
#else
if (leftBtn.isPressing) { flag = -1; }
else if (rightBtn.isPressing) { flag = 1; }
else flag = 0;
#endif
_isPressingBtn = flag != 0;
if (flag == 0 || CurrentLineIdx == 0 || CurrentLineIdx == (lines.Count - 1)) Idle();
MoveX(flag);
MoveTowardsCurrentLine(flag);
}
/// <summary>
/// -1 left & 1 right
/// </summary>
/// <param name="flag"></param>
public void MoveX (int flag)
{
if (!_canMoveLR) return;
if (inputDelay >= 0) inputDelay -= Time.deltaTime;
if (inputDelay > 0 || flag == 0) return;
inputDelay = _inputDelay;
if (flag == 1) MoveRight(flag);
else if (flag == -1) MoveLeft(flag);
}
// Helper Functions
private void MoveLeft(int flag)
{
CurrentLineIdx = Mathf.Clamp((CurrentLineIdx - 1), 0, lines.Count - 1);
SetCurrentLine(lines[CurrentLineIdx]);
if (CurrentLineIdx == 0)
{
Idle();
}
else
{
// mesh.DORotate(new Vector3(0, -45, 0), 0.3f);
// .OnComplete(() => {
// if (flag == 0) mesh.DORotate(new Vector3(0, 0, 0), 1f);
// });
}
}
private void MoveRight(int flag)
{
CurrentLineIdx = Mathf.Clamp((CurrentLineIdx + 1), 0, lines.Count - 1);
SetCurrentLine(lines[CurrentLineIdx]);
if (CurrentLineIdx == (lines.Count - 1))
{
Idle();
}
else {
// mesh.DORotate(new Vector3(0, 45, 0), 0.3f);
// .OnComplete(() => {
// if (flag == 0) mesh.DORotate(new Vector3(0, 0, 0), 1f);
// });
}
}
private void Idle()
{
if (_overRideQuaternion) return;
mesh.rotation = Quaternion.Lerp(mesh.rotation, Quaternion.Euler(0,0,0), Time.deltaTime * 8f);
// mesh.DORotate(new Vector3(0, 0, 0), 1f);
}
private void SetCurrentLine (Vector3 line)
{
inputDelay = _inputDelay;
OnLineChange?.Invoke(lines, CurrentLineIdx);
_currentTargetedLine = line;
}
private void MoveTowardsCurrentLine (int moveFlag)
{
var targetPos = Utilities.VectorX(transform, _currentTargetedLine.x);
if (!_isPressingBtn)
{
transform.position = Vector3.Lerp(transform.position, targetPos, lineChangeCurve
.Evaluate(Time.deltaTime * lineChangeSpeed));
}
else
{
transform.position = Vector3.Lerp(transform.position, targetPos,
lineChangeSmoothCurve.Evaluate(Time.smoothDeltaTime * lineChangeSpeedSmooth));
// transform.position = Vector3.MoveTowards (transform.position, targetPos, lineChangeSpeedSmooth * Time.deltaTime);
}
}
#endregion
MoveTowardsCurrentLine
is function where i have to do this logic
Hey how can I check spring is on rest let's suppose I want to animate the button and also wanna to open a panel using this button who can i dot this ?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using Randoms.Motions.Spring;
using UnityEngine.Events;
using UnityEngine.UI;
public class BtnController : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
[HideInInspector] public bool isPressing;
[SerializeField] private Vector2Spring spring;
[SerializeField] Vector2 targetValue;
[SerializeField] private RectTransform obj;
[SerializeField] private float getDowndelay = 1f;
bool isInitized = true;
internal Vector2 _targetValue;
internal Vector2 initialValue;
internal float delay = 1f;
public UnityEvent onClick;
void Start ()
{
initialValue = obj.localScale;
_targetValue = targetValue;
spring.GetSpringValues (targetValue, Time.deltaTime);
}
void SetValue (Vector2 value)
{
obj.localScale = value;
}
public bool wasPressed = false;
void Update ()
{
_targetValue = isPressing ? targetValue : initialValue;
Vector2 springValue = spring.GetSpringValues (_targetValue, Time.deltaTime);
if (isPressing)
{
wasPressed = true;
}
else
{
// calculate delay
if (wasPressed && springValue == initialValue)
{
wasPressed = false;
onClick?.Invoke ();
// getDowndelay -= Time.deltaTime;
// if (getDowndelay < 0)
// {
// onClick?.Invoke ();
// wasPressed = false;
// }
}
}
if (delay > 0)
{
delay -= Time.deltaTime;
}
else
{
SetValue (springValue);
}
}
public void OnPointerDown (PointerEventData eventData)
{
isPressing = true;
}
public void OnPointerUp (PointerEventData eventData)
{
isPressing = false;
}
}
here is the code
Thanks
Good to know, thanks for trying it out!