質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.33%

ベクトルやAddTorqueに関して。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,466

tkmnusr

score 330

前提・実現したいこと

前回の質問に引き続き、後半部分の質問です。

該当のソースコード

    void FixedUpdate()
    {
        float x = Input.GetAxis("Horizontal");
        float y = Input.GetAxis("Vertical");

        // xとyにspeedを掛ける
        rigidbody.AddForce(x * speed, y * speed, 0);

        Vector3 moveVector = Vector3.zero;
        Debug.Log(moveForceMultiplier * (moveVector - rigidbody.velocity));

        rigidbody.AddForce(moveForceMultiplier * (moveVector - rigidbody.velocity));

        /* 後半部分 これ以降の処理に関して質問です。 */

        // プレイヤーの入力に応じて姿勢をひねろうとするトルク
        Vector3 rotationTorque = new Vector3(-y * pitchTorqueMagnitude, x * yawTorqueMagnitude, -x * rollTorqueMagnitude);

        // 現在の姿勢のずれに比例した大きさで逆方向にひねろうとするトルク
        Vector3 right = transform.right;
        Vector3 up = transform.up;
        Vector3 forward = transform.forward;
        Vector3 restoringTorque = new Vector3(forward.y - up.z, right.z - forward.x, up.x - right.y) * restoringTorqueMagnitude;

        // 機体にトルクを加える
        rigidbody.AddTorque(rotationTorque + restoringTorque);

    }

試したこと

// プレイヤーの入力に応じて姿勢をひねろうとするトルク
Vector3 rotationTorque = new Vector3(-y * pitchTorqueMagnitude, x * yawTorqueMagnitude, -x * rollTorqueMagnitude);


上記コードに関しては理解できました。
実際、下記のコードで試したところ、想定通りの動きをしていたので理解できていると思います。

rigidbody.AddTorque(rotationTorque);

分からないコードは2点です。

・質問1。
下記コードでrotationTorqueの逆回転(?)のベクトルを生成しているみたいですが、
どのような処理になっているかわからないです。
例えば、x成分に入れているのは、「forward.y - up.z」ですが、これはどういった値なのでしょうか?
他、y成分、z成分に関しても同様です。

Vector3 restoringTorque = new Vector3(forward.y - up.z, right.z - forward.x, up.x - right.y) * restoringTorqueMagnitude;

・質問2。
x, yの値に応じて回転させるrotationTorqueベクトルと、その逆回転のrestoringTorqueを同時に与えると、
相殺して無回転になるようなイメージをしてしまうのですが、
ゲーム実行した挙動からすると、rotationTorqueで回転した後、restoringTorqueで逆回転させるというような処理になっているのでしょうか?
rigidbody.AddTorque(A + B);
とすると、A回転した後、B回転するようになるのでしょうか?

rigidbody.AddTorque(rotationTorque + restoringTorque);

追記

    void FixedUpdate()
    {
        float x = Input.GetAxis("Horizontal");
        float y = Input.GetAxis("Vertical");

        // xとyにspeedを掛ける
        rigidbody.AddForce(x * speed, y * speed, 0);

        Vector3 moveVector = Vector3.zero;

        //rigidbody.AddForce(moveForceMultiplier * (moveVector - rigidbody.velocity));

        // プレイヤーの入力に応じて姿勢をひねろうとするトルク
        Vector3 rotationTorque = new Vector3(-y * pitchTorqueMagnitude, x * yawTorqueMagnitude, -x * rollTorqueMagnitude);

        Quaternion target_rot = this.transform.rotation;
        Quaternion rot = target_rot * Quaternion.Inverse(this.transform.rotation);
        if(rot.w < 0f){
            rot.x = -rot.x;
            rot.y = -rot.y;
            rot.z = -rot.z;
            rot.w = -rot.w;
        }
        Vector3 restoringTorque = new Vector3(rot.x, rot.y, rot.z) * 100f;
        // 機体にトルクを加える
        // rigidbody.AddTorque(rotationTorque);
        rigidbody.AddTorque(rotationTorque + restoringTorque);

    }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

順番が前後しますが...

