[WIP] Animation & Button Interaction System for UI Buttons

using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
using System;
using UnityEngine.Events;
using UnityEngine.Serialization;
using System.Collections;
using System.Collections.Generic;

[Serializable]
public class AnimationAction
{
    public AnimationType type;       // Move, Rotate, or Scale
    public Vector3[] path;           // The path for the animation
    public float duration;           // Duration for this animation
    public Ease ease;                // Ease type for this animation
}

public enum AnimationType
{
    Move,
    Rotate,
    Scale
}

[Serializable]
public class ButtonAction
{
    public Transform tweenTransform;
    public Button button;                  // The button reference
    [FormerlySerializedAs("onClick")]
    public ButtonClickedEvent onClick = new ButtonClickedEvent();
    // List of individual time points to trigger onClick or other events
    public List<InvokeTimePoint> invokeTimePoints = new List<InvokeTimePoint>();
}

[Serializable]
public class ButtonClickedEvent : UnityEvent { }
[Serializable]
public struct InvokeTimePoint
{
    public float time; // Time during animation sequence to trigger the event
    public UnityEvent onEvent; // The specific event to trigger (can be any UnityEvent)
}

[Serializable]
public struct ButtonAnimation
{
    public float invokeTime;           // Duration to invoke listeners
    public List<AnimationAction> actions; // List of animation actions in order
    public List<ButtonAction> buttonActions; // List of buttons and their events
}
public class ButtonPressedController : MonoBehaviour
{
    #region Fields
    [SerializeField]
    private List<ButtonAnimation> animations = new List<ButtonAnimation>();

    private Dictionary<Button, ButtonAnimation> buttonAnimations = new Dictionary<Button, ButtonAnimation>();
    private Dictionary<Button, ButtonAction> buttonActions = new Dictionary<Button, ButtonAction>();
    #endregion

    #region Unity Methods

    private void Start()
    {
        foreach (var animation in animations)
        {
            foreach (var buttonAction in animation.buttonActions)
            {
                RegisterButton(buttonAction.button, animation, buttonAction);
            }
        }
    }

    #endregion

    #region Methods
    private void RegisterButton(Button button, ButtonAnimation animation, ButtonAction buttonAction)
    {
        if (!buttonAnimations.ContainsKey(button))
        {
            buttonAnimations.Add(button, animation);
        }
        if (!buttonActions.ContainsKey(button))
        {
            buttonActions.Add(button, buttonAction);
        }
        button.onClick.AddListener(() => PlayButtonAnimation(button));
    }

    private void PlayButtonAnimation(Button button)
    {
        if (!buttonAnimations.TryGetValue(button, out var animation)) return;
        StartCoroutine(AnimateButton(button, animation));
    }
    private IEnumerator AnimateButton(Button button, ButtonAnimation animation)
    {
        if (buttonActions.TryGetValue(button, out var buttonAction))
        {
            Sequence seq = DOTween.Sequence().Pause().SetUpdate(true);

            foreach (var action in animation.actions)
            {
                switch (action.type)
                {
                    case AnimationType.Move:
                        if(action.path.Length>1)
                            seq.Append(buttonAction.tweenTransform.DOLocalPath(action.path, action.duration).SetEase(action.ease));
                        else
                            seq.Append(buttonAction.tweenTransform.DOLocalMove(action.path[0], action.duration).SetEase(action.ease));
                        ////seq.Append(button.transform.DOPath(action.path, action.duration).SetEase(action.ease));
                        break;

                    case AnimationType.Rotate:
                        foreach (var rotation in action.path)
                        {
                            seq.Append(buttonAction.tweenTransform.DORotate(rotation, action.duration).SetEase(action.ease));
                            //seq.Append(button.transform.DORotate(rotation, action.duration).SetEase(action.ease));
                        }
                        break;

                    case AnimationType.Scale:
                        foreach (var scale in action.path)
                        {
                            seq.Append(buttonAction.tweenTransform.DOScale(scale, action.duration).SetEase(action.ease));
                            //seq.Append(button.transform.DOScale(scale, action.duration).SetEase(action.ease));
                        }
                        break;
                }
            }

            // Insert callbacks to invoke events at the specified times
            foreach (var timePoint in buttonAction.invokeTimePoints)
            {
                seq.InsertCallback(timePoint.time, () =>
                {
                    timePoint.onEvent?.Invoke();  // Trigger the specific event for this button at the given time
                });
            }

            // Insert the final callback for the button's main onClick event at the end of the animation
            seq.InsertCallback(animation.invokeTime, () =>
            {
                buttonAction.onClick?.Invoke();
            });

            ////// Insert a callback to invoke the onClick event of the button's ButtonAction
            ////if (buttonAnimations.TryGetValue(button, out var buttonAnimation))
            ////{
            ////    seq.InsertCallback(buttonAnimation.invokeTime, () =>
            ////    {
            ////        buttonAction.onClick?.Invoke();
            ////    });
            ////}

            seq.Restart();
            yield return seq.WaitForCompletion();
        }
        else
        {
            Debug.LogWarning($"No ButtonAction found for {button.name}.");
            yield break;
        }
           
        
    }
    #endregion
}

