Building snappy UI and reactive gameplay feedback in Unity usually comes down to a key architectural choice: reliance on Mecanim or writing programmatic code. Using Unity’s built-in Animator everywhere gets expensive fast. Animator components continuously evaluate state machines, trigger hidden memory allocations, and introduce rigid clip overhead. DOTween provides a lean, C#-driven alternative that cuts out heavy animation controllers while keeping framerates high. In this unity dotween animation tutorial, we will walk through writing clean code-based animations, controlling memory overhead, and building scalable UI workflows for mobile and desktop titles.
At its core, DOTween mutates component properties over time using cached interpolation routines. Instead of cluttering an Update() loop with manual Mathf.Lerp or Vector3.SmoothDamp calls, DOTween handles changes via pre-allocated data structures that directly update native and managed references. This keeps Garbage Collector allocations to a minimum, even when animating dozens of objects simultaneously.
To start scripting, add using DG.Tweening; to your class. DOTween hooks directly into standard Unity types—like Transform, CanvasGroup, Material, and SpriteRenderer—via C# extension methods.
DOMove / DOAnchorPos: Tweens world space positions or UI RectTransform anchored coordinates over a target duration.DORotate / DOLocalRotate: Rotates objects via Euler angles, including full support for multi-turn rotations past 360 degrees.DOScale: Scales object transforms across X, Y, and Z axes for dynamic button bounces and feedback triggers.DOFade: Animates alpha values on UI Graphic elements, CanvasGroup components, or SpriteRenderers.Here is a direct implementation showing how to animate a transform on interaction using standard extension syntax and callback functions:
using UnityEngine;
using DG.Tweening;
public class TargetInteractable : MonoBehaviour
{
[SerializeField] private Vector3 targetOffset = new Vector3(0f, 2f, 0f);
[SerializeField] private float duration = 0.75f;
[SerializeField] private Ease easeType = Ease.OutBack;
private Vector3 initialPosition;
private void Awake()
{
initialPosition = transform.position;
}
public void TriggerAnimation()
{
// Animate position with an OutBack ease curve
transform.DOMove(initialPosition + targetOffset, duration)
.SetEase(easeType)
.OnComplete(ResetPosition);
}
private void ResetPosition()
{
transform.DOMove(initialPosition, duration * 0.5f)
.SetEase(Ease.InQuad);
}
}
Firing off independent, isolated tweens for complex UI panels gets messy fast. Timings drift, states overlap, and debugging becomes a chore. DOTween solves this using Sequence objects, which group multiple tweens to execute sequentially or concurrently with precise timing delays.
Sequences give you total control over complex UI popups, health bar impacts, and screen transitions inside a single readable C# script—no custom coroutines required.
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
public class ModalWindowController : MonoBehaviour
{
[SerializeField] private RectTransform modalPanel;
[SerializeField] private CanvasGroup backdropCanvasGroup;
[SerializeField] private Button actionButton;
private Sequence windowSequence;
private void Awake()
{
// Pre-configure initial UI state
modalPanel.localScale = Vector3.zero;
backdropCanvasGroup.alpha = 0f;
}
public void OpenWindow()
{
// Kill active sequences to prevent overlaps
if (windowSequence != null && windowSequence.IsActive())
{
windowSequence.Kill();
}
// Initialize sequence container
windowSequence = DOTween.Sequence();
windowSequence.Append(backdropCanvasGroup.DOFade(1f, 0.3f))
.Append(modalPanel.DOScale(Vector3.one, 0.4f).SetEase(Ease.OutBack))
.Join(actionButton.transform.DOScale(Vector3.one, 0.3f).SetEase(Ease.OutSine))
.SetUpdate(true); // Ignore Time.timeScale when game is paused
}
public void CloseWindow()
{
if (windowSequence != null && windowSequence.IsActive())
{
windowSequence.Kill();
}
windowSequence = DOTween.Sequence();
windowSequence.Append(modalPanel.DOScale(Vector3.zero, 0.25f).SetEase(Ease.InBack))
.Join(backdropCanvasGroup.DOFade(0f, 0.2f))
.OnComplete(() => gameObject.SetActive(false));
}
}
While code-based animations eliminate Animator frame overhead, bad lifecycle management will still hurt performance. Destroying objects while active tweens are running leads to memory leaks, missing reference exceptions, or random GC spikes. Following a few key principles ensures solid frame rates on both mobile hardware and desktop rigs.
DOTween.SetTweensCapacity(maxTweens, maxSequences) during game startup to prevent DOTween from resizing internal arrays during intense gameplay scenes.OnDisable() using DOTween.Kill(transform), or disable auto-killing via SetAutoKill(false) for manual re-use.OnComplete generates garbage if called repeatedly. For high-frequency tweens, cache your delegate references to stop heap allocations completely.SetUpdate(UpdateType.Fixed) for physics-based objects, and use SetUpdate(UpdateType.UnscaledTime) for UI screens that must animate while game time is paused.Here is how to properly manage tween lifecycles on a pooled entity script:
using UnityEngine;
using DG.Tweening;
public class PooledProjectile : MonoBehaviour
{
[SerializeField] private float travelDistance = 25f;
[SerializeField] private float speed = 15f;
private Tween moveTween;
public void Fire(Vector3 direction)
{
float duration = travelDistance / speed;
Vector3 targetPos = transform.position + (direction * travelDistance);
// Reuse or assign move tween
moveTween = transform.DOMove(targetPos, duration)
.SetEase(Ease.Linear)
.OnComplete(DeactivateSelf);
}
private void DeactivateSelf()
{
gameObject.SetActive(false);
}
private void OnDisable()
{
// Safely kill running tweens when returned to object pool
if (moveTween != null && moveTween.IsActive())
{
moveTween.Kill();
}
}
}
All Unity assets and tools featured in this guide are available for developer evaluation and testing to members of Ultimate Game Assets. Join our developer membership to access our complete asset sandbox and speed up your production workflow.