Unity & C# 学習教材

線形補間アニメーション(Lerp)

「オブジェクトをゆっくり目標地点まで移動させたい」というときに使う線形補間(Lerp)を学びます。まず手計算で仕組みを理解し、次に Unity の便利なメソッドで書き直します。最後に「イージング」を使ってアニメーションに緩急をつける方法も学びます。

学習目標

このページを読み終えると、以下のことができるようになります。

前提知識


1. 手計算で理解する線形補間

「X 座標を 0 から 10 に、2 秒かけて動かす」場面を考えます。1 秒後(全体の半分)なら値は 5、1.5 秒後(全体の 75%)なら値は 7.5 であるべきです。これを一般化すると次の式になります。

1
現在値 = 開始値 + (目標値 − 開始値) × t

t(進捗度) は 0 〜 1 の値で、0 が「まだ始まっていない」、1 が「完了した」を意味します。

1
t = 経過時間 ÷ アニメーション全体の時間

たとえば「全体 2 秒のアニメーションで 1.5 秒が経過した」場合、t = 1.5 ÷ 2 = 0.75 です。

1
現在値 = 0 + (10 − 0) × 0.75 = 7.5

毎フレーム更新する

Unity では Time.deltaTime で「直前のフレームから今のフレームまでの経過時間(秒)」を取得できます。これを Update 内で積み重ねることで経過時間を計測し、毎フレーム t を更新できます。

Mathf.Clamp01 — 値を 0 〜 1 の範囲に制限します。Mathf.Clamp(value, 0f, 1f) と同じ意味ですが、よく使うので専用メソッドが用意されています。

書式:Mathf.Clamp01 メソッド

1
public static float Clamp01(float value);
パラメータ 説明
value float 制限したい値

戻り値: floatvalue が 0 未満なら 0、1 より大きければ 1、それ以外はそのまま返す

_elapsed / _duration は時間が経つにつれて 1 を超えます。Clamp01 で制限しないと、オブジェクトが目標地点を通り越して動き続けてしまいます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;

public class LerpSample : MonoBehaviour
{
    private float _elapsed  = 0f;   // 経過時間(秒)
    private float _duration = 2f;   // アニメーション全体の時間(秒)
    private float _startX   = 0f;   // 開始 X 座標
    private float _endX     = 10f;  // 目標 X 座標

    private void Update()
    {
        _elapsed += Time.deltaTime;

        float t = _elapsed / _duration;   // 進捗度(0 〜 ∞)
        t = Mathf.Clamp01(t);             // 0 〜 1 に制限

        float x = _startX + (_endX - _startX) * t;

        var pos = transform.position;
        pos.x = x;
        transform.position = pos;
    }
}

このコードを実行すると、オブジェクトは 2 秒かけて X = 0 から X = 10 まで等速で移動して止まります。


2. Unity の Lerp メソッド

前のセクションで書いた式 開始値 + (目標値 − 開始値) × t は、Unity が Lerp メソッドとして提供しています。

Vector3.Lerp — 2 つの Vector3t の割合で線形補間した値を返します。

書式:Vector3.Lerp メソッド

1
public static Vector3 Lerp(Vector3 a, Vector3 b, float t);
パラメータ 説明
a Vector3 開始値(t = 0 のとき返る値)
b Vector3 終了値(t = 1 のとき返る値)
t float 補間の進捗度(0 〜 1)。範囲外は自動的に丸められる

Vector3.Lerp(a, b, t) は内部で a + (b - a) * t を計算します。手計算の式とまったく同じです。

先ほどのコードを Vector3.Lerp を使って書き直すと、次のようにすっきりします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;

public class LerpSample : MonoBehaviour
{
    private float   _elapsed  = 0f;
    private float   _duration = 2f;
    private Vector3 _startPos;
    private Vector3 _endPos;

    private void Start()
    {
        _startPos = transform.position;
        _endPos   = transform.position + new Vector3(10f, 0f, 0f);
    }

    private void Update()
    {
        _elapsed += Time.deltaTime;

        float t = Mathf.Clamp01(_elapsed / _duration);
        transform.position = Vector3.Lerp(_startPos, _endPos, t);
    }
}

💡 ポイント: Vector3.Lerpt は自動的に 0 〜 1 に丸められます。ただし明示的に Clamp01 を書いておくと、コードの意図が読み手に伝わりやすくなります。


Mathf.Lerp — float の補間

座標だけでなく、音量・透明度・速度などの float 値を補間したいときは Mathf.Lerp を使います。

Mathf.Lerp — 2 つの floatt の割合で線形補間した値を返します。

書式:Mathf.Lerp メソッド

1
public static float Lerp(float a, float b, float t);
パラメータ 説明
a float 開始値
b float 終了値
t float 補間の進捗度(0 〜 1)
1
2
// 音量を 0 から 1 に 3 秒かけてフェードイン
float volume = Mathf.Lerp(0f, 1f, t);

Vector3.Lerp との違いは扱う型だけです。仕組みはまったく同じです。


3. イージング関数

線形補間では t が一定の速さで 0 → 1 へ進みます。これが「等速運動」のように見える原因です。

