Creating of wave / ripple effect for buttons like in Material Design in Unity
Hey, everybody. In today's short tutorial I'd like to show you how to work with the built-in Unity UI (UGUI) event system on the example of creating a wave effect when you click on an element (whether it's a button or Image doesn't matter), like in Material Design
So, let's get started!
Let's make an universal component based on MonoBehaviour and IPointerClickHandler
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
// We need to Disallow Multiple Component for Performance Issue
[DisallowMultipleComponent]
public class UIRippleEffect : MonoBehaviour, IPointerClickHandler
{
[Header("Ripple Setup")]
public Sprite m_EffectSprite; // Our Ripple Sprite
public Color RippleColor; // Ripple Color
public float MaxPower = .25f; // Max Opacity of Ripple (from 0 to 1)
public float Duration = .25f; // Duration of Ripple effect (in sec)
// Our Internal Parameters
private bool m_IsInitialized = false; // Initialization Flag
private RectMask2D m_RectMask; // Rect Mask for Ripple
// Here we Check our Effect Sprite and Setup Container
private void Awake() {
if (m_EffectSprite == null) {
Debug.LogWarning("Failed to add ripple graphics. Not Ripple found.");
return;
}
SetupRippleContainer();
}
// Here we add our mask for ripple effect
private void SetupRippleContainer() {
m_RectMask = gameObject.AddComponent<RectMask2D>();
m_RectMask.padding = new Vector4(5, 5, 5, 5);
m_RectMask.softness = new Vector2Int(20, 20);
m_IsInitialized = true;
}
// This is our Click event based on IPointerClickHandler for Unity Event System
public void OnPointerClick(PointerEventData pointerEventData) {
if(!m_IsInitialized) return;
GameObject rippleObject = new GameObject("_ripple_");
LayoutElement crl = rippleObject.AddComponent<LayoutElement>();
crl.ignoreLayout = true;
Image currentRippleImage = rippleObject.AddComponent<Image>();
currentRippleImage.sprite = m_EffectSprite;
currentRippleImage.transform.SetAsLastSibling();
currentRippleImage.transform.SetPositionAndRotation(pointerEventData.position, Quaternion.identity);
currentRippleImage.transform.SetParent(transform);
currentRippleImage.color = new Color(RippleColor.r, RippleColor.g, RippleColor.b, 0f);
currentRippleImage.raycastTarget = false;
StartCoroutine(AnimateRipple(rippleObject.GetComponent<RectTransform>(), currentRippleImage, () => {
currentRippleImage = null;
Destroy(rippleObject);
StopCoroutine(nameof(AnimateRipple));
}));
}
// Here we work with animation of single ripple
private IEnumerator AnimateRipple(RectTransform rippleTransform, Image rippleImage, Action onComplete) {
Vector2 initialSize = Vector2.zero;
Vector2 targetSize = new Vector2(150,150);
Color initialColor = new Color(RippleColor.r, RippleColor.g, RippleColor.b, MaxPower);
Color targetColor = new Color(RippleColor.r, RippleColor.g, RippleColor.b, 0f);
float elapsedTime = 0f;
while (elapsedTime < Duration)
{
elapsedTime += Time.deltaTime;
rippleTransform.sizeDelta = Vector2.Lerp(initialSize, targetSize, elapsedTime / Duration);
rippleImage.color = Color.Lerp(initialColor, targetColor, elapsedTime / Duration);
yield return null;
}
onComplete?.Invoke();
}
}
So, using standard Unity interfaces, we created a wave effect inside the mask created on our element (this can also be replaced with a shader-based effect for better performance) when clicked. It doesn't matter what type of element our UI will be - the main thing is that we can catch it with Raycast.
Do not forgot to setup your new component at UI:
You can practice more by adding new effects using hover/unhover and other UIs for that. Use the IPointerEnterHandler, IPointerExitHandler interfaces to do this.
Thanks for reading the article, I'll always be happy to discuss any projects with you and help you with your ideas on Unity: