【Unity】Spline上でオブジェクトを移動させる方法!速度指定やレーン移動も実装

Splineに沿って移動させる方法 Unity

こんにちは、ともくんのゲーム作り部屋にようこそ!

このページでは、

「Splineで作ったコースの上を移動させる方法が知りたい。」

「指定した速度に合わせて座標を更新する場合はどうすればいい?」

というお悩みの方に向けた内容となっています。

Splineは、レースゲームなどのコースを描写することができる機能で、その描いたコースに沿ってオブジェクトを移動させることができます。

Spline上で移動処理を作る場合、Spline Animateコンポーネントを使ったり、Evaluateメソッドで位置を取得することで簡単に移動することができます。

実際に僕が作り始めた競馬ゲームもSplineで競馬場を描いているため、Evaluateメソッドを使って以下のように馬を指定した速度で走らせたり、レーンの移動処理などを作っています。

そこでこの記事では、Spline上でオブジェクトを移動させる場合の基本的な方法と、実際に僕が競馬ゲームで作っている馬を走らせる実装処理についてまとめていきます。

参考記事:【Unity】レースゲームのコースをSplineで作成する方法

開発環境

Splineのコース上を移動させる基本的な方法

まずは、Splineで描いたコースに沿ってオブジェクトを移動させる場合の基本的な2つの方法をまとめていきます。

Spline上で移動処理を作る場合、

  • 「Spline Animate」コンポーネントを使う方法
  • 「Evaluate」メソッドを使ってスクリプトから動かす方法

という主に2つのパターンがあります。

ここでは、Splineで以下のようなサンプルコースを作り、この上を赤の丸い球が移動するという簡単な処理を実装してみます。

Spline Animateを使って移動処理を行う

Splineの上を移動させるのに最も簡単な方法が、Spline Animateというコンポーネントを使う方法です。

まず空のオブジェクトを作成して、AddComponentからSplines > Spline Animateを選択します。

そして、インスペクターからSplineの項目に作成したコースのSplineContainerをアタッチしておきます。

あとは、この親オブジェクトの配下に移動させたいオブジェクトを配置して、コースに埋もれないように座標を調整します。

これで親オブジェクトのSpline AnimateにあるPreviewのPlayを押すと、シーンビュー上でテスト的に動かすことができるので、確認しながらその他の設定項目を調整してあげます。

Spline Animateの主な設定項目
  • Play On Awake:ゲームスタートと同時に移動させるかどうか
  • Start Offset:スタート地点のずれを0~1の割合で指定する
  • Method:移動方法を時間か速度のどちらで指定するか
  • Easing:スタート(In)およびゴール(Out)の際にスムーズな加速・減速をさせる
  • Loop Mode:ループ時の詳細設定

すると、以下のようにSpline上を簡単に移動させることができました。

なお、Splineの線を中心軸として移動させるので、直接移動したいオブジェクトにSpline Animateをそのまま付けてしまうと、コースに埋もれてしまったりできることが限られます。そのため、上記のように空オブジェクトを挟んでおくと何かと便利です。

EvaluateでスクリプトからSpline上を移動させる

スクリプトからオブジェクトを移動させたい場合は、Evaluateというメソッドを使います。

EvaluateはSplineのコースにおける割合を引数に渡すことで、座標位置や向きを取得できるメソッドとなっていて、その座標を使いSplineに沿って移動処理を作ることができます。

Spline Animateの時と同様に、空オブジェクトを作成しておき、以下のスクリプトを紐づけておきます。

using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;

public class SplineMove : MonoBehaviour
{
    [SerializeField] private SplineContainer _splineContainer;
    private float _lapTime = 3f;
    private float _progress;

    private void Update()
    {
        // 1周を3秒で移動する場合の割合を計算
        _progress += Time.deltaTime / _lapTime;
        if (_progress > 1f)
        {
            _progress = 0f;
        }

        // Spline上の位置と向きを取得
        _splineContainer.Evaluate(_progress, out float3 position, out float3 tangent, out float3 upVector);

        // オブジェクト自身に反映させる
        transform.position = position;
        transform.rotation = quaternion.LookRotation(math.normalizesafe(tangent), upVector);
    }
}

ここでは、progressの変数を使って時間における進捗割合(進み具合)を表していて、コースの1周を3秒で移動する場合の計算をしています。

