-
-
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; | |
| } | |
| } | |
| } |
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);
}
}
#endregionMoveTowardsCurrentLine 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
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