Unityで等速に移動する方法まとめ

基本的に全部右(1, 0, 0)に移動する場合について考えます。
その他の方向についても同様に出来ると思います。

Unityのバージョンは2019.3です。

Transform

Transform.positionの書き換え

単純に座標の足し算です。
これらの処理はfpsの影響を受けるのでUpdateで書く際にはTime.deltaTimeを掛けましょう。(そうしないとfpsによって速度が変わります)

private void Update()
{
    transform.position += Vector3.right * Time.deltaTime;
}

また、FixedUpdateで書く場合はTime.deltaTimeは必要ありません。

private void Update(){
    transform.position += Vector3.right * 0.02f;
}

Transform.Traslate

Transform.positionの書き換えと原理はほぼ同じですが、Unityの関数を利用します。
fpsの影響を受けるという点でも同じです。

private void Update(){
    transform.Translate(Vector3.forward * Time.deltaTime);
}
private void Update(){
    transform.Translate(Vector3.forward * 0.02f);
}

違いとしては、引数に指定する数値がワールド座標ではなく対象のオブジェクトからみた進行方向で指定するという事です。
Vector3.forwardを指定すれば前方(向いてる方向)に進み、Vector3.rightを指定すれば右に進みます。

このようにTransformの利用はシンプルで分かりやすいです。
しかしRigidbodyやColliderとの相性が悪かったりしますよね...。 (例えば、Rigidbody.velocityの値は変わりません)

Rigidbody

ここから先はRigidbody及びColliderを利用する場合の話です。 Rigidbodyを利用した物理演算に関する処理は必ずFixedUpdateで行います。

velocityの値を書き換える

Rigidbodyがもつ速度のパラメータであるvelocityの値を上書きします。

private void FixedUpdate()
{
    GetComponent<Rigidbody>().velocity = Vector3.right;
}

Rigidbodyを使った一番シンプルな方法かと思います。
ただしこのようにvelocityを直接書き換えてしまうと、他の物理演算の影響を受ける時に問題になる事が多いです。

AddForce + velocity を使う

velocityの書き換えは問題になることが多いので、AddForceを利用します。 しかし単純にAddForceを繰り替えすだけでは、ひたすら加速してしまいます。

そこで速度の遅い時には力を強めて速い時には弱める事で、等速を維持します。
自転車を漕ぐときに最初は力を入れて踏み込み、スピードに乗ったら徐々に力を抜くのに似ていますね。

これをUnityで実装してみます。
自身の速度はrb.velocity (rbはRigidbody)で取得できます。
これを減算する事で、遅い時は強く、速い時は弱い力を与える事ができます。
(つまり厳密には最初から等速ではなく、凄い速さで加速するだけで"ほぼ"等速と言えます)

private void FixedUpdate(){
    var rb = GetComponent<Rigidbody>();
    const float targetVelocity = 2;
    const float power = 20;
    rb.AddForce(Vector3.right * ((targetVelocity - rb.velocity.x) * power), ForceMode.Acceleration);
}

f:id:tmls:20200923190634g:plain

これで等速になりました。

しかしtargetVelocity = 2としたのに実際には1.4しか出ていないのは気になります。
これは摩擦が影響していて、↓のように摩擦0のPhysic Materialを作成して、

f:id:tmls:20200923191226p:plain

Cubeのコライダーに設定すれば、

f:id:tmls:20200923191346p:plain

velocityが2になっている事を確認できます。

f:id:tmls:20200923191737g:plain

現状、私はこの方法がベストなのではないかと思っています。

AddForce + 空気抵抗 を使う

Rigidbodyのdragの値は抵抗の値を表しています。
抵抗はF = kvの式で表され、速度に比例して変化します。

なので終端速度というものが存在し、徐々に加速して一定の速度に達するとそこからは等速になります。
雨粒がどれだけ高いところから落ちてきても、全部同じ速度なのと同じ原理です。

この終端速度を使って等速を表現します。
まず下記のようなスクリプトを作成し、

private void FixedUpdate(){
    const int power = 25;
    GetComponent<Rigidbody>().AddForce(Vector3.right * power, ForceMode.Acceleration);
}

続いてRigidbodyのdragの値を大き目に設定します。

f:id:tmls:20200923175835p:plain

これで動き始めは加速、しばらくすると等速になるかと思います。

f:id:tmls:20200923175415g:plain

徐々に加速したい場合には良いかもしれません。

摩擦を0にする

コライダーに設定可能なPhysic MaterialはデフォルトでNoneになっていると思いますが、これは摩擦が0であるわけではありません。
Noneの時には、デフォルト値としてDynamic Friction = 0.6 Static Friction = 0.6が設定されています。

従ってこれを上述の方法でDynamic Friction = 0 Static Friction = 0にして例えば、

private void Start()
{
    GetComponent<Rigidbody>().velocity = Vector3.right;
}

このように初速のみを与えれば、あとは速度が維持され等速で動き続けることになります。 ただしこれは氷の上を滑ってるのと同じなので、障害物に当たれば減速したり止まったりします。

AddForce + 摩擦 で等速にすることは可能か?

AddForceと摩擦を使うと、何となく等速に出来そうな気がしますが基本的には出来ません。

摩擦力はF = μNの式で表され、垂直抗力に比例します。 垂直抗力が重力のみの場合、摩擦力は常に一定の力を加え続けます。 従って、空気抵抗のように終端速度が存在しません。

ただし一応、摩擦力と全く同じだけの力を加え続ければ理論上は等速に移動が可能です。 摩擦分を打ち消すだけの力を加える事で、実質的に摩擦力が0にするという話です。

下記のコードは、その一例です。

        private Rigidbody m_rb;
            
        private void Start()
        {
            m_rb = GetComponent<Rigidbody>();
            m_rb.velocity = Vector3.right;
        }

        private void FixedUpdate()
        {
            var col = GetComponent<Collider>();
            Vector3 g = Physics.gravity;
            float m = m_rb.mass;
            float u = 2 * col.material.dynamicFriction;

            float F = u * m * g.y;
            rb.AddForce(Vector3.right * -F, ForceMode.Acceleration);
        }

今回はuの計算を自身のコライダーに設定されたDynamicFrictionの値であると仮定しています。 実際にはこの条件が成立しないこともあるので、上記のコードは常に動くとは限りません。

このように、摩擦を用いた等速の表現は非常にシビアで汎用性が低い事が分かります。 また今回紹介してきた方法は全て摩擦の影響があっても無くてもpowerの値が十分に大きければ、正しく動作するはずですので、摩擦については自由に設定して良いかと思います。

まとめ

Unityで等速に移動する方法を見てきました。
どの方法を使うかは、ケースバイケースですので合ったものを利用いただければと思います。

私のおすすめとしては、物理演算の影響を受けられ即座に等速にすることができる「AddForce + velocity」が汎用性が高いと思うので困ったら試してみてはどうでしょうか。