そして、21行目でEvaluateメソッドを使ってSplineにおける位置と向きを取得しています。

Evaluateを使う際は、第一引数の部分(今回はprogress)でSplineのどの位置かを表す割合を0~1の範囲で指定、第二引数以降でSpline上のそのデータをoutパラメーターを使い、座標の位置・進行方向のベクトル・上方向のベクトルの順番にそれぞれfloat3型で取得することができます。

ここで取得した値を自身のpositionやrotationにセットしてあげる(23行目以降)ことで、Spline上を移動させることができます。

あとは、この配下に移動させたいオブジェクトを配置して、先ほどと同様にコースに埋もれないように座標を微調整してあげると、以下のようにSpline上を綺麗に移動できているのが分かります。

なお、ここではEvaluateメソッドを使っていますが、向きなどの情報が要らない場合は、EvaluatePositionメソッドを使うことで、outなどを使わずに直接float3型の値を戻すことができるので便利です。

競馬場のコース上に馬を走らせる実装作業

ここからは、実際に自分で作成中の競馬ゲームにおいて、競馬場のコースに馬を走らせる処理を作っていきます。

具体的に実装する内容としては、主に以下の2つの処理になります。

  • 速度を指定してSplineに沿って馬を移動する処理
  • 現在いるレーンの情報を渡して①の座標位置をずらす処理

なお、馬のオブジェクトはUnityAssetStoreにある「Stylized Low Poly Animated Horse Pack」を使用していて、競馬場のコースは「【Unity】レースゲームのコースをSplineで作成する方法」の記事で作成したものを使用していますので、どちらも参考にしてください。

指定速度から距離を計算してSpline上を移動させる処理

今回作成している競馬ゲームでは、走っている馬の速度をスクリプトから指定して、それに応じてSplineに沿って移動するという処理を作っていきます。

Splineで速度を指定して動かす場合は、フレーム単位でその速度で進んだ距離を事前に計算して、Splineコース全体の距離との割合を出してあげることで、Evaluateメソッドで座標を取得できるようになります。

実際のものとは少し違いますが、以下のようなスクリプトを作り、馬の親オブジェクトに紐づけることで実装しています。

using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;

public class HorseController : MonoBehaviour
{
    [SerializeField] private SplineContainer _splineContainer;
    private float _currentDistance = 0f;    // 現在の距離
    private float _splineLength;    // コース全体の距離
    private float _speed = 60f; // 速度(km/h)

    private void Start()
    {
        _splineLength = _splineContainer.CalculateLength(); // SplineContainer(コース)の長さを取得
    }

    private void Update()
    {
        // 速度の単位をkm/hからm/sに変換して距離を計算
        _currentDistance += _speed / 3600 * 1000 * Time.deltaTime;

        // コース上の進捗率を計算
        float progress = Mathf.Clamp01(_currentDistance / _splineLength);

        // 進捗率からSpline上のデータを取得
        _splineContainer.Evaluate(progress, out float3 position, out float3 tangent, out float3 up);

        // 取得した値をセットする
        transform.position = position;
        transform.rotation = Quaternion.LookRotation(tangent, up);
    }
}

まずは、事前にスクリプトの14行目でCalculateLengthというメソッドを使って、Splineコース全体の距離を取得しています。

次に、指定した速度(ここでは60km/h)とdeltaTime(各フレームの時間)から、Updateの中の20行目でフレーム単位で進む距離を使って計算、その値を加算することで現在いるべき距離を出しています。

参考記事:【Unity】Time.deltaTimeとは?前のフレームからの経過時間を表す変数

あとは、今の2つの距離の値から23行目でSpline全体における割合を計算して、Evaluateメソッドの引数に渡すことで、現在の距離における座標と向きを取得しています。

最後に、28行目以降で自身のオブジェクトに座標と向きを渡してあげれば、以下のようにSpline上を移動して馬が走るようになります。

前回頑張って作ったラチのおかげで、、、なんだか速そうに見えます!(笑)

なお、実際に競馬は1周以上回ることから、進んだ距離が全体の距離を超える場合があるため、割合を出す際は一度剰余を計算してから実装する方がたぶん安全です。

また、ここでは60kmと速度を固定しちゃっていますが、実際に組み込む際は加速度を計算して速度を変動していくとよりリアルになりそうです。

走っているレーンによって座標位置をずらす処理

