UI动效痛点:
UI动效一直是Unity游戏开发的一大痛点,大部分项目都在使用Animator/Animation制作UI动效。而Animation是以节点路径记录动画,一旦UI层级、节点名变更就会导致动效返工,且Animation编辑器缓动曲线很难控制,没有预置常用曲线可直接用。易用性、可维护性苦不堪言。
一小部分团队则是使用DOTween让程序员手写动效,即使DOTween简单易用,但脱离动效师主导很难制作出高品质的动效,且使用代码制作动效更加难以维护。
如果把DOTween强大的动效功能,以可视化、零代码的方式使用,即使对于动效师也远比使用Animation简单快速,同时又能解决使用Animation的痛点。
实际上DOTween Pro提供了一个可视化脚本DOTween Animation,坏消息是,不支持Sequence,很不好用,且代码也没有优化gc。
视频教程(视频结尾):
效果预览:
以下是作者花了两三分钟随便点点制作出的UI动效,不代表DOTween的功能上限,仅供参考。
无需运行,编辑器下随时预览动效,所见即所得。
工具需求:
工具的最终目的是简化UI动效制作、降低UI动效的制作门槛,并且易于复用以及维护。
UI动画通常是由一连串的多个组件的动效衔接而成,因此DOTween Pro自带的DOTweenAnimation(单个动效)无法满足需求。最适合的还是用Sequence,支持多个动效并行、串联播放,以实现复杂的动效需求。
并且每个动效都需要支持生命周期回调,比如:某个动效播放完成后需要执行一个函数功能等。
当然,写工具之前首先要了解市面上是否存在优秀的替代品,遗憾的是AssetStore竟然没有找到一款需求相近的插件。
不过github有个与设想高度重合的开源UI动效插件:Unity-AnimationUI
坏消息是此插件自己实现了一套缓动,经过试用发现还存在报错和倒放时回调触发时机错误的bug,最大的问题还是没有依赖比较成熟且免费的DOTween。
因此,最佳方案就是实现个基于DOTween Sequence的可视化脚本。
工具设计:
由于DOTween接口较多,不同组件扩展了多个接口且参数不一,因此需要用最小的内存代价适配所有参数设置;虽然DOTween参数较多,但是不能在面板上显示太多的参数而导致凌乱,可通过勾选框切换不同的参数模式。
1. Add Type: 也就是DOTween.Sequence添加动画的方式,Join并行或Append串行(不懂的去学习DOTween,不再赘述);
2. Target:需要动的物体;
3. AnimationType: 动效类型枚举,控制Target的动效方式,如DOMove、DOScale等等;
4. Delay:动画播放延迟时间;
5. From (勾选Enable启用):播放动画前会把Target设置到该值,作为初始值。(非常实用)
6. To :目标值,可填具体值,勾选ToTarget可将某个组件的值作为目标值,例如把某节点的位置作为移动目标点,而不是一个静态的数值。(非常实用)
7. DurationOrSpeed:动效的过渡用时或速度。勾选UseSpeed该值会作为速度使用(例如位移动效,填10则为每秒10米)
8. Ease:缓动类型,DOTween内置了三十多个缓动类型。勾选Use Curve即可自定义缓动曲线;
9. Loops: 动效循环播放次数,-1为循环播放。支持设置循环类型;
10. UpdateType: 动效刷新类型,用于设置动效在Update、FixedUpdate、LateUpdate中计算;
11. Animation Events: 动效的回调方法;
代码实现:
完整代码可以从开源自动化游戏框架GF_X中获取:https://github.com/sunsvip/GF_X/blob/master/Assets/AAAGame/Scripts/Common/DOTweenSequence.cshttps://github.com/sunsvip/GF_X/blob/master/Assets/AAAGame/Scripts/Common/DOTweenSequence.cs
主要是编辑器扩展功能,没有任何值得一提的难点,直接附上编辑器代码和Runtime代码:
using DG.Tweening;
using System;
using UnityEngine;
using static DOTweenSequence;
using UnityEngine.Events;
using UnityEngine.UI;
#if UNITY_EDITOR
using DG.DOTweenEditor;
using UnityEditorInternal;
using UnityEditor;
#region Editor Inspector
[CanEditMultipleObjects]
[CustomEditor(typeof(DOTweenSequence))]
public class DOTweeSequenceInspector : Editor
{
SerializedProperty m_Sequence;
ReorderableList m_SequenceList;
private void OnEnable()
{
m_Sequence = serializedObject.FindProperty("m_Sequence");
m_SequenceList = new ReorderableList(serializedObject, m_Sequence);
m_SequenceList.drawElementCallback = OnDrawSequenceItem;
m_SequenceList.elementHeightCallback = index =>
{
var item = m_Sequence.GetArrayElementAtIndex(index);
return EditorGUI.GetPropertyHeight(item);
};
m_SequenceList.drawHeaderCallback = OnDrawSequenceHeader;
}
public override void OnInspectorGUI()
{
if (!EditorApplication.isPlaying)
{
EditorGUILayout.BeginHorizontal();
{
if (GUILayout.Button("Play"))
{
if (DOTweenEditorPreview.isPreviewing)
{
DOTweenEditorPreview.Stop(true, true);
(target as DOTweenSequence).DOKill();
}
DOTweenEditorPreview.PrepareTweenForPreview((target as DOTweenSequence).DOPlay());
DOTweenEditorPreview.Start();
}
if (GUILayout.Button("Rewind"))
{
if (DOTweenEditorPreview.isPreviewing)
{
DOTweenEditorPreview.Stop(true, true);
(target as DOTweenSequence).DOKill();
}
DOTweenEditorPreview.PrepareTweenForPreview((target as DOTweenSequence).DORewind());
DOTweenEditorPreview.Start();
}
if (GUILayout.Button("Reset"))
{
DOTweenEditorPreview.Stop(true, true);
(target as DOTweenSequence).DOKill();
}
EditorGUILayout.EndHorizontal();
}
}
serializedObject.Update();
m_SequenceList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
base.OnInspectorGUI();
}
private void OnDrawSequenceHeader(Rect rect)
{
EditorGUI.LabelField(rect, "Animation Sequences");
}
private void OnDrawSequenceItem(Rect rect, int index, bool isActive, bool isFocused)
{
SerializedProperty element = m_Sequence.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(rect, element, true);
}
}
[CustomPropertyDrawer(typeof(SequenceAnimation))]
public class SequenceTweenMoveDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var onPlay = property.FindPropertyRelative("OnPlay");
var onUpdate = property.FindPropertyRelative("OnUpdate");
var onComplete = property.FindPropertyRelative("OnComplete");
return EditorGUIUtility.singleLineHeight * 11 + (property.isExpanded ? (EditorGUI.GetPropertyHeight(onPlay) + EditorGUI.GetPropertyHeight(onUpdate) + EditorGUI.GetPropertyHeight(onComplete)) : 0);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.indentLevel++;
var target = property.FindPropertyRelative("Target");
var addType = property.FindPropertyRelative("AddType");
var tweenType = property.FindPropertyRelative("AnimationType");
var toValue = property.FindPropertyRelative("ToValue");
var useToTarget = property.FindPropertyRelative("UseToTarget");
var toTarget = property.FindPropertyRelative("ToTarget");
var useFromValue = property.FindPropertyRelative("UseFromValue");
var fromValue = property.FindPropertyRelative("FromValue");
var duration = property.FindPropertyRelative("DurationOrSpeed");
var speedBased = property.FindPropertyRelative("SpeedBased");
var delay = property.FindPropertyRelative("Delay");
var customEase = property.FindPropertyRelative("CustomEase");
var ease = property.FindPropertyRelative("Ease");
var easeCurve = property.FindPropertyRelative("EaseCurve");
var loops = property.FindPropertyRelative("Loops");
var loopType = property.FindPropertyRelative("LoopType");
var updateType = property.FindPropertyRelative("UpdateType");
var snapping = property.FindPropertyRelative("Snapping");
var onPlay = property.FindPropertyRelative("OnPlay");
var onUpdate = property.FindPropertyRelative("OnUpdate");
var onComplete = property.FindPropertyRelative("OnComplete");
var lastRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(lastRect, addType);
EditorGUI.BeginChangeCheck();
lastRect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(lastRect, target);
lastRect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(lastRect, tweenType);
if (EditorGUI.EndChangeCheck())
{
var fixedComType = GetFixedComponentType(target.objectReferenceValue as Component, (DOTweenType)tweenType.enumValueIndex);
if (fixedComType != null)
{
target.objectReferenceValue = fixedComType;
}
}
if (target.objectReferenceValue != null && null == GetFixedComponentType(target.objectReferenceValue as Component, (DOTweenType)tweenType.enumValueIndex))
{
lastRect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.HelpBox(lastRect, string.Format("{0}不支持{1}", target.objectReferenceValue == null ? "Target" : target.objectReferenceValue.GetType().Name, tweenType.enumDisplayNames[tweenType.enumValueIndex]), MessageType.Error);
}
//Delay, Snapping
lastRect.y += EditorGUIUtility.singleLineHeight;
var horizontalRect = lastRect;
horizontalRect.width -= 115;
EditorGUI.PropertyField(horizontalRect, delay);
horizontalRect.x += horizontalRect.width;
horizontalRect.width = 115;
snapping.boolValue = EditorGUI.ToggleLeft(horizontalRect, "Snapping", snapping.boolValue);
//From Value
lastRect.y += EditorGUIUtility.singleLineHeight;
horizontalRect = lastRect;
horizontalRect.width -= 115;
//ToTarget
lastRect.y += EditorGUIUtility.singleLineHeight;
var toRect = lastRect;
toRect.width -= 115;
//To Value
var dotweenTp = (DOTweenType)tweenType.enumValueIndex;
switch (dotweenTp)
{
case DOTweenType.DOMoveX:
case DOTweenType.DOMoveY:
case DOTweenType.DOMoveZ:
case DOTweenType.DOLocalMoveX:
case DOTweenType.DOLocalMoveY:
case DOTweenType.DOLocalMoveZ:
case DOTweenType.DOAnchorPosX:
case DOTweenType.DOAnchorPosY:
case DOTweenType.DOAnchorPosZ:
case DOTweenType.DOFade:
case DOTweenType.DOCanvasGroupFade:
case DOTweenType.DOFillAmount:
case DOTweenType.DOValue:
case DOTweenType.DOScaleX:
case DOTweenType.DOScaleY:
case DOTweenType.DOScaleZ:
{
EditorGUI.BeginDisabledGroup(!useFromValue.boolValue);
var value = fromValue.vector4Value;
value.x = EditorGUI.FloatField(horizontalRect, "From", value.x);
fromValue.vector4Value = value;
EditorGUI.EndDisabledGroup();
if (!useToTarget.boolValue)
{
value = toValue.vector4Value;
value.x = EditorGUI.FloatField(toRect, "To", value.x);
toValue.vector4Value = value;
}
}
break;
case DOTweenType.DOAnchorPos:
case DOTweenType.DOFlexibleSize:
case DOTweenType.DOMinSize:
case DOTweenType.DOPreferredSize:
case DOTweenType.DOSizeDelta:
{
EditorGUI.BeginDisabledGroup(!useFromValue.boolValue);
fromValue.vector4Value = EditorGUI.Vector2Field(horizontalRect, "From", fromValue.vector4Value);
EditorGUI.EndDisabledGroup();
if (!useToTarget.boolValue)
toValue.vector4Value = EditorGUI.Vector2Field(toRect, "To", toValue.vector4Value);
}
break;
case DOTweenType.DOMove:
case DOTweenType.DOLocalMove:
case DOTweenType.DOAnchorPos3D:
case DOTweenType.DOScale:
case DOTweenType.DORotate:
case DOTweenType.DOLocalRotate:
{
EditorGUI.BeginDisabledGroup(!useFromValue.boolValue);
fromValue.vector4Value = EditorGUI.Vector3Field(horizontalRect, "From", fromValue.vector4Value);
EditorGUI.EndDisabledGroup();
if (!useToTarget.boolValue)
toValue.vector4Value = EditorGUI.Vector3Field(toRect, "To", toValue.vector4Value);
}
break;
case DOTweenType.DOColor:
{
EditorGUI.BeginDisabledGroup(!useFromValue.boolValue);
fromValue.vector4Value = EditorGUI.ColorField(horizontalRect, "From", fromValue.vector4Value);
EditorGUI.EndDisabledGroup();
if (!useToTarget.boolValue)
toValue.vector4Value = EditorGUI.ColorField(toRect, "To", toValue.vector4Value);
}
break;
}
if (useToTarget.boolValue)
{
toTarget.objectReferenceValue = EditorGUI.ObjectField(toRect, "To", toTarget.objectReferenceValue, target.objectReferenceValue != null ? target.objectReferenceValue.GetType() : typeof(Component), true);
if (toTarget.objectReferenceValue == null)
{
lastRect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.HelpBox(lastRect, "To target cannot be null.", MessageType.Error);
}
}
horizontalRect.x += horizontalRect.width;
horizontalRect.width = 115;
useFromValue.boolValue = EditorGUI.ToggleLeft(horizontalRect, "Enable", useFromValue.boolValue);
toRect.x += toRect.width;
toRect.width = 115;
useToTarget.boolValue = EditorGUI.ToggleLeft(toRect, "ToTarget", useToTarget.boolValue);
//Duration
lastRect.y += EditorGUIUtility.singleLineHeight;
horizontalRect = lastRect;
horizontalRect.width -= 115;
EditorGUI.PropertyField(horizontalRect, duration);
horizontalRect.x += horizontalRect.width;
horizontalRect.width = 115;
speedBased.boolValue = EditorGUI.ToggleLeft(horizontalRect, "Use Speed", speedBased.boolValue);
//Ease
lastRect.y += EditorGUIUtility.singleLineHeight;
horizontalRect = lastRect;
horizontalRect.width -= 115;
if (customEase.boolValue)
EditorGUI.PropertyField(horizontalRect, easeCurve);
else
EditorGUI.PropertyField(horizontalRect, ease);
horizontalRect.x += horizontalRect.width;
horizontalRect.width = 115;
customEase.boolValue = EditorGUI.ToggleLeft(horizontalRect, "Use Curve", customEase.boolValue);
//Loops
lastRect.y += EditorGUIUtility.singleLineHeight;
horizontalRect = lastRect;
horizontalRect.width -= 115;
EditorGUI.PropertyField(horizontalRect, loops);
horizontalRect.x += horizontalRect.width;
horizontalRect.width = 115;
EditorGUI.BeginDisabledGroup(loops.intValue == 1);
loopType.enumValueIndex = (int)(LoopType)EditorGUI.EnumPopup(horizontalRect, (LoopType)loopType.enumValueIndex);
EditorGUI.EndDisabledGroup();
//UpdateType
lastRect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(lastRect, updateType);
//Events
lastRect.y += EditorGUIUtility.singleLineHeight;
property.isExpanded = EditorGUI.Foldout(lastRect, property.isExpanded, "Animation Events");
if (property.isExpanded)
{
//OnPlay
lastRect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(lastRect, onPlay);
//OnUpdate
lastRect.y += EditorGUI.GetPropertyHeight(onPlay);
EditorGUI.PropertyField(lastRect, onUpdate);
//OnComplete
lastRect.y += EditorGUI.GetPropertyHeight(onUpdate);
EditorGUI.PropertyField(lastRect, onComplete);
}
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
}
private static Component GetFixedComponentType(Component com, DOTweenType tweenType)
{
if (com == null) return null;
switch (tweenType)
{
case DOTweenType.DOMove:
case DOTweenType.DOMoveX:
case DOTweenType.DOMoveY:
case DOTweenType.DOMoveZ:
case DOTweenType.DOLocalMove:
case DOTweenType.DOLocalMoveX:
case DOTweenType.DOLocalMoveY:
case DOTweenType.DOLocalMoveZ:
case DOTweenType.DOScale:
case DOTweenType.DOScaleX:
case DOTweenType.DOScaleY:
case DOTweenType.DOScaleZ:
return com.gameObject.GetComponent<Transform>();
case DOTweenType.DOAnchorPos:
case DOTweenType.DOAnchorPosX:
case DOTweenType.DOAnchorPosY:
case DOTweenType.DOAnchorPosZ:
case DOTweenType.DOAnchorPos3D:
case DOTweenType.DOSizeDelta:
return com.gameObject.GetComponent<RectTransform>();
case DOTweenType.DOColor:
case DOTweenType.DOFade:
return com.gameObject.GetComponent<UnityEngine.UI.Graphic>();
case DOTweenType.DOCanvasGroupFade:
return com.gameObject.GetComponent<UnityEngine.CanvasGroup>();
case DOTweenType.DOFillAmount:
return com.gameObject.GetComponent<UnityEngine.UI.Image>();
case DOTweenType.DOFlexibleSize:
case DOTweenType.DOMinSize:
case DOTweenType.DOPreferredSize:
return com.gameObject.GetComponent<UnityEngine.UI.LayoutElement>();
case DOTweenType.DOValue:
return com.gameObject.GetComponent<UnityEngine.UI.Slider>();
}
return null;
}
}
#endregion
#endif
public class DOTweenSequence : MonoBehaviour
{
[HideInInspector][SerializeField] SequenceAnimation[] m_Sequence;
[SerializeField] bool m_PlayOnAwake = false;
[SerializeField] float m_Delay = 0;
[SerializeField] Ease m_Ease = Ease.OutQuad;
[SerializeField] int m_Loops = 1;
[SerializeField] LoopType m_LoopType = LoopType.Restart;
[SerializeField] UpdateType m_UpdateType = UpdateType.Normal;
[SerializeField] bool m_Snapping = false;
[SerializeField] UnityEvent m_OnPlay = null;
[SerializeField] UnityEvent m_OnUpdate = null;
[SerializeField] UnityEvent m_OnComplete = null;
private Tween m_Tween;
private void Awake()
{
InitTween();
if (m_PlayOnAwake) DOPlay();
}
private void InitTween()
{
foreach (var item in m_Sequence)
{
var useFromValue = item.UseFromValue;
if (!useFromValue) continue;
var targetCom = item.Target;
var resetValue = item.FromValue;
switch (item.AnimationType)
{
case DOTweenType.DOMove:
{
(targetCom as Transform).position = resetValue;
break;
}
case DOTweenType.DOMoveX:
{
(targetCom as Transform).SetPositionX(resetValue.x);
break;
}
case DOTweenType.DOMoveY:
{
(targetCom as Transform).SetPositionY(resetValue.x);
break;
}
case DOTweenType.DOMoveZ:
{
(targetCom as Transform).SetPositionZ(resetValue.x);
break;
}
case DOTweenType.DOLocalMove:
{
(targetCom as Transform).localPosition = resetValue;
break;
}
case DOTweenType.DOLocalMoveX:
{
(targetCom as Transform).SetLocalPositionX(resetValue.x);
break;
}
case DOTweenType.DOLocalMoveY:
{
(targetCom as Transform).SetLocalPositionY(resetValue.x);
break;
}
case DOTweenType.DOLocalMoveZ:
{
(targetCom as Transform).SetLocalPositionZ(resetValue.x);
break;
}
case DOTweenType.DOAnchorPos:
{
(targetCom as RectTransform).anchoredPosition = resetValue;
break;
}
case DOTweenType.DOAnchorPosX:
{
(targetCom as RectTransform).SetAnchoredPositionX(resetValue.x);
break;
}
case DOTweenType.DOAnchorPosY:
{
(targetCom as RectTransform).SetAnchoredPositionY(resetValue.x);
break;
}
case DOTweenType.DOAnchorPosZ:
{
(targetCom as RectTransform).SetAnchoredPosition3DZ(resetValue.x);
break;
}
case DOTweenType.DOAnchorPos3D:
{
(targetCom as RectTransform).anchoredPosition3D = resetValue;
break;
}
case DOTweenType.DOColor:
{
(targetCom as UnityEngine.UI.Graphic).color = resetValue;
break;
}
case DOTweenType.DOFade:
{
(targetCom as UnityEngine.UI.Graphic).SetColorAlpha(resetValue.x);
break;
}
case DOTweenType.DOCanvasGroupFade:
{
(targetCom as UnityEngine.CanvasGroup).alpha = resetValue.x;
break;
}
case DOTweenType.DOValue:
{
(targetCom as UnityEngine.UI.Slider).value = resetValue.x;
break;
}
case DOTweenType.DOSizeDelta:
{
(targetCom as RectTransform).sizeDelta = resetValue;
break;
}
case DOTweenType.DOFillAmount:
{
(targetCom as UnityEngine.UI.Image).fillAmount = resetValue.x;
break;
}
case DOTweenType.DOFlexibleSize:
{
(targetCom as LayoutElement).SetFlexibleSize(resetValue);
break;
}
case DOTweenType.DOMinSize:
{
(targetCom as LayoutElement).SetMinSize(resetValue);
break;
}
case DOTweenType.DOPreferredSize:
{
(targetCom as LayoutElement).SetPreferredSize(resetValue);
break;
}
case DOTweenType.DOScale:
{
(targetCom as Transform).localScale = resetValue;
break;
}
case DOTweenType.DOScaleX:
{
(targetCom as Transform).SetLocalScaleX(resetValue.x);
break;
}
case DOTweenType.DOScaleY:
{
(targetCom as Transform).SetLocalScaleY(resetValue.x);
break;
}
case DOTweenType.DOScaleZ:
{
(targetCom as Transform).SetLocalScaleZ(resetValue.z);
break;
}
case DOTweenType.DORotate:
{
(targetCom as Transform).eulerAngles = resetValue;
break;
}
case DOTweenType.DOLocalRotate:
{
(targetCom as Transform).localEulerAngles = resetValue;
break;
}
}
}
}
private Tween CreateTween(bool reverse = false)
{
if (m_Sequence == null || m_Sequence.Length == 0)
{
return null;
}
var sequence = DOTween.Sequence();
if (reverse)
{
for (int i = m_Sequence.Length - 1; i >= 0; i--)
{
var item = m_Sequence[i];
var tweener = item.CreateTween(reverse);
if (tweener == null)
{
Debug.LogErrorFormat("Tweener is null. Index:{0}, Animation Type:{1}, Component Type:{2}", i, item.AnimationType, item.Target == null ? "null" : item.Target.GetType().Name);
continue;
}
switch (item.AddType)
{
case AddType.Append:
sequence.Append(tweener);
break;
case AddType.Join:
sequence.Join(tweener);
break;
}
}
}
else
{
for (int i = 0; i < m_Sequence.Length; i++)
{
var item = m_Sequence[i];
var tweener = item.CreateTween(reverse);
if (tweener == null)
{
Debug.LogErrorFormat("Tweener is null. Index:{0}, Animation Type:{1}, Component Type:{2}", i, item.AnimationType, item.Target == null ? "null" : item.Target.GetType().Name);
continue;
}
switch (item.AddType)
{
case AddType.Append:
sequence.Append(tweener);
break;
case AddType.Join:
sequence.Join(tweener);
break;
}
}
}
sequence.SetEase(m_Ease).SetUpdate(m_UpdateType).SetLoops(m_Loops, m_LoopType).SetDelay(m_Delay);
if (m_OnPlay != null) sequence.OnPlay(m_OnPlay.Invoke);
if (m_OnUpdate != null) sequence.OnUpdate(m_OnUpdate.Invoke);
if (m_OnComplete != null) sequence.OnComplete(m_OnComplete.Invoke);
sequence.SetAutoKill(true);
return sequence;
}
public void Play()
{
DOPlay();
}
public Tween DOPlay()
{
m_Tween = CreateTween();
return m_Tween?.Play();
}
public Tween DORewind()
{
m_Tween = CreateTween(true);
return m_Tween?.Play();
}
public void DOComplete(bool withCallback = false)
{
m_Tween?.Complete(withCallback);
}
public void DOKill()
{
m_Tween?.Kill();
m_Tween = null;
}
public enum DOTweenType
{
DOMove,
DOMoveX,
DOMoveY,
DOMoveZ,
DOLocalMove,
DOLocalMoveX,
DOLocalMoveY,
DOLocalMoveZ,
DOScale,
DOScaleX,
DOScaleY,
DOScaleZ,
DORotate,
DOLocalRotate,
DOAnchorPos,
DOAnchorPosX,
DOAnchorPosY,
DOAnchorPosZ,
DOAnchorPos3D,
DOColor,
DOFade,
DOCanvasGroupFade,
DOFillAmount,
DOFlexibleSize,
DOMinSize,
DOPreferredSize,
DOSizeDelta,
DOValue
}
[Serializable]
public class SequenceAnimation
{
public AddType AddType = AddType.Append;
public DOTweenType AnimationType = DOTweenType.DOMove;
public Component Target = null;
public Vector4 ToValue = Vector4.zero;
public bool UseToTarget = false;
public Component ToTarget = null;
public bool UseFromValue = false;
public Vector4 FromValue = Vector4.zero;
public bool SpeedBased = false;
public float DurationOrSpeed = 1;
public float Delay = 0;
public UpdateType UpdateType = UpdateType.Normal;
public bool CustomEase = false;
public AnimationCurve EaseCurve;
public Ease Ease = Ease.OutQuad;
public int Loops = 1;
public LoopType LoopType = LoopType.Restart;
public bool Snapping = false;
public UnityEvent OnPlay = null;
public UnityEvent OnUpdate = null;
public UnityEvent OnComplete = null;
public Tween CreateTween(bool reverse)
{
Tween result = null;
float duration = this.DurationOrSpeed;
switch (AnimationType)
{
case DOTweenType.DOMove:
{
var transform = Target as Transform;
Vector3 targetValue = UseToTarget ? (ToTarget as Transform).position : ToValue;
Vector3 startValue = UseFromValue ? FromValue : transform.position;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
transform.position = startValue;
if (SpeedBased)
duration = Vector3.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = transform.DOMove(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOMoveX:
{
var transform = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).position.x : ToValue.x;
var startValue = UseFromValue ? FromValue.x : transform.position.x;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
transform.SetPositionX(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = transform.DOMoveX(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOMoveY:
{
var transform = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).position.y : ToValue.x;
var startValue = UseFromValue ? FromValue.x : transform.position.y;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
transform.SetPositionY(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = transform.DOMoveY(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOMoveZ:
{
var transform = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).position.z : ToValue.x;
var startValue = UseFromValue ? FromValue.x : transform.position.z;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
transform.SetPositionZ(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = transform.DOMoveZ(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOLocalMove:
{
var transform = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localPosition : (Vector3)ToValue;
var startValue = UseFromValue ? (Vector3)FromValue : transform.localPosition;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
transform.localPosition = startValue;
if (SpeedBased)
duration = Vector3.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = transform.DOLocalMove(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOLocalMoveX:
{
var transform = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localPosition.x : ToValue.x;
var startValue = UseFromValue ? FromValue.x : transform.localPosition.x;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
transform.SetLocalPositionX(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = transform.DOLocalMoveX(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOLocalMoveY:
{
var transform = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localPosition.y : ToValue.x;
var startValue = UseFromValue ? FromValue.x : transform.localPosition.y;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
transform.SetLocalPositionY(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = transform.DOLocalMoveY(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOLocalMoveZ:
{
var transform = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localPosition.z : ToValue.x;
var startValue = UseFromValue ? FromValue.x : transform.localPosition.z;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
transform.SetLocalPositionZ(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = transform.DOLocalMoveZ(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOScale:
{
var com = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localScale : (Vector3)ToValue;
var startValue = UseFromValue ? (Vector3)FromValue : com.localScale;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.localScale = startValue;
if (SpeedBased) duration = Vector3.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = com.DOScale(targetValue, duration);
}
break;
case DOTweenType.DOScaleX:
{
var com = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localScale.x : ToValue.x;
var startValue = UseFromValue ? FromValue.x : com.localScale.x;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.SetLocalScaleX(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = com.DOScaleX(targetValue, duration);
}
break;
case DOTweenType.DOScaleY:
{
var com = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localScale.y : ToValue.x;
var startValue = UseFromValue ? FromValue.x : com.localScale.y;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.SetLocalScaleY(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = com.DOScaleY(targetValue, duration);
}
break;
case DOTweenType.DOScaleZ:
{
var com = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localScale.z : ToValue.x;
var startValue = UseFromValue ? FromValue.x : com.localScale.z;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.SetLocalScaleZ(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = com.DOScaleZ(targetValue, duration);
}
break;
case DOTweenType.DORotate:
{
var com = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).eulerAngles : (Vector3)ToValue;
var startValue = UseFromValue ? (Vector3)FromValue : com.eulerAngles;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.eulerAngles = startValue;
if (SpeedBased)
duration = GetEulerAnglesAngle(targetValue, startValue) / this.DurationOrSpeed;
result = com.DORotate(targetValue, duration, RotateMode.FastBeyond360);
}
break;
case DOTweenType.DOLocalRotate:
{
var com = Target as Transform;
var targetValue = UseToTarget ? (ToTarget as Transform).localEulerAngles : (Vector3)ToValue;
var startValue = UseFromValue ? (Vector3)FromValue : com.localEulerAngles;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.localEulerAngles = startValue;
if (SpeedBased)
duration = GetEulerAnglesAngle(targetValue, startValue) / this.DurationOrSpeed;
result = com.DOLocalRotate(targetValue, duration, RotateMode.FastBeyond360);
}
break;
case DOTweenType.DOAnchorPos:
{
var rectTransform = Target as RectTransform;
var targetValue = UseToTarget ? (ToTarget as RectTransform).anchoredPosition : (Vector2)ToValue;
var startValue = UseFromValue ? (Vector2)FromValue : rectTransform.anchoredPosition;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
rectTransform.anchoredPosition = startValue;
if (SpeedBased)
duration = Vector2.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = rectTransform.DOAnchorPos(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOAnchorPosX:
{
var rectTransform = Target as RectTransform;
var targetValue = UseToTarget ? (ToTarget as RectTransform).anchoredPosition.x : ToValue.x;
var startValue = UseFromValue ? FromValue.x : rectTransform.anchoredPosition.x;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
rectTransform.SetAnchoredPositionX(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = rectTransform.DOAnchorPosX(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOAnchorPosY:
{
var rectTransform = Target as RectTransform;
var targetValue = UseToTarget ? (ToTarget as RectTransform).anchoredPosition.y : ToValue.x;
var startValue = UseFromValue ? FromValue.x : rectTransform.anchoredPosition.y;
if (reverse)
{
var swapValue = startValue;
startValue = targetValue;
targetValue = swapValue;
}
rectTransform.SetAnchoredPositionY(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = rectTransform.DOAnchorPosY(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOAnchorPosZ:
{
var rectTransform = Target as RectTransform;
var targetValue = UseToTarget ? (ToTarget as RectTransform).anchoredPosition3D.z : ToValue.x;
var startValue = UseFromValue ? FromValue.x : rectTransform.anchoredPosition3D.z;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
rectTransform.SetAnchoredPosition3DZ(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = rectTransform.DOAnchorPos3DZ(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOAnchorPos3D:
{
var rectTransform = Target as RectTransform;
var targetValue = UseToTarget ? (ToTarget as RectTransform).anchoredPosition3D : (Vector3)ToValue;
var startValue = UseFromValue ? (Vector3)FromValue : rectTransform.anchoredPosition3D;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
rectTransform.anchoredPosition3D = startValue;
if (SpeedBased)
duration = Vector3.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = rectTransform.DOAnchorPos3D(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOSizeDelta:
{
var rectTransform = Target as RectTransform;
var targetValue = UseToTarget ? (ToTarget as RectTransform).sizeDelta : (Vector2)ToValue;
var startValue = UseFromValue ? (Vector2)FromValue : rectTransform.sizeDelta;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
rectTransform.sizeDelta = startValue;
if (SpeedBased)
duration = Vector2.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = rectTransform.DOSizeDelta(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOColor:
{
var com = Target as UnityEngine.UI.Graphic;
var targetValue = UseToTarget ? (ToTarget as UnityEngine.UI.Graphic).color : (Color)ToValue;
var startValue = UseFromValue ? (Color)FromValue : com.color;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.color = startValue;
if (SpeedBased)
duration = Vector4.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = com.DOColor(targetValue, duration);
}
break;
case DOTweenType.DOFade:
{
var com = Target as UnityEngine.UI.Graphic;
var targetValue = UseToTarget ? (ToTarget as UnityEngine.UI.Graphic).color.a : ToValue.x;
var startValue = UseFromValue ? FromValue.x : com.color.a;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.SetColorAlpha(startValue);
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = com.DOFade(targetValue, duration);
}
break;
case DOTweenType.DOCanvasGroupFade:
{
var com = Target as UnityEngine.CanvasGroup;
var targetValue = UseToTarget ? (ToTarget as UnityEngine.CanvasGroup).alpha : ToValue.x;
var startValue = UseFromValue ? FromValue.x : com.alpha;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.alpha = startValue;
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = com.DOFade(targetValue, duration);
}
break;
case DOTweenType.DOValue:
{
var com = Target as UnityEngine.UI.Slider;
var targetValue = UseToTarget ? (ToTarget as UnityEngine.UI.Slider).value : ToValue.x;
var startValue = UseFromValue ? FromValue.x : com.value;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.value = startValue;
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = com.DOValue(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOFillAmount:
{
var com = Target as UnityEngine.UI.Image;
var targetValue = UseToTarget ? (ToTarget as UnityEngine.UI.Image).fillAmount : ToValue.x;
var startValue = UseFromValue ? FromValue.x : com.fillAmount;
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.fillAmount = startValue;
if (SpeedBased)
duration = Mathf.Abs(targetValue - startValue) / this.DurationOrSpeed;
result = com.DOFillAmount(targetValue, duration);
}
break;
case DOTweenType.DOFlexibleSize:
{
var com = Target as LayoutElement;
var targetValue = UseToTarget ? (ToTarget as LayoutElement).GetFlexibleSize() : (Vector2)ToValue;
var startValue = UseFromValue ? (Vector2)FromValue : com.GetFlexibleSize();
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.SetFlexibleSize(startValue);
if (SpeedBased)
duration = Vector2.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = com.DOFlexibleSize(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOMinSize:
{
var com = Target as LayoutElement;
var targetValue = UseToTarget ? (ToTarget as LayoutElement).GetMinSize() : (Vector2)ToValue;
var startValue = UseFromValue ? (Vector2)FromValue : com.GetMinSize();
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.SetMinSize(startValue);
if (SpeedBased)
duration = Vector2.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = com.DOMinSize(targetValue, duration, Snapping);
}
break;
case DOTweenType.DOPreferredSize:
{
var com = Target as LayoutElement;
var targetValue = UseToTarget ? (ToTarget as LayoutElement).GetPreferredSize() : (Vector2)ToValue;
var startValue = UseFromValue ? (Vector2)FromValue : com.GetPreferredSize();
if (reverse)
{
(targetValue, startValue) = (startValue, targetValue);
}
com.SetPreferredSize(startValue);
if (SpeedBased)
duration = Vector2.Distance(targetValue, startValue) / this.DurationOrSpeed;
result = com.DOPreferredSize(targetValue, duration, Snapping);
}
break;
}
if (result != null)
{
result.SetAutoKill(true).SetTarget(Target.gameObject).SetLoops(Loops, LoopType).SetUpdate(UpdateType);
if (Delay > 0) result.SetDelay(Delay);
if (CustomEase) result.SetEase(EaseCurve);
else result.SetEase(Ease);
if (OnPlay != null) result.OnPlay(OnPlay.Invoke);
if (OnUpdate != null) result.OnUpdate(OnUpdate.Invoke);
if (OnComplete != null) result.OnComplete(OnComplete.Invoke);
}
return result;
}
public static float GetEulerAnglesAngle(Vector3 euler1, Vector3 euler2)
{
// 计算差值
Vector3 delta = euler2 - euler1;
delta.x = Mathf.DeltaAngle(euler1.x, euler2.x);
delta.y = Mathf.DeltaAngle(euler1.y, euler2.y);
delta.z = Mathf.DeltaAngle(euler1.z, euler2.z);
float angle = Mathf.Sqrt(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z);
return (angle + 360) % 360;
}
}
public enum AddType
{
Append,
Join
}
}