You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/Feel/MMFeedbacks/MMFeedbacks/Feedbacks/MMF_SquashAndStretch.cs

385 lines
13 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Serialization;
namespace MoreMountains.Feedbacks
{
/// <summary>
/// This feedback will let you modify the scale of an object on an axis while the other two axis (or only one) get automatically modified to conserve mass
/// </summary>
[AddComponentMenu("")]
[FeedbackPath("Transform/Squash and Stretch")]
[FeedbackHelp("This feedback will let you modify the scale of an object on an axis while the other two axis (or only one) get automatically modified to conserve mass.")]
public class MMF_SquashAndStretch : MMF_Feedback
{
/// sets the inspector color for this feedback
#if UNITY_EDITOR
public override Color FeedbackColor { get { return MMFeedbacksInspectorColors.TransformColor; } }
public override bool EvaluateRequiresSetup() { return (SquashAndStretchTarget == null); }
public override string RequiredTargetText { get { return SquashAndStretchTarget != null ? SquashAndStretchTarget.name : ""; } }
public override string RequiresSetupText { get { return "This feedback requires that a SquashAndStretchTarget be set to be able to work properly. You can set one below."; } }
#endif
public override bool HasAutomatedTargetAcquisition => true;
protected override void AutomateTargetAcquisition() => SquashAndStretchTarget = FindAutomatedTarget<Transform>();
/// a static bool used to disable all feedbacks of this type at once
public static bool FeedbackTypeAuthorized = true;
/// the possible modes this feedback can operate on
public enum Modes { Absolute, Additive, ToDestination }
/// the various axis on which to apply the squash and stretch
public enum PossibleAxis { XtoYZ, XtoY, XtoZ, YtoXZ, YtoX, YtoZ, ZtoXZ, ZtoX, ZtoY }
/// the possible timescales for the animation of the scale
public enum TimeScales { Scaled, Unscaled }
[MMFInspectorGroup("Squash & Stretch", true, 54, true)]
/// the object to animate
[Tooltip("the object to animate")]
public Transform SquashAndStretchTarget;
/// the mode this feedback should operate on
/// Absolute : follows the curve
/// Additive : adds to the current scale of the target
/// ToDestination : sets the scale to the destination target, whatever the current scale is
[Tooltip("the mode this feedback should operate on" +
"Absolute : follows the curve" +
"Additive : adds to the current scale of the target" +
"ToDestination : sets the scale to the destination target, whatever the current scale is")]
public Modes Mode = Modes.Absolute;
public PossibleAxis Axis = PossibleAxis.YtoXZ;
/// the duration of the animation
[Tooltip("the duration of the animation")]
public float AnimateScaleDuration = 0.2f;
/// the value to remap the curve's 0 value to
[Tooltip("the value to remap the curve's 0 value to")]
public float RemapCurveZero = 1f;
/// the value to remap the curve's 1 value to
[Tooltip("the value to remap the curve's 1 value to")]
[FormerlySerializedAs("Multiplier")]
public float RemapCurveOne = 2f;
/// how much should be added to the curve
[Tooltip("how much should be added to the curve")]
public float Offset = 0f;
/// the curve along which to animate the scale
[Tooltip("the curve along which to animate the scale")]
public AnimationCurve AnimateCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(0.3f, 1.5f), new Keyframe(1, 0));
/// if this is true, calling that feedback will trigger it, even if it's in progress. If it's false, it'll prevent any new Play until the current one is over
[Tooltip("if this is true, calling that feedback will trigger it, even if it's in progress. If it's false, it'll prevent any new Play until the current one is over")]
public bool AllowAdditivePlays = false;
/// if this is true, initial and destination scales will be recomputed on every play
[Tooltip("if this is true, initial and destination scales will be recomputed on every play")]
public bool DetermineScaleOnPlay = false;
/// the scale to reach when in ToDestination mode
[Tooltip("the scale to reach when in ToDestination mode")]
[MMFEnumCondition("Mode", (int)Modes.ToDestination)]
public float DestinationScale = 2f;
/// the duration of this feedback is the duration of the scale animation
public override float FeedbackDuration { get { return ApplyTimeMultiplier(AnimateScaleDuration); } set { AnimateScaleDuration = value; } }
public override bool HasRandomness => true;
protected Vector3 _initialScale;
protected float _initialAxisScale;
protected Vector3 _newScale;
protected Coroutine _coroutine;
/// <summary>
/// On init we store our initial scale
/// </summary>
/// <param name="owner"></param>
protected override void CustomInitialization(MMF_Player owner)
{
base.CustomInitialization(owner);
if (Active && (SquashAndStretchTarget != null))
{
GetInitialScale();
}
}
/// <summary>
/// Stores initial scale for future use
/// </summary>
protected virtual void GetInitialScale()
{
_initialScale = SquashAndStretchTarget.localScale;
}
protected virtual void GetAxisScale()
{
switch (Axis)
{
case PossibleAxis.XtoYZ:
_initialAxisScale = SquashAndStretchTarget.localScale.x;
break;
case PossibleAxis.XtoY:
_initialAxisScale = SquashAndStretchTarget.localScale.x;
break;
case PossibleAxis.XtoZ:
_initialAxisScale = SquashAndStretchTarget.localScale.x;
break;
case PossibleAxis.YtoXZ:
_initialAxisScale = SquashAndStretchTarget.localScale.y;
break;
case PossibleAxis.YtoX:
_initialAxisScale = SquashAndStretchTarget.localScale.y;
break;
case PossibleAxis.YtoZ:
_initialAxisScale = SquashAndStretchTarget.localScale.y;
break;
case PossibleAxis.ZtoXZ:
_initialAxisScale = SquashAndStretchTarget.localScale.z;
break;
case PossibleAxis.ZtoX:
_initialAxisScale = SquashAndStretchTarget.localScale.z;
break;
case PossibleAxis.ZtoY:
_initialAxisScale = SquashAndStretchTarget.localScale.z;
break;
}
}
/// <summary>
/// On Play, triggers the scale animation
/// </summary>
/// <param name="position"></param>
/// <param name="feedbacksIntensity"></param>
protected override void CustomPlayFeedback(Vector3 position, float feedbacksIntensity = 1.0f)
{
if (!Active || !FeedbackTypeAuthorized || (SquashAndStretchTarget == null))
{
return;
}
if (DetermineScaleOnPlay && NormalPlayDirection)
{
GetInitialScale();
}
GetAxisScale();
float intensityMultiplier = ComputeIntensity(feedbacksIntensity, position);
if (Active || Owner.AutoPlayOnEnable)
{
if ((Mode == Modes.Absolute) || (Mode == Modes.Additive))
{
if (!AllowAdditivePlays && (_coroutine != null))
{
return;
}
_coroutine = Owner.StartCoroutine(AnimateScale(SquashAndStretchTarget, FeedbackDuration, AnimateCurve, Axis, RemapCurveZero, RemapCurveOne * intensityMultiplier));
}
if (Mode == Modes.ToDestination)
{
if (!AllowAdditivePlays && (_coroutine != null))
{
return;
}
_coroutine = Owner.StartCoroutine(ScaleToDestination());
}
}
}
/// <summary>
/// An internal coroutine used to scale the target to its destination scale
/// </summary>
/// <returns></returns>
protected virtual IEnumerator ScaleToDestination()
{
if (SquashAndStretchTarget == null)
{
yield break;
}
if (FeedbackDuration == 0f)
{
yield break;
}
float journey = NormalPlayDirection ? 0f : FeedbackDuration;
_initialScale = SquashAndStretchTarget.localScale;
_newScale = _initialScale;
GetAxisScale();
IsPlaying = true;
while ((journey >= 0) && (journey <= FeedbackDuration) && (FeedbackDuration > 0))
{
float percent = Mathf.Clamp01(journey / FeedbackDuration);
float newScale = Mathf.LerpUnclamped(_initialAxisScale, DestinationScale, AnimateCurve.Evaluate(percent) + Offset);
newScale = MMFeedbacksHelpers.Remap(newScale, 0f, 1f, RemapCurveZero, RemapCurveOne);
ApplyScale(newScale);
SquashAndStretchTarget.localScale = _newScale;
journey += NormalPlayDirection ? FeedbackDeltaTime : -FeedbackDeltaTime;
yield return null;
}
ApplyScale(DestinationScale);
SquashAndStretchTarget.localScale = NormalPlayDirection ? _newScale : _initialScale;
_coroutine = null;
IsPlaying = false;
yield return null;
}
/// <summary>
/// An internal coroutine used to animate the scale over time
/// </summary>
/// <param name="targetTransform"></param>
/// <param name="duration"></param>
/// <param name="curveX"></param>
/// <param name="curveY"></param>
/// <param name="curveZ"></param>
/// <param name="multiplier"></param>
/// <returns></returns>
protected virtual IEnumerator AnimateScale(Transform targetTransform, float duration, AnimationCurve curve, PossibleAxis axis, float remapCurveZero = 0f, float remapCurveOne = 1f)
{
if (targetTransform == null)
{
yield break;
}
if (duration == 0f)
{
yield break;
}
float journey = NormalPlayDirection ? 0f : duration;
_initialScale = targetTransform.localScale;
IsPlaying = true;
while ((journey >= 0) && (journey <= duration) && (duration > 0))
{
float percent = Mathf.Clamp01(journey / duration);
ComputeAndApplyScale(percent, curve, remapCurveZero, remapCurveOne, targetTransform);
journey += NormalPlayDirection ? FeedbackDeltaTime : -FeedbackDeltaTime;
yield return null;
}
ComputeAndApplyScale(1f, curve, remapCurveZero, remapCurveOne, targetTransform);
_coroutine = null;
IsPlaying = false;
yield return null;
}
/// <summary>
/// Computes the new scale based on the current percent, and applies it to the transform
/// </summary>
/// <param name="percent"></param>
/// <param name="curve"></param>
/// <param name="remapCurveZero"></param>
/// <param name="remapCurveOne"></param>
/// <param name="targetTransform"></param>
protected virtual void ComputeAndApplyScale(float percent, AnimationCurve curve, float remapCurveZero, float remapCurveOne, Transform targetTransform)
{
float newScale = curve.Evaluate(percent) + Offset;
newScale = MMFeedbacksHelpers.Remap(newScale, 0f, 1f, remapCurveZero, remapCurveOne);
if (Mode == Modes.Additive)
{
newScale += _initialAxisScale;
}
newScale = Mathf.Abs(newScale);
ApplyScale(newScale);
targetTransform.localScale = _newScale;
}
/// <summary>
/// Applies the new scale on the selected axis
/// </summary>
/// <param name="newScale"></param>
protected virtual void ApplyScale(float newScale)
{
float invertScale = 1 / Mathf.Sqrt(newScale);
switch (Axis)
{
case PossibleAxis.XtoYZ:
_newScale.x = newScale;
_newScale.y = invertScale;
_newScale.z = invertScale;
break;
case PossibleAxis.XtoY:
_newScale.x = newScale;
_newScale.y = invertScale;
_newScale.z = _initialScale.z;
break;
case PossibleAxis.XtoZ:
_newScale.x = newScale;
_newScale.y = _initialScale.y;
_newScale.z = invertScale;
break;
case PossibleAxis.YtoXZ:
_newScale.x = invertScale;
_newScale.y = newScale;
_newScale.z = invertScale;
break;
case PossibleAxis.YtoX:
_newScale.x = invertScale;
_newScale.y = newScale;
_newScale.z = _initialScale.z;
break;
case PossibleAxis.YtoZ:
_newScale.x = newScale;
_newScale.y = _initialScale.y;
_newScale.z = invertScale;
break;
case PossibleAxis.ZtoXZ:
_newScale.x = invertScale;
_newScale.y = invertScale;
_newScale.z = newScale;
break;
case PossibleAxis.ZtoX:
_newScale.x = invertScale;
_newScale.y = _initialScale.y;
_newScale.z = newScale;
break;
case PossibleAxis.ZtoY:
_newScale.x = _initialScale.x;
_newScale.y = invertScale;
_newScale.z = newScale;
break;
}
}
/// <summary>
/// On stop, we interrupt movement if it was active
/// </summary>
/// <param name="position"></param>
/// <param name="feedbacksIntensity"></param>
protected override void CustomStopFeedback(Vector3 position, float feedbacksIntensity = 1.0f)
{
if (Active && (_coroutine != null))
{
Owner.StopCoroutine(_coroutine);
_coroutine = null;
IsPlaying = false;
}
}
/// <summary>
/// On disable we reset our coroutine
/// </summary>
public override void OnDisable()
{
_coroutine = null;
}
/// <summary>
/// On restore, we restore our initial state
/// </summary>
protected override void CustomRestoreInitialValues()
{
if (!Active || !FeedbackTypeAuthorized)
{
return;
}
SquashAndStretchTarget.localScale = _initialScale;
}
}
}