「オブジェクトをゆっくり目標地点まで移動させたい」というときに使う線形補間(Lerp)を学びます。まず手計算で仕組みを理解し、次に Unity の便利なメソッドで書き直します。最後に「イージング」を使ってアニメーションに緩急をつける方法も学びます。
このページを読み終えると、以下のことができるようになります。
Time.deltaTime を使って毎フレーム進捗度を更新できるVector3.Lerp でオブジェクトを滑らかに移動させられる「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) と同じ意味ですが、よく使うので専用メソッドが用意されています。
1
public static float Clamp01(float value);
| パラメータ | 型 | 説明 |
|---|---|---|
value |
float |
制限したい値 |
戻り値: float — value が 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 まで等速で移動して止まります。
前のセクションで書いた式 開始値 + (目標値 − 開始値) × t は、Unity が Lerp メソッドとして提供しています。
Vector3.Lerp — 2 つの Vector3 を t の割合で線形補間した値を返します。
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.Lerpのtは自動的に 0 〜 1 に丸められます。ただし明示的にClamp01を書いておくと、コードの意図が読み手に伝わりやすくなります。
座標だけでなく、音量・透明度・速度などの float 値を補間したいときは Mathf.Lerp を使います。
Mathf.Lerp — 2 つの float を t の割合で線形補間した値を返します。
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 との違いは扱う型だけです。仕組みはまったく同じです。
線形補間では t が一定の速さで 0 → 1 へ進みます。これが「等速運動」のように見える原因です。
イージング関数とは、t の進み方を加工することで、アニメーションの見た目に緩急をつける関数です。加工した値を t' とすれば、Lerp の t に t' を渡すだけで実現できます。
加工なし。等速で動きます。
1
t' = t
1
2
float tEased = t;
transform.position = Vector3.Lerp(_startPos, _endPos, tEased);
最初はゆっくりで、後半に向かって速くなります。動き始めの唐突感を和らげるのに使います。
1
t' = t × t
1
2
float tEased = t * t;
transform.position = Vector3.Lerp(_startPos, _endPos, tEased);
最初は速く、後半はゆっくり止まります。ボールが転がってゆっくり止まるような印象になります。
1
t' = 1 − (1 − t) × (1 − t)
1
2
float tEased = 1f - (1f - t) * (1f - t);
transform.position = Vector3.Lerp(_startPos, _endPos, tEased);
最初と最後はゆっくり、中間は速くなります。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 = Linear、1 = Ease In、2 = Ease Out、3 = 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 Type を 0、1、2、3 に切り替えて、動きの違いを確認してみましょう。
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);
現在値 = 開始値 + (目標値 − 開始値) × tt = Mathf.Clamp01(経過時間 / アニメーション時間) で 0 〜 1 の進捗度を作るVector3.Lerp(a, b, t) は手計算の式と同じ計算を行うt を加工して動きに緩急をつける関数t = 0.3 のとき、Vector3.Lerp(Vector3.zero, new Vector3(10, 0, 0), t) はどの座標を返しますか?Mathf.Clamp01 が必要な理由を説明してください。1 − (1 − t) × (1 − t) において t = 0 と t = 1 を代入すると、それぞれいくつになりますか?(3, 0, 0) — 0 + (10 − 0) × 0.3 = 3_elapsed / _duration は時間が経つと 1 を超える。Clamp01 で制限しないと、オブジェクトが目標地点を通り越してしまう。t = 0 のとき 1 − (1 − 0) × (1 − 0) = 0、t = 1 のとき 1 − (1 − 1) × (1 − 1) = 1イージング関数の違いを動かして確認できます。グラフの横軸は経過時間、縦軸は進捗度 t' です。
Rigidbody で力を加える では、オブジェクトに物理的な力を加えて動かす方法を学びます。