先ほど作成した移動処理を少し修正して、馬が走っているレーンを指定できるようにしていきます。

レーン番号を使って、Splineの線から今いるレーンに合わせて座標をずらすことになるのですが、競馬場コースは直線ではなく楕円の形状で走るため、ずらす方向が場所によって変わることからEvaluateで取得した向きを使っていくことになります。

以下がレーン移動も含めたスクリプトになります。

using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;

public class HorseController : MonoBehaviour
{
    [SerializeField] private SplineContainer _splineContainer;
    private float _currentDistance = 0f;
    private float _splineLength;
    private float _speed = 60f;

    // 追加したメンバ変数
    private float _currentLanePosition = 0f;    // 現在のレーン位置
    private float _centerLanePosition = 4.0f;   // 中央となるレーン位置
    [SerializeField] private float _targetLanePosition;  // 目標のレーン位置
    private float _laneWidth = 2.0f;    // レーンの幅
    private float _laneChangeSpeed = 1.0f;  // レーン変更の速度

    private void Start()
    {
        _splineLength = _splineContainer.CalculateLength();
    }

    void Update()
    {
        // レーン変更の処理
        if (_currentLanePosition != _targetLanePosition)
        {
            _currentLanePosition = Mathf.MoveTowards(_currentLanePosition, _targetLanePosition, _laneChangeSpeed * Time.deltaTime);
        }

        _currentDistance += _speed / 3600 * 1000 * Time.deltaTime;
        float progress = Mathf.Clamp01(_currentDistance / _splineLength);
        _splineContainer.Evaluate(progress, out float3 position, out float3 tangent, out float3 up);

        // 進行方向の右向きのベクトルを計算
        Vector3 rightDir = Vector3.Cross((Vector3)up, (Vector3)tangent).normalized;

        // レーンに応じてずれを計算
        float laneOffset = (_currentLanePosition - _centerLanePosition) * _laneWidth;

        // 座標にレーンのずれを反映
        transform.position = (Vector3)position + (rightDir * laneOffset);
        transform.rotation = Quaternion.LookRotation(tangent, up);
    }
}

まず、12行目辺りで現在のレーン位置を保持するfloat型の変数や必要となるレーン用変数を準備しておきます。

そして、Evaluateの第三引数で進行方向、第四引数で上方向のベクトルをそれぞれ取得できているので、37行目でそれらを使って進行方向の右側を正(左回りの場合)としたベクトルを取得して、その向きとレーン位置に応じてずらす座標分を40行目で計算しています。

あとは、43行目でこのずらした座標を加えたうえで、自身の座標に入れてあげれば完成です。

実際にレーン移動する際は、26~30行目に書いたようにMoveTowardsを使ってcurrentLanePositionの値を変動することで、馬がスッとレーン移動できるようになります。

今回はこのレーン移動をテスト的にインスペクターから行えるようにしていますが、以下のように徐々にレーンを移動するように馬が動けているのが分かります。

まとめ

このページでは、UnityのSplineで描いたコースに沿ってオブジェクトを移動させる方法と、実際に作っている競馬ゲームにおいて馬を走らせる処理についてまとめていきましたが、いかがでしたでしょうか?

Spline上に沿ってオブジェクトを移動させたい場合は、基本的にSpline Animateで移動させるか、Evaluateメソッドを使うことで、Spline上の座標を取得して移動させることができます。

また、速度を指定して移動させる場合は、フレーム単位で進んだ距離を事前に計算しておき、Splineの全体の距離との割合を使ってEvaluateで座標を取得して移動させます。

今回紹介したように、スクリプトからSplineを扱うことで、ただ移動させるだけでなくレーンに合わせてSplineからずらすといったこともできるので非常に便利です。

最後までお読みいただきまして、ありがとうございました!

この記事を書いた人

ゲーム作りを学び始めた一児のパパ、ともくんです!

このブログは、子供の「ゲームを作ってみたい!」という一言がきっかけで始まりました。プログラミング経験ゼロから、本を片手に独学でゲーム開発に挑戦している記録です。

初めてゲーム作りに触れる方と同じ目線で、「一緒に学んでいける場所」にできたら嬉しいです!

未熟な部分も多いので、「このコードはもっとこう書けるよ!」といったアドバイスがあれば、ぜひ優しく教えていただけると助かります。

Unity

コメント