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:
- ButtonAnimation:
- This holds a list of
AnimationAction
(Move, Rotate, Scale) andButtonAction
(specific button events tied to animation). invokeTime
: Defines the time at which the button’sonClick
event is triggered at the end of the animation.
- ButtonAction:
- Contains the button reference (
Button
), theTransform
that will be animated, and a list ofInvokeTimePoint
events. - Each
ButtonAction
can have multiple time points (InvokeTimePoint
) where different events are triggered, such as enabling/disabling GameObjects or playing sounds.
- 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.
- 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:
- Registration:
- When the scene starts, the system registers each button along with its specific
ButtonAction
and animation.
- Button Click:
- When a button is clicked, it starts an animation sequence based on the actions defined in its
ButtonAnimation
.
- Animation:
- The button’s transform animates according to the list of actions (e.g., moving, rotating, or scaling).
- Event Triggers:
- Events defined in the
InvokeTimePoint
list are triggered at specified times during the animation. For instance, at0.3s
, a sound could play, at0.6s
a visual effect could be enabled, and at1.0s
, the button’s normalonClick
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:
- Improvements/Optimizations: Are there any optimizations I can make to improve performance, especially when handling multiple buttons at once or when triggering many events?
- 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).
- 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?
- 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!