Hi everyone!

I’ve been working on a system that allows for complex animations to be triggered by UI buttons in Unity, and I’d love to get some feedback or suggestions for improvements. The system lets you animate buttons (Move, Rotate, Scale) and trigger events at specific times during the animation sequence, including the button’s normal onClick event or other custom events.

System Overview

The goal of this system is to enable more dynamic and interactive button behaviors. You can animate buttons with different actions (movement, rotation, scaling) and trigger events at different time points during these animations, including button-specific events (like enabling/disabling GameObjects) or triggering sound effects, etc. All of this happens while the button is being animated.

Here’s a quick breakdown of how the system works:

Key Components:

  1. ButtonAnimation:
  • This holds a list of AnimationAction (Move, Rotate, Scale) and ButtonAction (specific button events tied to animation).
  • invokeTime: Defines the time at which the button’s onClick event is triggered at the end of the animation.
  1. ButtonAction:
  • Contains the button reference (Button), the Transform that will be animated, and a list of InvokeTimePoint events.
  • Each ButtonAction can have multiple time points (InvokeTimePoint) where different events are triggered, such as enabling/disabling GameObjects or playing sounds.
  1. InvokeTimePoint:
  • Defines the time in the animation sequence at which to trigger a custom UnityEvent.
  • This allows for granular control over what happens during the animation sequence for each button.
  1. AnimationAction:
  • Represents a single animation action (Move, Rotate, Scale) that can be applied to a button’s transform.
  • This structure holds the path (target positions for Move, Rotation angles for Rotate, etc.), the duration, and the easing type for the animation.

How It Works:

  1. Registration:
  • When the scene starts, the system registers each button along with its specific ButtonAction and animation.
  1. Button Click:
  • When a button is clicked, it starts an animation sequence based on the actions defined in its ButtonAnimation.
  1. Animation:
  • The button’s transform animates according to the list of actions (e.g., moving, rotating, or scaling).
  1. Event Triggers:
  • Events defined in the InvokeTimePoint list are triggered at specified times during the animation. For instance, at 0.3s, a sound could play, at 0.6s a visual effect could be enabled, and at 1.0s, the button’s normal onClick event is invoked.

Why This System?

  • Customization: This system gives you complete control over button behavior. Each button can have its own unique sequence of animations and events that are triggered at specific points in the animation, allowing for highly interactive UIs.
  • Flexibility: You can attach multiple actions (Move, Rotate, Scale) to the same button, as well as trigger any number of events during the animation, including the standard onClick.
  • Performance: By using DOTween sequences, the animations are highly performant and easy to manage.

Request for Feedback:

This is a work-in-progress, and I’m looking for feedback on the following:

  1. Improvements/Optimizations: Are there any optimizations I can make to improve performance, especially when handling multiple buttons at once or when triggering many events?
  2. Extending Functionality: What other features or types of animations/events could be added to this system? I’m thinking of adding more animation types (e.g., Skew, Color changes, etc.), or potentially allowing more complex event triggers (e.g., combining multiple events into one).
  3. Best Practices: Are there best practices I might have missed, especially when it comes to handling UI animations and events in Unity? Are there any tools, plugins, or techniques I could leverage to improve the system?
  4. Bug Reports: If you’ve tried out the system, did you encounter any issues or unexpected behaviors?

Example Use Case:

Here’s a small example of how to set up a button animation with events:

public List<ButtonAnimation> animations = new List<ButtonAnimation>
{
    new ButtonAnimation
    {
        invokeTime = 2.0f, // Total duration for animation
        actions = new List<AnimationAction>
        {
            new AnimationAction { type = AnimationType.Move, path = new Vector3[] { new Vector3(0, 1, 0) }, duration = 1.0f, ease = Ease.OutQuad },
            new AnimationAction { type = AnimationType.Rotate, path = new Vector3[] { new Vector3(0, 90, 0) }, duration = 1.0f, ease = Ease.OutQuad }
        },
        buttonActions = new List<ButtonAction>
        {
            new ButtonAction
            {
                button = myButton,
                tweenTransform = myButton.transform,
                invokeTimePoints = new List<InvokeTimePoint>
                {
                    new InvokeTimePoint { time = 0.3f, onEvent = mySoundEffectButton.onClick }, // Play sound at 0.3s
                    new InvokeTimePoint { time = 0.7f, onEvent = myVisualEffectButton.onClick }  // Enable visual effect at 0.7s
                }
            }
        }
    }
};

This will move the button upwards, rotate it 90 degrees, and trigger sound and visual effects at the specified times.

Conclusion:

I’m excited to hear your thoughts, suggestions, and feedback! I’d also love to see if anyone can come up with optimizations or additional features that could make this system even more powerful.

Thanks in advance for your help!

Hello,
Thanks for this info .
myAARPMedicare Health
Best Regards
merry867

1 Like