Transform.RotateとQuaternion.Eulerでオブジェクト回転

ゲームを作っているとオブジェクトを回転させたい時など出てきたりします。
例えばルーレットとか、方位をコンパス。またはプレイヤーがやられた時に回転させるなどでしょうか。

今回はシンプルにゲームオブジェクトを回転させてみたいと思います。
Unity2Dでやってみます。
ゲームオブジェクトの回転
こんな感じでクルクル回してみましょう。

回転の仕組み

そもそも回転はどのようにして行われるのか確認してみます。
ゲームオブジェクトのInspectorを見るとTransformコンポーネントにRotationというものがあります。
ゲームオブジェクトのTransform
この値を変化させると回転出来そうですね。
ではx,y,xを順番に変化させていってみましょう。

x,y,zの回転

適当なオブジェクトを配置してそれを回してみましょう。
まずはxから変化させてみます。
x軸に回転させる

続いてyをやってみます。
Y軸で回転させる
ケバブ屋さんのクルクル回っているやつみたいです。

最後にZの値を変化させてみます。
Z軸で回転させる
このような感じで回転しました。
この値はX、Y、Zそれぞれどれを軸にして回転させるかというものです。
3Dにしてみるとわかりやすいと思ったので図にしました。
回転の仕組みの3D図
それぞれの軸を回転させると先程のような回転になります。
私は最初Xを回転させたら左右に、Yを回転させたら上下に回るだろうと勘違いしていました。
そしてどうしてZを回転させるとクルクル回るのかがイマイチ理解出来ませんでした。
それぞれの軸を中心に回ると言うことを理解したら納得できました。

すんなり理解できる方は良いのですが、私は理解力が乏しいので図にしてみたら理解できました。
今回は2Dのオブジェクトを回転させますので、正面を向いた状態で回すにはZ軸の値を変化させてあげれば良いということになります。
では早速やっていきましょう。

一定の速度で回転させてみる