イージング関数とは、t の進み方を加工することで、アニメーションの見た目に緩急をつける関数です。加工した値を t' とすれば、Lerptt' を渡すだけで実現できます。

Linear(線形)

加工なし。等速で動きます。

1
t' = t
1
2
float tEased = t;
transform.position = Vector3.Lerp(_startPos, _endPos, tEased);

Ease In(ゆっくり始まり → 加速)

最初はゆっくりで、後半に向かって速くなります。動き始めの唐突感を和らげるのに使います。

1
t' = t × t
1
2
float tEased = t * t;
transform.position = Vector3.Lerp(_startPos, _endPos, tEased);

Ease Out(勢いよく始まり → 減速)

最初は速く、後半はゆっくり止まります。ボールが転がってゆっくり止まるような印象になります。

1
t' = 1 − (1 − t) × (1 − t)
1
2
float tEased = 1f - (1f - t) * (1f - t);
transform.position = Vector3.Lerp(_startPos, _endPos, tEased);

Ease In Out(ゆっくり始まり → 速くなり → ゆっくり終わる)

最初と最後はゆっくり、中間は速くなります。UI パネルの開閉やカメラの移動に向いています。

1
2
t' = t < 0.5 のとき → 2 × t × t
     t ≥ 0.5 のとき → 1 − 2 × (1 − t) × (1 − t)
1
2
3
4
float tEased = t < 0.5f
    ? 2f * t * t
    : 1f - 2f * (1f - t) * (1f - t);
transform.position = Vector3.Lerp(_startPos, _endPos, tEased);

💡 ポイント: t = 0.5 のとき、前半の式も後半の式も同じ値(0.5)を返します。そのため、中間点でなめらかにつながります。


イージング込みの完全なサンプル

どのイージングを使うかを int の数値で切り替えるサンプルです。0 = Linear1 = Ease In2 = Ease Out3 = Ease In Out とします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using UnityEngine;

public class EasingDemo : MonoBehaviour
{
    [SerializeField] private int   _easingType = 0;  // 0=Linear, 1=Ease In, 2=Ease Out, 3=Ease In Out
    [SerializeField] private float _duration   = 2f;

    private Vector3 _startPos;
    private Vector3 _endPos;
    private float   _elapsed = 0f;

    private void Start()
    {
        _startPos = transform.position;
        _endPos   = transform.position + new Vector3(5f, 0f, 0f);
    }

    private void Update()
    {
        _elapsed += Time.deltaTime;
        float t      = Mathf.Clamp01(_elapsed / _duration);
        float tEased = ApplyEasing(t);
        transform.position = Vector3.Lerp(_startPos, _endPos, tEased);
    }

    private float ApplyEasing(float t)
    {
        if (_easingType == 1)
        {
            return t * t;
        }

        if (_easingType == 2)
        {
            return 1f - (1f - t) * (1f - t);
        }

        if (_easingType == 3)
        {
            if (t < 0.5f)
            {
                return 2f * t * t;
            }

            return 1f - 2f * (1f - t) * (1f - t);
        }

        return t;
    }
}

Inspector の Easing Type0123 に切り替えて、動きの違いを確認してみましょう。


よくあるミス

1
2
3
4
5
6
// ❌ NG: _elapsed を t に直接渡す(1 を超えると目標地点を通り越す)
transform.position = Vector3.Lerp(_startPos, _endPos, _elapsed);

// ✅ OK: Clamp01 で 0〜1 に制限してから渡す
float t = Mathf.Clamp01(_elapsed / _duration);
transform.position = Vector3.Lerp(_startPos, _endPos, t);
1
2
3
4
5
// ❌ NG: 毎フレーム「今の位置」から補間すると、一定速度ではなく最後に近づくほどゆっくりになる
transform.position = Vector3.Lerp(transform.position, _endPos, 0.1f);

// ✅ OK: Start で記録した開始座標を _startPos フィールドに保持しておく
transform.position = Vector3.Lerp(_startPos, _endPos, t);

まとめ


理解度チェック

  1. t = 0.3 のとき、Vector3.Lerp(Vector3.zero, new Vector3(10, 0, 0), t) はどの座標を返しますか?
  2. Mathf.Clamp01 が必要な理由を説明してください。
  3. Ease Out の式 1 − (1 − t) × (1 − t) において t = 0t = 1 を代入すると、それぞれいくつになりますか?
解答を見る
  1. (3, 0, 0)0 + (10 − 0) × 0.3 = 3
  2. _elapsed / _duration は時間が経つと 1 を超える。Clamp01 で制限しないと、オブジェクトが目標地点を通り越してしまう。
  3. t = 0 のとき 1 − (1 − 0) × (1 − 0) = 0t = 1 のとき 1 − (1 − 1) × (1 − 1) = 1

視覚化デモ

イージング関数の違いを動かして確認できます。グラフの横軸は経過時間、縦軸は進捗度 t' です。

インタラクティブデモ

1 .75 .5 .25 0 t'(進捗度) 0s 3s 経過時間
現在値
0
100 0.00

次のステップ

Rigidbody で力を加える では、オブジェクトに物理的な力を加えて動かす方法を学びます。