質問2について
今回の2つのトルクベクトルは互いに逆であるわけではないのです(もし本当に逆ならば、おっしゃる通り2つの和がゼロベクトルになってしまい、機体に回転する力はかからなくなるでしょう)。コメントにあるように、rotationTorqueはプレイヤーの入力の方へねじれを追加しようとするトルク、restoringTorqueは機体の現在のねじれを解消して無回転の姿勢に戻そうとするトルクです。

rigidbody.AddTorque(A + B);は「A回転したあとB回転」という風に順序があるわけではなく、「トルクA(大きさがAの長さ、回転軸がAの向き)とトルクB(大きさがBの長さ、回転軸がBの向き)を同時にかける」とでも言うべきでしょうかね...?ですのでrigidbody.AddTorque(B + A);でも結果は同じになるはずです。この辺は普段よくやる「Transformの姿勢をQuaternionで回転」とかとはまた違った感覚でしょうから、いろいろいじってみて慣れていただくのがいいでしょうかね。

回転関連の理屈や物理量については、力学の解説書に載っているようなことが使えるはずです。EMANの物理学・力学・回転に関する物理量とか(「力のモーメント」がトルクに相当します)、EMANの物理学・力学・慣性モーメントテンソルといった記事もご参考になるかと思います。

質問1について
簡略化のためにVector3.rightVector3.upVector3.forwardをそれぞれR、U、Fとし、transform.righttransform.uptransform.forwardをそれぞれr、u、fとし、Vector3.Cross(lhs, rhs)をC(lhs, rhs)と書くことにします。

あの復元トルクの式は、rをRの向きに、uをUの向きに、fをFの向きにねじってやれば元の姿勢に戻るだろう...と思ってやってみたものです。
rをRの向きにねじるトルクの軸はrとRの外積の向きにするのがいいでしょう。大きさはどうするべきかですが、どうやらrとRがなす角度に比例させるとフックの法則に従ったバネらしい挙動になるらしいですね。ですが今回は元の姿勢に戻す作用が得られれば十分かと思い、外積の大きさ(rとRがなす角度のサイン、あるいはrとRが作る平行四辺形の符号付き面積といってもいいでしょう)をそのまま利用することにしました。姿勢のずれはさほど大きくはないでしょうから、こんなやり方でもバネっぽく見せられると思います。
uからU、fからFも同様にして、3つのベクトルを足すと

C(r, R) + C(u, U) + C(f, F)

となり、展開すると

(r.y * R.z - r.z * R.y, r.z * R.x - r.x * R.z, r.x * R.y - r.y * R.x) + (u.y * U.z - u.z * U.y, u.z * U.x - u.x * U.z, u.x * U.y - u.y * U.x) + (f.y * F.z - f.z * F.y, f.z * F.x - f.x * F.z, f.x * F.y - f.y * F.x)

となり、R == (1, 0, 0)U == (0, 1, 0)F == (0, 0, 1)なので大幅に簡略化され

(0, r.z, -r.y) + (-u.z, 0, u.x) + (f.y, -f.x, 0)

となって、各成分を足して一つにすると

(f.y - u.z, r.z - f.x, u.x - r.y)

の形が出てきます。これにrestoringTorqueMagnitudeをかけて大きさを調整したものを使いました。

余談
あの姿勢復元コードの投稿後に検索してみたのですが、【Unite Tokyo 2018】誘導ミサイル完全マスターの講演で紹介されている「現在の姿勢から目標の姿勢への回転を表すQuaternionを得て、そのxyz成分に比例したトルクを加える」という案の方がエレガントでよさそうですね。xyz成分の向きはまさしく回転軸の方を向いていますし、大きさは「角度差÷2」のサインに比例しています。トルクの大きさが角度差0°~180°まで単調増加するので、より一層バネっぽくなりそうです。またQuaternionの計算にUnityの提供する各種メソッドがそのまま利用できるため、コードも簡潔になってわかりやすいでしょう。【Unite 2017 Tokyo】スマートフォンでどこまでできる?3Dゲームをぐりぐり動かすテクニック講座では実演しながら解説されており、非常にご参考になるかと思います。