まず回転させたいゲームオブジェクトを配置し、回転用のC#スクリプトをアタッチしておきましょう。
私は今回ArrowというゲームオブジェクトにRotateControllerというスクリプトをアタッチしました。
RotateContorllerには回転の速度と回転の角度をInspectorから設定したいので以下のようにしておきました。


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateController : MonoBehaviour
{
    public float rotateSpeed,rotateAngle;

続いてUpdate関数内でゲームオブジェクトのRotationの値を変化させてみましょう。

Transform.Rotate

Rotationの値を変化させるにはいくつか方法がありますが、まずは直感的にわかりやすいTransform.Rotate関数をUpdate内で使ってみます。

Update


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateController : MonoBehaviour
{
  /* 略 */
      void Update()
    {
        transform.Rotate(0, 0, rotateAngle *  Time.deltaTime * rotateSpeed );
    }

こんな感じです。引数はそれぞれx,y,zの順番となっています。
今回はz軸を回転させるので第三引数のみに値を入れています。
RotateSpeedを10,RotateAngleを1にして実行してみます。
Transform.Rotateの実験
このようにゆっくり動きました。
これはrotateAngleが1度の角度、それにTime.deltaTime(前のフレームからの経過時間)を掛けて1秒間に1度の回転をさせ、さらにrotateSpeedの10を掛けています。
もっと早く回してみましょうか。RotateSpeedは10にしたままで、RotateAngleの方を100にしてみました。
1秒間に100度回転する10倍ということになります。
transform.rotateの早いバージョン
こんな感じで早く回りました。

Quaternion.Euler

先ほどはTransform.Rotateで回転させましたが、これ以外にも Quaternion.Eulerというメソッドで回転させることも出来ます。
どのように使うのかまず実際にコードを書いてみましょう。

Update


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateController : MonoBehaviour
{
  /* 略 */
      void Update()
    {
    transform.rotation *= Quaternion.Euler(0, 0, rotateAngle * Time.deltaTime * rotateSpeed);
    }

ここで出てくるQuaternion.Eulerは、オブジェクトを回転させる別の方法です。この方法は少し複雑に見えるかもしれませんが、実際にはただ回転させたい角度を設定しているだけです。
引数もTransform.Rotateと同じくx,y,zの順番です。
QuaternionとかEulerってなんなの?ってなると思います。
簡単ですがそれぞれについて触れておきます。

Quaternion

Quaternion(クォータニオン)は、3D空間での回転を表現する数学的なツールです。
普通の数字とは違い、回転をより複雑に、でも正確に表すことができる特別な数値のセットです。
回転の「ねじれ」をうまく扱い、予期しない動き(ジンバルロックと呼ばれる問題)を防ぎます。
これは、空間内で物体がどう回転するかを正確に伝えるために非常に便利だそうです。

Euler

Euler角(オイラー角)を計算するメソッドです。
X軸、Y軸、Z軸の周りの回転を示す3つの数値で、より直感的に回転を理解できる方法です。
例えば、「上を向いて(X軸)、右に傾いて(Y軸)、ぐるっと回って(Z軸)」というような回転を表します。
しかし、Euler角を使うと、特定の状況で回転がおかしくなることがあります(ジンバルロックと呼ばれるそうです)。
だから、内部的にはQuaternionが使われることが多いみたいです。

詳しく知りたい方は以下にリンクを貼っておきます。


読んでみても難しいです。
とりあえずこんなふうに回転させることもできると覚えておいてください。

それではどうなるか実際に動かしてみましょう。
クルクルと回転する
先ほどと同じようにクルクル回りだしました。

特定の角度に回転させてみる

今まではUpdate関数内でクルクル回していましたが、次は特定の角度に回転させることを実験してみます。
試しに90度傾ける回転を実装してみます。
目標の角度を設定するためのfloat型のtargetAngle、そして現在の角度を保持しておくcurrentAngleというフィールドを用意します。


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateController : MonoBehaviour
{
    public float rotateSpeed,rotateAngle, targetAngle;
    private float currentAngle = 0.0f;
  /* 略 */

ではまずTransform.Rotateを使ってやってみましょう。

Transform.Rotateで目標の角度に回転させる

考え方として現在の角度がターゲットの角度に達するまで繰り返し処理をすれば良さそうに思います。
それをコードに書いてみましょう。


   /* 略 */
   void Update()
    {
        //目標に達していない場合は回転させる
        if(currentAngle < targetAngle)
        {
            //このフレームで回転させる角度を取得
            float rotateAmount = rotateAngle * Time.deltaTime;
            //現在の角度に足して更新する
            currentAngle += rotateAmount;
            //現在の角度がターゲット以上になってしまった場合
            if(currentAngle > targetAngle)
            {
                //回転させる量から差分を引く
                rotateAmount -= currentAngle - targetAngle;
            }
            //オブジェクトを回転させる
            transform.Rotate(0, 0, rotateAmount);
        }
    }

実行してみるとこのように90度で止まります。
90度で止まる
今回は左回りですが、右回りにしたい場合はどうなるでしょうか。
現在の角度がターゲットの角度より大きい場合に現在の角度から毎フレーム回転させる量をマイナスしていけば良さそうです。


   /* 略 */
   void Update()
    {
            if(currentAngle > targetAngle)
            {
                float rotateAmount = -rotateSpeed * Time.deltaTime;
                currentAngle += rotateAmount;
                if(currentAngle < targetAngle)
                {
                    rotateAmount += targetAngle - currentAngle;
                }
                transform.Rotate(0, 0, rotateAmount);
            }
    }

先程の逆ですね。
プラスでもマイナスでも対応したいのであればtargetAngleの値をマイナスかどうかif文でチェックし、左回り、右回りの処理を書いてあげればいいと思います。
それでは実際に動かしてみます。
targetAngleのプラスマイナス
プラスでもマイナスでも動くようになりました。

Quaternion.Lerpを使って回転させてみる

では次にQuaternionを利用して同じように回転させてみましょう。
こちらのほうがコードは簡潔になります。


public class RotateController : MonoBehaviour
{
    public float rotateSpeed,rotateAngle, targetAngle;
    Quaternion targetRotation;//ターゲット回転用の変数
    void Start()
    {
	//目標とする回転を設定。
        targetRotation = Quaternion.Euler(0, 0, targetAngle);

    }

    // Update is called once per frame
    void Update()
    {
        //現在の回転から目標の回転に徐々に移行。
        transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime);
        //LerpではなくSlerpのほうがより自然な動くをする。但し処理のコストは高くなるみたい
        //transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime);
        //現在の回転と目標の回転が0とほぼ等しいかどうかをチェック
        if (Mathf.Approximately(Quaternion.Angle(transform.rotation, targetRotation), 0f))
        {
            //等しいと判断されたらターゲットの回転にする
            transform.rotation = targetRotation;
        } 
    }

この様になりました。
StartでQuaternion型のtargetRotationを設定し、Update内でLerp(またはSlerp)で現在の回転と目標の回転の値を近づけていく(2つのクォータニオン値の間を補間)ことをしています。
回転終了に近づくにつれzの値の変化がすごく小さくなっていきますので、Mathf.Approximatelyを使ってほぼ同じ値かどうかを判断して最終的にターゲットの角度にしています。
実際に動かしてみます。
Quaternion.Lerp

このように回転させることが出来ました。
Transform.RotateとQuaternion.Lerpのどちらを使うかはケースによって異なると思います。

力を加えて回転させてみる

では最後にゲームオブジェクトに力を加えて回転させてみようと思います。
この記事の冒頭にあった画像のような感じですね。

Rigidbody2Dコンポーネント追加

力を掛けて回すということは物理演算をする必要があります。
そこでゲームオブジェクトにRigidbody2Dを追加して必要な設定していきましょう。
Rigidbody2Dのインスペクタ
赤い線のところが重要です。
Angular Dragは回転抗力という値で、この値はオブジェクトの回転を減速させるために使います。
初期値では0.05となっていますが、これだと回転がなかなか終わらないので1にしています。
次にConstraintsです。
ゲームオブジェクトが動かないようにFreeze PositionのXとYにチェックを入れておきます。
今回はYだけでも良さそうですが、Xに動かす事も無いので両方にチェックしてあります。

ここまで出来たら続いてスクリプトの方を書いていきます。

Rigidbody2D.AddTorque

Start時に回転の力をオブジェクトに加えてクルクル回しましょう。
Rigidbody2DのAddTorqueメソッドを使います。
このような感じになりました。


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateController : MonoBehaviour
{
    public float rotateSpeed,rotateAngle, targetAngle;
    Rigidbody2D rb2d;// Rigidbody2D コンポーネント
    float threshold = 0.01f;//停止判断の閾値
    bool isStop;//停止状態のフラグ
    void Start()
    {
        //Rigidbody2D コンポーネントを取得
        rb2d = GetComponent();
        // 初期トルクを追加
        rb2d.AddTorque(rotateSpeed, ForceMode2D.Impulse);
    }

    void Update()
    {
        // 回転速度が閾値以下になったら停止
        if (Mathf.Abs(rb2d.angularVelocity) < threshold && !isStop)
        {
            isStop = true;// 停止フラグを立てる
            Debug.Log("stop");// コンソールに停止をログ出力
            
            //回転を完全に止める
            rb2d.angularVelocity = 0f;// 角速度を0に設定
            rb2d.velocity = Vector2.zero;// 速度を0に設定
        }

    }
}

こんな感じでしょうか。
Start時にAddTorqueで回転させています。
ForceMode2D.Impulseは一瞬で大きな力をオブジェクトに加えるときに使用します。
これは、ボールをパンチする、バットでボールを強く打つ、またはロケットが打ち上げられる瞬間のような、短い時間で大きな力が作用する状況をシミュレートするのに適しています。
「一瞬でぐっと押す力」のようなものだと思ってください。
Mathf.Abs(rb2d.angularVelocity)はangular velocity(角度速)の絶対値を取得しています。Mathf.Absは値の正負にかかわらず、その数を取得します。(Mathf.Abs(-1)だったら1みたいな感じ)
シンプルに言うと「オブジェクトがどれだけ速く回転しているか」を示す値であり、その値が小さければ小さいほど、オブジェクトの回転は遅いことを意味します。
その値とthresholdの値を比較して、まだ止まっていない場合は止める処理に入る感じです。
では実際に動かしてみましょう。
まずはrotateSpeedが小さい値です。
ルーレット的な回転スロー
続いて値を大きくしてみます。
ルーレット的な回転高速
このように早く回転するようになりました。

以上簡単ではありますがUnityでゲームオブジェクトを回転させる方法の紹介でした。
ゲームオブジェクトの移動とは若干異なりますが、慣れるとそんなに難しくないと思います。
理論を突き詰めると大変そうですが。。

この内容をGitHubに公開しておきますのでよかったら参考にしてみてください。
GitHub GameObjectRotate

関連記事

最後までご覧頂いてありがとうございました。