Why Your Unity Animations Feel Stiff (And How a Custom Unity Tweening System Fixed Mine)
Learn how to build a custom Unity tweening system from scratch using coroutines and lerp. Create smooth animations, juicy UI effects, and professional game feel without third-party libraries.
Here's the thing—I spent a good afternoon once trying to make a simple button scale up on hover. I opened Unity's animation window, created a clip, set keyframes, hooked it up to an Animator Controller... and then realized I needed to do this for 20 different buttons. That's when I learned about tweening, and honestly, it changed everything about how I approach game feel and UI polish.
Let me show you how to build a custom Unity tweening system from scratch that'll make your animations smooth, your UI juicy, and your life way easier.
What the Hell Is Tweening Anyway?
Alright, let's cut through the jargon. Tweening (short for "in-betweening") is the process of generating intermediate values between a start and end value over a specific time period. Think of it like a dimmer switch for a light bulb—instead of instantly flipping the light on or off, the switch smoothly interpolates the brightness from 0% to 100% over a few seconds. That smooth transition? That's tweening.
In Unity game feel programming, tweening solves a massive problem: creating smooth, procedural animations directly in code without opening Unity's animation system. You know how tedious it is to create animation clips for every little movement? Tweening lets you do it in one line of code.
Here's what a Unity tweening system lets you create:
- Dynamic, responsive UI that scales, fades, and slides smoothly
- Satisfying gameplay feedback like hit flashes and screen shakes
- Polished object movements that respond to game data
- Juicy visual effects that make your game feel professional
The beauty? It's all code-based, which means you can trigger animations dynamically, respond to player input instantly, and iterate on timing in seconds instead of minutes.
The Core Building Blocks You Need to Know
Before we dive into code, let's nail down the fundamental concepts that make tweening work. Understanding these will make everything else click into place.
Tween: The Basic Animation Operation
A tween is a single animation operation that modifies a property of an object—position, rotation, scale, color—from a starting value to an ending value over a set duration. It's the atomic unit of procedural animation Unity.
Easing Function: The Secret to Natural Motion
An easing function is a mathematical formula that dictates the rate of change over time. Instead of moving at a constant speed (which looks robotic), easing functions create acceleration and deceleration. This is what makes movement feel natural instead of mechanical.
Interpolation: The Math Behind Smooth Transitions
Interpolation is the mathematical process of estimating values between two known values. In Unity, we use functions like Vector3.Lerp to calculate where an object should be at any point during its journey from start to end.
Coroutine: Your Animation Timer
A coroutine is a special Unity function that can pause execution and resume later. It's perfect for managing tween duration because it can run over multiple frames, updating the animated property each frame until the duration expires.
Callback: Chaining and Triggering Logic
A callback is a function you provide to the tweening system to execute when the tween completes. This is huge for chaining animations or triggering game logic after a movement finishes (like showing a popup after a panel slides in).
How Coroutines Become Your Animation Engine
Here's where Unity coroutine animation really shines. A coroutine acts as a timer that runs for the specified duration, updating your animated property every frame. Let me show you the basic structure:
// This basic structure forms the timer for any tween.
IEnumerator TweenCoroutine(float duration)
{
float elapsedTime = 0f;
while (elapsedTime < duration)
{
// We'll do our interpolation work here.
elapsedTime += Time.deltaTime;
yield return null; // Wait for the next frame.
}
// Tween is complete.
}
See what's happening? The coroutine loops each frame, accumulating Time.deltaTime until the total elapsed time exceeds the duration. The yield return null is the magic—it pauses execution and resumes on the next frame. This gives you frame-perfect control over your animation.
The Magic of Lerp: Interpolation Explained
Unity's built-in Lerp functions are the workhorses of lerp animation Unity. They take a start value, an end value, and a progress value (from 0 to 1), then return the interpolated value.
// 't' is the progress of our tween, from 0.0 at the start to 1.0 at the end.
float t = elapsedTime / duration;
// Lerp calculates the position at the current point in time.
transform.position = Vector3.Lerp(startPosition, endPosition, t);
The progress value t is the key. When t = 0, you get startPosition. When t = 1, you get endPosition. When t = 0.5, you get the midpoint. Lerp handles all the math for you—it works with floats, Vector3s, Colors, and more.
Easing Functions: Making Movement Feel Natural
This is where custom animation code Unity gets interesting. To make movement feel natural, we pass our linear progress t through an easing function. This modifies the progress to create acceleration and deceleration effects.
// A simple Ease-Out Quad function makes the tween start fast and slow down at the end.
float EaseOutQuad(float t)
{
return 1 - (1 - t) * (1 - t);
}
// We apply the easing function to our progress value before passing it to Lerp.
float easedT = EaseOutQuad(elapsedTime / duration);
transform.position = Vector3.Lerp(startPosition, endPosition, easedT);
That EaseOutQuad function takes linear time and makes it curve. At the start, progress happens quickly. Near the end, it slows down dramatically. This creates that satisfying "settle into place" feeling that makes animations look polished.
Different easing functions Unity create different feels:
EaseInQuad: Starts slow, ends fast (good for objects accelerating away)EaseInOutQuad: Smooth acceleration and deceleration (natural, organic motion)EaseOutBack: Overshoots and settles (bouncy, playful feel)
Verified: Easing Functions Cheat Sheet
Building Your TweenManager (The Control Center)
A central tween manager C# class handles starting coroutines so you don't have to call StartCoroutine from every script. This keeps your code clean and centralized.
// A simple static manager class to handle starting our tweens.
public static class TweenManager
{
// We need a reference to a MonoBehaviour to start coroutines.
private static MonoBehaviour coroutineRunner;
public static void Move(Transform target, Vector3 endPosition, float duration)
{
if (coroutineRunner == null)
{
// Create a hidden GameObject to run our coroutines.
coroutineRunner = new GameObject("TweenRunner").AddComponent();
}
coroutineRunner.StartCoroutine(MoveCoroutine(target, endPosition, duration));
}
}
Verified: Unity Docs - Coroutines
This pattern is crucial. Since coroutines must be started from a MonoBehaviour, we create a persistent GameObject to act as our coroutine runner. This lets us call TweenManager.Move(...) from anywhere in our code without worrying about MonoBehaviour dependencies.
Custom vs Third-Party: When to Build vs Buy
Been there—choosing between building a custom system versus using a DOTween alternative Unity library. Let me break down when each makes sense:
| Criteria | Approach A: Custom Coroutine System | Approach B: Third-Party Library (e.g., DOTween, LeanTween) |
|---|---|---|
| Best For | Learning the fundamentals of procedural animation, or for very small projects where you only need a few simple tweens. | Production games of any scale, especially those with complex UI animations, chained sequences, and a need for high performance and stability. |
| Performance | Can be performant if managed carefully, but is prone to generating garbage due to coroutine overhead if not optimized. | Highly optimized, often avoiding garbage collection entirely, and designed to handle thousands of concurrent tweens with minimal overhead. |
| Complexity | Simple to start, but becomes very complex to add advanced features like sequencing, callbacks, and different ease types. | Extremely easy to use with a fluent, chainable API that makes creating complex animation sequences simple and readable. |
| Code Example | StartCoroutine(MoveCoroutine(...)); |
transform.DOMove(endPosition, duration).SetEase(Ease.OutQuad); |
Here's my rule of thumb: Build your own if you're learning or prototyping. For production games, use DOTween or LeanTween—they're battle-tested, garbage-free, and save you weeks of debugging edge cases.
Why This Makes Your Games Feel Better
From my time at KIXEYE and working on shipped titles, I've learned that mastering code-based animation through tweening unlocks a level of polish that's hard to achieve otherwise.
Juicy and Responsive UI
Tweening is the key to making Unity UI animation code feel alive. Buttons can scale up on hover, panels slide into view smoothly, health bars animate their fill amount—all providing critical feedback to the player.
Dynamic Gameplay Feedback
Use tweens to provide satisfying feedback for player actions. Make enemies flash red and scale down when hit. Make the camera shake and zoom when a powerful ability is used. This kind of responsive feedback is what separates good games from great ones.
Procedural and Data-Driven Animation
Tweens let you create animations that respond to in-game variables. Move a platform to a position determined by player score. Animate a progress bar based on real-time data. This is impossible with pre-made animation clips.
Faster Iteration and Development
Creating and tweaking a simple movement in code with a tween takes seconds. Compare that to opening the animation window, creating a clip, setting keyframes, managing an Animator Controller... tweening is just faster for simple movements.
Pro Tips for a Rock-Solid Tweening System
This one had me stuck for a bit when I first built a tweening system—managing conflicting tweens and avoiding garbage generation. Let me share what I learned.
Use a Struct for Tween Data
Instead of passing many parameters to your coroutine, encapsulate all the tween's data into a struct. This keeps your code clean and organized.
public struct Tween
{
public Transform Target;
public Vector3 EndValue;
public float Duration;
public Action OnComplete;
}
Manage and Overwrite Existing Tweens
If you start a new tween on an object that's already tweening, you should stop the old tween first. Otherwise, two coroutines will fight to control the object's properties, creating jittery movement. A dictionary can track active tweens:
// Use a dictionary to store the active coroutine for each object.
private static Dictionary activeTweens = new Dictionary();
public static void Move(Transform target, ...)
{
// If a tween is already running on this target, stop it first.
if (activeTweens.ContainsKey(target))
{
coroutineRunner.StopCoroutine(activeTweens[target]);
activeTweens.Remove(target);
}
// Start the new tween and store its coroutine reference.
Coroutine newTween = coroutineRunner.StartCoroutine(...);
activeTweens.Add(target, newTween);
}
Create a Static Easing Function Library
Keep all your easing functions in a single static class. This makes them accessible from anywhere and keeps your tweening logic separate from the math.
public static class Easing
{
public static float EaseOutQuad(float t) { /* ... */ }
public static float EaseInSine(float t) { /* ... */ }
// Add all other easing functions here.
}
Verified: Easing Functions Cheat Sheet
Games That Nailed Tweening (And What You Can Learn)
Let me show you how professional studios use tweening to create unforgettable game feel. I've studied these implementations extensively, and they're masterclasses in animation polish.
Hearthstone: Tactile Card Animations
I've seen this technique used brilliantly in Hearthstone. When a card is played, it smoothly flies from the player's hand to the center of the board, often rotating and scaling to fit its new position.
Here's how you can adapt this for your own game: This is a classic tweening sequence. A function is called that tweens the card's position, rotation, and scale simultaneously over about half a second. An EaseOut function is likely used to make the card settle into place gently.
What I find fascinating about this approach is that it makes playing cards feel tactile and impactful. The smooth, predictable motion makes the game board feel like a physical space. This provides clear visual feedback about what's happening—critical for a complex card game where players need to track multiple actions.
Celeste: Bouncy, Juicy UI
One of my favorite implementations of this is in Celeste. The entire user interface, from the main menu to dialogue boxes, is filled with bouncy, satisfying animations. Buttons scale up and down on selection, and text appears with a slight overshoot and bounce.
After analyzing dozens of games, this stands out because the UI system is built on a powerful tweening engine. Every UI element's appearance is controlled by tweens that modify its scale, position, and opacity. An EaseOutBack or EaseOutElastic function is likely used to create the signature bouncy feel.
From a developer's perspective, what makes this brilliant is how it creates an incredibly polished and "juicy" feel. Interacting with the game's menus becomes a joy. The constant motion and feedback make the UI feel alive and responsive, contributing significantly to the game's overall charm and quality.
Overwatch: HUD That Communicates Instantly
This is why I always recommend studying Overwatch's approach. When a player's ultimate ability is ready, the icon in the UI pulses and glows. When activated, the health and ammo meters animate and flash to provide feedback.
Let me tell you about how they solved this: The HUD is driven by tweens. A looping "Yoyo" tween is used to make the ultimate icon scale up and down continuously. Other tweens are triggered by game events to animate the health bar's color and fill amount when the player takes damage or is healed.
I always tell my students to look at how this provides critical, at-a-glance information in a way that's visually engaging but not distracting. The smooth animations draw the player's eye to important events, allowing them to react quickly in a fast-paced game. That's the power of well-implemented tweening.
Blueprint 1: Building the Core Tweening Engine
Alright, let's build a practical Unity tweening system from scratch. This is the exact method I use when setting up animation systems for new projects.
Scenario Goal
Create the foundational TweenManager script that can run a generic tweening coroutine for moving a Transform.
Unity Editor Setup
Let me show you how I approach this. First, set up your project:
- Create a new C# script named
TweenManager.cs - Create a new C# script named
Easing.csto hold easing functions - Create a Cube in your scene to act as the object we will move
- Create a test script named
TweenTester.csand attach it to the Cube
Step-by-Step Code Implementation
Step 1: Create the Easing Library
First, we'll create the static class that holds the mathematical formulas for our easing functions:
// Easing.cs
using UnityEngine;
public static class Easing
{
// An Ease-In-Out function provides smooth acceleration and deceleration.
public static float EaseInOutQuad(float t)
{
return t < 0.5f ? 2 * t * t : 1 - Mathf.Pow(-2 * t + 2, 2) / 2;
}
}
Step 2: Build the TweenManager
This static class will manage the creation of a coroutine runner and contain the main tweening coroutine logic:
// TweenManager.cs
using System;
using System.Collections;
using UnityEngine;
public static class TweenManager
{
private class CoroutineRunner : MonoBehaviour { }
private static CoroutineRunner runner;
// This ensures our coroutine runner exists in the scene.
private static void Init()
{
if (runner == null)
{
GameObject go = new GameObject("TweenRunner");
runner = go.AddComponent();
GameObject.DontDestroyOnLoad(go);
}
}
public static void MoveTo(Transform target, Vector3 endPosition, float duration, Action onComplete = null)
{
Init();
runner.StartCoroutine(MoveCoroutine(target, endPosition, duration, onComplete));
}
private static IEnumerator MoveCoroutine(Transform target, Vector3 endPosition, float duration, Action onComplete)
{
Vector3 startPosition = target.position;
float elapsedTime = 0f;
while (elapsedTime < duration)
{
// If the target is destroyed, stop the tween.
if (target == null) yield break;
float t = elapsedTime / duration;
// Apply the easing function.
float easedT = Easing.EaseInOutQuad(t);
target.position = Vector3.Lerp(startPosition, endPosition, easedT);
elapsedTime += Time.deltaTime;
yield return null;
}
// Ensure the target is exactly at the end position.
if (target != null)
{
target.position = endPosition;
}
// Execute the callback function if one was provided.
onComplete?.Invoke();
}
}
Step 3: Create a Test Script
This simple script will call our TweenManager to move the Cube when the game starts:
// TweenTester.cs
using UnityEngine;
public class TweenTester : MonoBehaviour
{
public Vector3 targetPosition = new Vector3(5, 2, 0);
public float duration = 2f;
void Start()
{
Debug.Log("Starting tween...");
TweenManager.MoveTo(transform, targetPosition, duration, OnTweenComplete);
}
// This method will be the callback when the tween finishes.
void OnTweenComplete()
{
Debug.Log("Tween has completed!");
}
}
Verified: Unity Docs - Vector3.Lerp
Let's tackle this together: Press Play, and you'll see the Cube smoothly move to the target position with a nice ease-in-out curve. When it arrives, the console logs "Tween has completed!" That's a complete, functional tweening system in under 100 lines of code.
Blueprint 2: Fading UI Elements Like a Pro
Now let's extend the system to handle Unity canvas group fade operations—one of the most common UI animation tasks.
Scenario Goal
Extend the tweening system to handle non-Transform properties, such as fading a UI panel by tweening its CanvasGroup alpha.
Unity Editor Setup
Here's the exact setup I use for UI fading:
- Create a UI Panel (
GameObject -> UI -> Panel) - Add a
CanvasGroupcomponent to the Panel - Create a script named
UIFader.csand attach it to the Panel
Step-by-Step Code Implementation
Step 1: Add a Generic Tween Coroutine
We'll add a new, more generic coroutine to our TweenManager that can tween any float value using an Action<float> delegate:
// TweenManager.cs (additions)
public static void TweenFloat(Action setter, float startValue, float endValue, float duration)
{
Init();
runner.StartCoroutine(FloatCoroutine(setter, startValue, endValue, duration));
}
private static IEnumerator FloatCoroutine(Action setter, float startValue, float endValue, float duration)
{
float elapsedTime = 0f;
while (elapsedTime < duration)
{
float t = elapsedTime / duration;
float easedT = Easing.EaseInOutQuad(t);
// Use the setter delegate to update the target property.
setter(Mathf.Lerp(startValue, endValue, easedT));
elapsedTime += Time.deltaTime;
yield return null;
}
// Ensure the final value is set.
setter(endValue);
}
Step 2: Create the UI Fader Script
This script will get the CanvasGroup component and use our new TweenFloat method to animate its alpha property:
// UIFader.cs
using UnityEngine;
[RequireComponent(typeof(CanvasGroup))]
public class UIFader : MonoBehaviour
{
private CanvasGroup canvasGroup;
void Awake()
{
canvasGroup = GetComponent();
}
void Update()
{
// Press F to fade out.
if (Input.GetKeyDown(KeyCode.F))
{
// We pass an anonymous function (lambda) as the setter.
// This function takes a float 'alpha' and sets the canvasGroup's alpha to it.
TweenManager.TweenFloat(alpha => canvasGroup.alpha = alpha, 1f, 0f, 1.5f);
}
// Press G to fade in.
if (Input.GetKeyDown(KeyCode.G))
{
TweenManager.TweenFloat(alpha => canvasGroup.alpha = alpha, 0f, 1f, 1.5f);
}
}
}
Verified: Unity Docs - CanvasGroup
See what's happening? The lambda expression alpha => canvasGroup.alpha = alpha is a setter function. Our TweenFloat method calls this setter every frame with the interpolated alpha value. This pattern works for any property—color, volume, fill amount, anything that's a float.
Blueprint 3: Creating That Satisfying Bouncy Scale Effect
Let's create a satisfying "pop-in" effect using an EaseOutBack easing function—the kind of animation that makes UI elements feel alive.
Scenario Goal
Create a satisfying "pop-in" effect for an object by tweening its scale with a bouncy EaseOutBack easing function.
Unity Editor Setup
I've configured this dozens of times for game objects that need impact:
- Create a 3D Sphere in your scene
- Attach a new script named
BouncyScaler.csto the Sphere
Step-by-Step Code Implementation
Step 1: Add an EaseOutBack Easing Function
We'll add a new easing function to our Easing.cs library that creates an "overshoot and settle" effect:
// Easing.cs (additions)
public static float EaseOutBack(float t)
{
const float c1 = 1.70158f;
const float c3 = c1 + 1;
return 1 + c3 * Mathf.Pow(t - 1, 3) + c1 * Mathf.Pow(t - 1, 2);
}
Step 2: Add a TweenScale Method to the Manager
We'll create a dedicated method in TweenManager for tweening the scale of a Transform, using our new easing function:
// TweenManager.cs (additions)
public static void ScaleTo(Transform target, Vector3 endScale, float duration)
{
Init();
runner.StartCoroutine(ScaleCoroutine(target, endScale, duration));
}
private static IEnumerator ScaleCoroutine(Transform target, Vector3 endScale, float duration)
{
Vector3 startScale = target.localScale;
float elapsedTime = 0f;
while (elapsedTime < duration)
{
if (target == null) yield break;
float t = elapsedTime / duration;
// Use our new bouncy easing function.
float easedT = Easing.EaseOutBack(t);
target.localScale = Vector3.LerpUnclamped(startScale, endScale, easedT);
elapsedTime += Time.deltaTime;
yield return null;
}
if (target != null)
{
target.localScale = endScale;
}
}
Note: We use Vector3.LerpUnclamped here because an EaseOutBack function intentionally overshoots the target value of 1.0, and Lerp would clamp it, ruining the effect.
Step 3: Create the Bouncy Scaler Script
This script will shrink the object to zero and then use our new ScaleTo method to animate it in with a bounce:
// BouncyScaler.cs
using UnityEngine;
public class BouncyScaler : MonoBehaviour
{
private Vector3 initialScale;
void Start()
{
initialScale = transform.localScale;
// Start the object at zero scale.
transform.localScale = Vector3.zero;
// Tween it to its initial scale with the bouncy effect.
TweenManager.ScaleTo(transform, initialScale, 1.0f);
}
}
Verified: Unity Docs - Vector3.LerpUnclamped
Trust me, you'll thank me later for this tip: The overshoot effect is what makes UI elements feel satisfying to interact with. That little bounce makes everything feel more playful and polished. It's a small detail that has a huge impact on perceived quality.
What You'll Gain from Mastering Tweening
After working on multiple Unity projects with tweening systems, I've learned that this skill gives you:
Complete Control Over Game Feel
You'll be able to create juicy, responsive feedback for every player action. That satisfying "pop" when an enemy dies? That's a scale tween with EaseOutBack. The smooth health bar animation? That's a float tween on the fill amount.
Rapid Prototyping and Iteration
Tweaking animation timing becomes instant. Change one float value and press Play. No opening animation windows, no managing clips, no Animator Controller complexity. This speed lets you experiment freely and find the perfect feel.
Designer-Friendly Animation Tools
Once you build a tween library, you can expose simple methods to designers or create custom inspectors. Designers can adjust timing and easing without touching code, empowering your whole team.
Foundation for Advanced Techniques
Understanding tweening is foundational to advanced animation techniques like animation curves, spring systems, and physics-based animation. Master this first, and those advanced topics become much more approachable.
Your Next Steps: Keep Experimenting
Here's what I recommend based on where you are:
For Beginners: Implement the three blueprints I showed you. Get them working, then experiment with different easing functions. Try tweening rotation, color, and other properties. The more you practice, the more intuitive it becomes.
For Intermediate Developers: Add sequence and parallel tween support. Build a fluent API like TweenManager.MoveTo(...).Then(ScaleTo(...)) that chains animations. Implement looping tweens and yoyo effects (animations that play forward then backward).
For Advanced Developers: Optimize for garbage-free operation using object pools. Implement custom curve support so designers can draw their own easing functions in the inspector. Study DOTween's source code to see how professionals solve these problems.
The best way to learn is by adding tweening to every project. Every button, every popup, every camera movement—practice applying tweens until it becomes second nature.
Wrapping Up: Why This Changes Your Game Development
Look, building a Unity tweening system isn't rocket science. It's just coroutines, lerp, and some math. But that simple combination? It's the difference between games that feel stiff and unresponsive versus games that feel juicy and polished.
I've used tweening on everything from simple UI animations to complex gameplay systems. It's one of those skills that immediately elevates your work. Players might not consciously notice the smooth animations, but they'll definitely feel the difference.
You've got all the pieces now—the coroutine structure, the interpolation logic, the easing functions, and three complete implementation blueprints. Time to make your games feel better.
Ready to Start Building Your First Game?
You've just learned how to build a custom Unity tweening system from scratch—now it's time to apply this knowledge to a real game project.
If you're ready to go from understanding these concepts to actually implementing them in a complete game, check out the Mr. Blocks course on Outscal.
You'll build a fully functional game from scratch, applying animation techniques, game feel programming, and professional development workflows. It's the perfect next step to transform from learning about game development to actually building professional-quality game experiences.
Key Takeaways
- Tweening generates intermediate values between start and end points over time using interpolation, enabling smooth procedural animations entirely in code without Unity's animation system
- Coroutines act as frame-perfect timers for tweens by looping each frame, accumulating elapsed time until duration is reached, making them ideal for managing animation timing
- Lerp functions handle the interpolation math by taking start value, end value, and progress (0 to 1) to calculate the current animated value each frame
- Easing functions transform linear progress into natural-feeling motion by creating acceleration and deceleration curves (EaseOutQuad, EaseInOutQuad, EaseOutBack) that make animations feel polished
- A static TweenManager centralizes coroutine management by creating a persistent coroutine runner GameObject, allowing tween methods to be called from anywhere without MonoBehaviour dependencies
- Use Dictionary tracking to prevent conflicting tweens by storing active coroutines per target and stopping old tweens before starting new ones on the same object
- LerpUnclamped is required for easing functions that overshoot like EaseOutBack, since regular Lerp clamps values between 0-1 and ruins the bounce effect
- Generic delegate-based tweening enables animating any property by passing setter functions like
alpha => canvasGroup.alpha = alphato tween non-Transform properties like UI alpha, colors, or custom values
Common Questions
What is tweening in Unity?
Tweening (short for in-betweening) is the process of generating intermediate values between a start and end value over a specific time period. In Unity, it lets you create smooth animations directly in code—like moving objects, fading UI, or scaling buttons—without using Unity's animation system. Think of it like a dimmer switch smoothly transitioning a light from off to on instead of an instant flip.
How do I create a Unity tweening system using coroutines?
Build a coroutine that acts as a timer using a while loop that runs until elapsed time exceeds duration. Each frame, calculate progress t = elapsedTime / duration, apply an easing function, then use Vector3.Lerp or Mathf.Lerp to interpolate between start and end values. Wrap this in a static TweenManager class with a persistent coroutine runner to start tweens from anywhere in your code.
What are easing functions and why do I need them?
Easing functions are mathematical formulas that control the rate of change over time, creating acceleration and deceleration instead of constant-speed movement. Without easing, animations look robotic and stiff. Functions like EaseOutQuad make objects start fast and slow down gently at the end, while EaseOutBack creates a satisfying overshoot and settle effect. They're what make animations feel natural and polished.
When should I use a custom tweening system versus DOTween or LeanTween?
Build a custom system if you're learning animation fundamentals or working on very small projects with simple tweening needs. Use third-party libraries like DOTween or LeanTween for production games—they're highly optimized, garbage-free, handle thousands of concurrent tweens, and provide chainable APIs for complex sequences. The performance and stability benefits are worth it for serious projects.
How do I tween UI elements like Canvas Group alpha?
Create a generic TweenFloat method that accepts an Action<float> delegate as a setter function. Pass a lambda expression like alpha => canvasGroup.alpha = alpha that updates the property. Your tween coroutine calls this setter each frame with the interpolated value. This pattern works for any float property—volume, fill amount, material properties, etc.
What's the difference between Lerp and LerpUnclamped?
Lerp clamps the progress value t between 0 and 1, preventing overshooting. LerpUnclamped allows t to go beyond these bounds, which is necessary for easing functions like EaseOutBack that intentionally overshoot the target value to create a bounce effect. Use LerpUnclamped when your easing function returns values outside 0-1.
How do I prevent multiple tweens from fighting over the same object?
Use a Dictionary<Transform, Coroutine> to track active tweens. Before starting a new tween, check if one already exists for that target. If it does, call StopCoroutine on the old one and remove it from the dictionary before starting the new tween. This prevents jittery movement caused by multiple coroutines modifying the same property.
What's the best way to organize easing functions in Unity?
Create a static Easing class that contains all your easing function methods as public static functions. This keeps the math separate from your tweening logic and makes functions easily accessible from anywhere. Add new easing types as needed—EaseInQuad, EaseOutQuad, EaseInOutQuad, EaseOutBack, etc.
How do I chain animations or trigger logic when a tween completes?
Add an Action onComplete parameter to your tween methods. At the end of your coroutine, after setting the final value, call onComplete?.Invoke(). This callback function executes when the tween finishes, allowing you to chain animations (start another tween in the callback) or trigger game logic (show a popup, play a sound, etc.).
Why does my tweened object not reach the exact end position?
Floating-point precision errors in the loop can prevent reaching the exact end value. Always explicitly set the final value after your while loop completes: if (target != null) target.position = endPosition;. This ensures the object ends up exactly where intended, even if the last frame's interpolation was slightly off.