追記
現状だと機体の現在の回転とそれの逆回転をかけてしまっており、得られるクォータニオンが無回転になっているようです。
後ほどじっくり講演を見直していただきたいのですが、トルク源として用いるべき回転は「現在の姿勢から目標の姿勢への相対回転」となります。現在の姿勢はtransform.rotation、目標姿勢はQuaternion.identityです。ですので、講演の式に当てはめるとQuaternion.identity * Quaternion.Inverse(transform.rotation)ということになりますね。
もっと単純に言うと、無回転からtransform.rotationだけ回転すれば現在の姿勢になるのですから、現在の姿勢から無回転に戻す回転はそれの逆回転...Quaternion.Inverse(transform.rotation)です。

        Quaternion rot = Quaternion.Inverse(transform.rotation);
        if (rot.w < 0f) {
            rot.x = -rot.x;
            rot.y = -rot.y;
            rot.z = -rot.z;
            rot.w = -rot.w;
        }
        Vector3 restoringTorque = new Vector3(rot.x, rot.y, rot.z) * restoringTorqueMagnitude;

        // 機体にトルクを加える
        rigidbody.AddTorque(rotationTorque + restoringTorque);

補足しますと、この方式に書き換えると復元トルクの大きさが小さめに(バネがやわらかく)なりますので、それに応じてrestoringTorqueMagnitudeをもっと大きくしてやるのがいいかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/23 01:05 編集

    ご回答ありがとうございます。
    なるほど、目標姿勢が現在の回転(回転中の回転)になっていたことが原因だったのですね。
    すみません、さらに3点質問です。

    1点目。
    public float restoringTorqueMagnitude = 100000000000000.0f;
    とかなり大きな値まで試したのですが、かなりバネの反発力が弱い挙動に見えるのですが、
    そういった挙動で合っていますか?
    (ご教示いただいた通りのコードを試しているつもりですが、もしかしたら、自分のコードがどこかで間違っているかもしれないので)

    質問2。
    Quaternion.identityが無回転なので、
    下記3つのコードは同等ですか?
    //Aはクォータニオン。
    Quaternion.identity * A //1つめのコード。
    A * Quaternion.identity //2つめのコード。
    A //3つめのコード。
    (これはもしかしたら、以前別質問のときに質問させていただいたことかもしれませんが、メモに書いてなかったので、再質問してしまっているかもしれませんが、すみません。今度は必ずメモに残しておきます。)

    質問3。
    これも当然かもしれない質問で申し訳ないですが、
    「Quaternion.identityは、インスペクタのTransformのRotationで、[X:0, Y:0, Z:0]の状態である」ということで合っていますか?

    キャンセル

  • 2019/07/23 04:43

    1点目については、さすがにそれは妙ですね。そのスクリプトをアタッチしたオブジェクトのインスペクター上の入力欄にも、本当にそのような巨大な値が表示されている状態でしょうか?restoringTorqueMagnitudeはシリアライズ対象になっているはずなので、もしかしてシーンファイルに保存された古い値が残っているだけなのではないかと疑われるのですが...

    2点目についてはおっしゃる通りで、Quaternion.identityを右からかけても左からかけても値はAのまま維持されます。

    3点目も正しいかと思います。まあ念のため申し上げますと、今回のコードではワールド回転を(0, 0, 0)に戻そうとしているのに対し、インスペクター上に(0, 0, 0)を入力するとローカル回転が無回転になるという違いはありますが( https://docs.unity3d.com/ja/current/ScriptReference/Transform-localRotation.html に例示されている最初のコードでは、transform.localRotationにQuaternion.identityをセットすることでローカル回転を無回転に戻しています)...

    キャンセル

  • 2019/07/23 23:07

    ご回答ありがとうございます。
    質問1点目ですが、おっしゃる通り、インスペクタに反映されていませんでした。インスペクタで値を調整したら反動が変わりました。
    シリアライズのフィールドは、コードで値を変更しても、インスペクタの方に反映されず、実際は値が変更されないのですね。勉強になりました。

    質問2点目と3点目、ありがとうございます。
    transform.localRotationにセットすることでインスペクタに反映されることを忘れていました。
    理解できました。

    とても勉強になりました。
    ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.33%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る