【Unity】任意軸周りでオブジェクトを円運動させる

キャラクターに次のような円を描く動きをつけたいけど…
どうすればいいの~?

Unityにはこのような円運動を実現できる方法がいくつか存在するわ!
それぞれ利点・欠点が存在するから用途によって使い分けるのが良いわね。

結論を述べると、クォータニオン(Quaternion)を用いることで自由度が高く、簡潔なコードで円運動を実装できます。

本記事では、指定された中心点の周りを指定された軸で円運動させることを目標とします。
実現方法をいくつかご紹介します。

Transform.RotateAroundメソッドを使う

スクリプトから円運動を行う比較的楽な方法の一つです。
Transform.RotateAround() は、中心点、軸、回転角度の3つの引数からなるメソッドです。

public void RotateAround (Vector3 point, Vector3 axis, float angle);

pointにはワールド座標中心点、axisには回転軸、angleには度数法回転角度を指定します。

実装例

Transform.RotateAround()で円運動をするスクリプトのサンプルです。

UseRotateAround.cs
using UnityEngine;

/// <summary>
/// Transform.RotateAroundを用いた円運動
/// </summary>
public class UseRotateAround : MonoBehaviour
{
    // 中心点
    [SerializeField] private Vector3 _center = Vector3.zero;

    // 回転軸
    [SerializeField] private Vector3 _axis = Vector3.up;

    // 円運動周期
    [SerializeField] private float _period = 2;

    private void Update()
    {
        // 中心点centerの周りを、軸axisで、period周期で円運動
        transform.RotateAround(
            _center,
            _axis,
            360 / _period * Time.deltaTime
        );
    }
}

このスクリプトを円運動させたいゲームオブジェクトにアタッチしてください。

実行結果

ゲームを実行すると、Periodに指定した周期で円運動するようになります。

CenterAxisも色々弄って試してみてください。

円運動するキューブは、位置だけでなく向きも回転するんだね~

そう、それが利点でもあり、欠点でもあるの。

Transform.RotateAround()は、指定された中心点を基準にオブジェクトを回転させるメソッドであるため、位置と向きが同時に更新されます。

向きを変えずに位置だけ更新させることはできません。

もし向きを変えずに位置だけ変えたい場合、ゲームオブジェクトを2つ用意する必要がありますが、PositionConstraintを用いる方法があります。

PositionConstraintとの組み合わせで向きを変えずに円運動

PositionConstraintは、位置の制約を持たせるためのもので、例えばあるゲームオブジェクトと位置をピッタリ合わせることができます。

参考:Unity - Scripting API: PositionConstraint

ここでは、空のゲームオブジェクトを円運動させて、これにPositionConstraintで位置だけ追従させるようにします。

実装例

空のゲームオブジェクトCircularMotionに前述のUseRotateAroundスクリプトをアタッチ、対象のCubeオブジェクトにはPositionConstraintをアタッチします。

アタッチしたPositionConstraintのActivateボタンをクリックし、Constraint SettingsのSourcesにCircularMotionを指定します。

実行結果

これで向きを変えずに円運動できるようになりました。

でも、オブジェクトが2つ必要だと管理が煩雑になっちゃうよね…

…そうね。
でも、クォータニオンを使えばこの問題も解決だわ!

クォータニオンを使う

先ほど挙げた方法では、動きに縛りが出てきたり、オブジェクトの管理が煩雑になったりとデメリットがありました。

そこで、円運動による軌道を自前で計算する方法の登場です!
自前と言っても、Unityのクォータニオンを活用すれば、煩雑な数式を設計せずとも自由度の高い円運動が実現できます。

実装例

前に挙げた例の円運動をすべて行えるようにしたスクリプトです。

CalcMyself.cs
using UnityEngine;

/// <summary>
/// クォータニオンで円運動の軌道を計算
/// </summary>
public class CalcMyself : MonoBehaviour
{
    // 中心点
    [SerializeField] private Vector3 _center = Vector3.zero;

    // 回転軸
    [SerializeField] private Vector3 _axis = Vector3.up;

    // 円運動周期
    [SerializeField] private float _period = 2;

    // 向きを更新するかどうか
    [SerializeField] private bool _updateRotation = true;

    private void Update()
    {
        var tr = transform;
        // 回転のクォータニオン作成
        var angleAxis = Quaternion.AngleAxis(360 / _period * Time.deltaTime, _axis);

        // 円運動の位置計算
        var pos = tr.position;

        pos -= _center;
        pos = angleAxis * pos;
        pos += _center;

        tr.position = pos;

        // 向き更新
        if (_updateRotation)
        {
            tr.rotation = tr.rotation * angleAxis;
        }
    }
}

_updateRotationフラグを設けることで、向きを一緒に回転させるか、あるいは固定させるか選択できるようにしました。

Quaternion.AngleAxis()は、指定された角度と軸での回転を表すクォータニオンを取得するためのメソッドです。

public static Quaternion AngleAxis(float angle, Vector3 axis);

angleには度数法回転角度を、axisには回転軸を指定します。

得られたクォータニオンをVector3に掛けることでベクトルを回転できます。
位置を回転させる場合、原点中心での回転になります。

ただし、本記事では原点以外の回転中心で回転させたいため、次の部分で回転前後で位置をずらしています。

pos -= _center;
pos = angleAxis * pos;
pos += _center;

向きの回転は、 transform.rotationにクォータニオンを掛けることで実現しています。

tr.rotation = tr.rotation * angleAxis;

クォータニオン同士を掛けると、回転をひとまとめにすることができます。
この時、掛ける順序によって結果が異なることに注意してください。(交換法則が成り立たない)
回転操作は右側から順に実行されていきます。

実行結果

期待通りの円運動になりました!

まとめ

円運動はUnity標準のAPIで比較的簡単に実現できることが分かりました。
クォータニオンがあれば、三角関数(Sin、Cos)を用いなくても汎用的な円運動を設計できます。

人によっては使ったり使わなかったりするかもしれませんが、開発の一助となれれば幸いです!

参考サイト