UnityのTimeline(タイムライン)は、カットシーンの演出やキャラクターのアニメーションシーケンスを直感的に構築するための強力なツールです。しかし、歩行や走行などの同じアニメーションクリップをタイムライン上でループ再生させたり、複数のクリップを繋ぎ合わせて連続再生させたりした際、「ループを繰り返すたびに、キャラクターの足の位置や体の中心が徐々にズレていき、最終的に本来のルートから大きく逸脱してしまう」という現象(アニメーション・ドリフト)に悩まされたことはないでしょうか。本記事では、この位置ズレバグの技術的な背景と、初心者にも分かりやすい例え話、そして完全にズレを解消するための設定フローと具体的なC#制御コードを徹底解説します。
1. 例え話で理解する:『ランニングマシンの緩み』と『微小な歩幅の差』
ループ再生時の位置ズレ(ドリフト)を直感的に理解するために、「ランニングマシン(トレッドミル)で走るランナー」を例えに考えてみましょう。
ランナー(キャラクター)は、ランニングマシンの上で、全く同じ歩幅とタイミングで走り続けています。マシンのベルトコンベアが後ろに下がる速度と、ランナーが前に進む速度が100%完全に一致していれば、ランナーの位置は1ミリもズレることなく、常にマシンの中心に留まり続けます。これが正しいループアニメーションの状態です。
しかし、もしマシンのベルトがループ(1周)するたびに、「ほんの0.1ミリだけベルトの滑りが甘く、ランナーが前に進みすぎてしまう」という小さなバグ(計算の端数切り捨てや、初期位置への戻し忘れ)があったとします。1歩走るだけなら、0.1ミリのズレは目に見えません。しかし、このループを100回、1000回と繰り返していくと、どうなるでしょうか。最初は0.1ミリだったズレが、「1センチ」「10センチ」「1メートル」と累積していき、最終的にはランナーがマシンの前方に衝突するか、はみ出して転倒してしまいます。
Timelineにおけるアニメーション・ドリフトもこれと全く同じです。アニメーションクリップが終了して次のループ(または次のクリップ)に切り替わる瞬間、キャラクターの基準点(Root)の位置座標を完全に「補正・リセット」しなければ、フレーム間の微小なズレや外挿計算の誤差がループのたびに積み重なり、キャラクターの位置が画面上で目に見えてスライドしていくことになります。
2. 根本的な原因:Root Motion の蓄積と外挿(Extrapolation)
技術的に、このバグを引き起こす要因は主に以下の2点に集約されます。
- Root Motion(ルートモーション)の累積とリセット不全: ルートモーションとは、アニメーション内の移動量(重心の変位)を、Unityの
Transform座標に動的に足し合わせて移動させる機能です。Timeline上でクリップがループする際、Animatorは「アニメーションが1周したので、重心位置をクリップの初期位置に戻す」必要がありますが、Timelineの評価システムとAnimatorの更新が正しく噛み合っていないと、戻るべき初期位置がループ前の最終位置のまま加算され続け、変位が累積してしまいます。 - Post-Extrapolation(後方外挿)の設定不備: クリップの終了後から次のクリップが始まるまでの隙間、あるいはループの継ぎ目の極小のフレームにおいて、キャラクターのTransformがどのように挙動するかを定義するのが外挿設定です。これが不適切だと、クリップ外の暗黙のフレームでTransformが引き伸ばされ、微小なドリフトが発生します。
3. 実務で役立つ解決ステップと設定手順
このアニメーション・ドリフトを解消するための標準的な設定手順と、スクリプトによる完全な制御手法です。
ステップ1:Track Offsets の適切な設定(最も重要)
Timelineエディタ上で、対象の Animation Track のヘッダーを右クリック、またはインスペクターを展開し、Track Offsets プロパティを設定します。
- Apply Scene Offsets: キャラクターの初期位置をシーン内に配置された現在のTransform座標を基準として開始させます。手動配置されたキャラクターにループをかける場合に最適です。
- Apply Transform Offsets: タイムライン側で開始座標と角度を厳密に固定指定します。カットシーンなどで開始位置を毎回一定にクランプさせ、蓄積エラーを防止したい場合に強力です。
- Auto (デフォルト): Unityが自動判別しますが、ループ処理時は判別が狂うことがあるため、上記のいずれかを明示的に指定することを強く推奨します。
ステップ2:Clip Extrapolation の最適化
ループさせるアニメーションクリップをTimeline上で選択し、インスペクターの Animation Playable Asset > Clip Extrapolation を調整します。
- Pre-Extrapolation(再生開始前の挙動): `Hold` または `None` に設定します。
- Post-Extrapolation(再生終了後の挙動): `Hold` または `None` に設定します。
※これを `Continue`(ループなしのままパラメータの移動速度を維持して補間し続ける)などに設定していると、クリップの終端を跨ぐたびに余計な移動慣性がTransformに加算され、滑るようなドリフトの原因になります。
ステップ3:C#スクリプトによる移動制御の切り離し(確実な回避策)
ルートモーションとTimelineの連携は、複雑なゲーム進行状況によって破綻しやすいため、「アニメーションはただの再生(Transform移動なし)に専念させ、座標移動はC#スクリプトから完全に制御する」設計に切り離すのが実務上最も頑健です。Animatorの Apply Root Motion をOFFにし、以下のスクリプトをアタッチして物理挙動を完全に制御します。
using UnityEngine;
using UnityEngine.Playables;
[RequireComponent(typeof(Animator))]
public class TimelineMovementController : MonoBehaviour
{
public PlayableDirector director;
public float moveSpeed = 3.0f;
private Animator animator;
private bool isPlayingTimeline = false;
void Start()
{
animator = GetComponent<Animator>();
// Timelineの再生・停止イベントを購読
if (director != null)
{
director.played += OnTimelinePlayed;
director.stopped += OnTimelineStopped;
}
}
void OnTimelinePlayed(PlayableDirector pd)
{
isPlayingTimeline = true;
// ルートモーションをOFFにし、スクリプト制御に切り替える
animator.applyRootMotion = false;
}
void OnTimelineStopped(PlayableDirector pd)
{
isPlayingTimeline = false;
}
void Update()
{
// タイムライン再生中のみ、物理的に一定の速度で前方に移動させる
if (isPlayingTimeline)
{
// キャラクターの前方ベクトルに向かって、フレームに依存しない一定速度で移動
transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
}
}
void OnDestroy()
{
if (director != null)
{
director.played -= OnTimelinePlayed;
director.stopped -= OnTimelineStopped;
}
}
}
4. トラブルシューティング・チェックリスト
設定を見直してもキャラクターがスライドしてズレる場合は、以下の項目を確認してください。
- アニメーションクリップのLoop Pose設定: Projectウィンドウでアセット(.fbx)を選択し、インスペクターの「Rig / Animation」タブで「Loop Time」および 「Loop Pose」 チェックボックスがONになっているか。Loop PoseがOFFだと、アニメーションの最初と最後のフレームのポーズが一致せず、ループの瞬間にカクつきやズレが生じます。
- Bake Into Pose の適用: Animationインスペクターの「Root Transform Position (Y)」および「Root Transform Position (XZ)」の 「Bake Into Pose」 をONにして、重心の高さや前後左右の物理座標への反映を強制的にアニメーション内に焼き込み(固定化)します。