おもちゃラボ

Unityで遊びを作ってます

【Unity】Physicsを使わずに平面でボールを反射させる

Unityで物理挙動に従って物体を動かしたい場合にはPhysicsを使うのが便利です。ただ、物理挙動に従わない動きや、Physicsが使えない場合には自分で物理挙動を計算する必要があります。

この記事では、平面とボールの衝突と反射を計算する方法を紹介します。「Unityの教科書」にも書いたように、物理挙動を実現するには

  1. 衝突判定
  2. 衝突応答

の2つを計算する必要があります。

ここで、衝突判定は平面とボールが当たったかどうか、衝突応答はボールの跳ね返る向きの計算になりますね。まずは衝突判定の部分から考えていきましょう。

f:id:nn_hokuson:20180330201116p:plain:w500

平面とボールの当たり判定

平面とボールの当たり判定はそんなに複雑ではありません。ボールの中心から平面に向けておろした垂線hの長さが、ボールの半径以上あれば衝突、半径未満なら衝突していないとみなします。

f:id:nn_hokuson:20180330194724p:plain:w300

垂線の長さhは、法線nの長さが1であることを利用して、dとnの内積を計算することで求められます。

{ \displaystyle
d \cdot n = |d||n|cos \theta = |d|cos \theta  = h
}

いきなり数学が出てきましたが、ゲーム作りにおいて数学は超大切です。ベクトルとか三角関数が苦手!って方は次の書籍がものすごく分かりやすいのでオススメです(高校参考書ですが。笑)

[asin:4098374021:detail]

これで垂線hの長さが求められたので、ボールの半径と比べて衝突判定ができそうですね。ただ、ここでは簡略化のため、無限遠の平面を考えています。ボールが平面からはみ出ていた場合の処理については省略しています。

ボールの反射方向の計算

平面とボールの当たり判定ができたので、第二の難関(というほどでもない)の衝突応答を作っていきましょう。

平面とボールの衝突応答は比較的簡単です。平面に当たったとき、ボールは平面に対して逆側に反射します。この方向を反射ベクトルと呼びます。この反射ベクトルを求めるには次の図のベクトルrを求めればよさそうですね。

f:id:nn_hokuson:20180330194954p:plain:w300

反射ベクトルrの求め方は少しトリッキィというか、コロンブスの卵というか、面白い方法で計算できます。衝突地点から移動ベクトルをそのまま伸ばして、ボールと平面の距離ぶん法線ベクトルを2回足してやると、なんと反射ベクトルと同じところにたどり着きますね。

f:id:nn_hokuson:20180330195312p:plain:w300

ベクトルでは経路が違っていても、スタート地点とゴール地点が同じなら、同じベクトルとみなすことが出来ます。つまり上の図で、遠回りしている緑の線(dir + 2*a)と、求めたい反射ベクトル(r)は同じベクトルになるのです。この分かりやすさは結構好きです(笑)

ここで、{ \displaystyle a=(dir \cdot n) * n} なので、まとめると反射ベクトルは次のように計算できます。

{ \displaystyle r = dir + 2 * a = dir + 2 * (dir \cdot n) * n}

実際に平面とボールで反射するスクリプトを作る

さて、反射ベクトルの計算方法もわかったので実際のプログラムを作っていきましょう。まずはシーンビューに平面とボールを配置してください。

f:id:nn_hokuson:20180330200124j:plain:w600

次にBallにアタッチするプログラムを作成して、次のスクリプトを入力してください。

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

public class Ball : MonoBehaviour 
{
    public GameObject plane;
    Vector3 moveDir;    // 移動方向ベクトル
    float speed = 0.2f; // 移動速度

    void Start () 
    {
        moveDir = new Vector3(1, -2, 1).normalized * speed;
    }
		
    void Update () 
    {
        // 移動
        transform.position += moveDir;

        // ボールと平面の距離
        Vector3 d = transform.position - plane.transform.position;
        float h = Vector3.Dot(d, plane.transform.up);

        // 当たり判定
        if( h < transform.localScale.x){
            Collision();
        } 
    }

    void Collision()
    {
    	// 反射ベクトルを計算する
        Vector3 n = plane.transform.up;
        float   h = Mathf.Abs(Vector3.Dot(moveDir, n));
        Vector3 r = moveDir + 2 * n * h;
        moveDir = r;
    }
}

Updateメソッドの中で毎フレーム平面とボールの距離hを計算し、衝突しているかどうかをチェックしています。Collisionメソッドの中では、反射ベクトルを計算するため、ボールの移動ベクトル(moveDir)と平面の法線ベクトル(transform.up)を使って計算しています。

スクリプトが作成できたら、ヒエラルキーウインドウのSphereにアタッチしてください。また、インスペクタからBallスクリプトのPlane欄にPlaneをドラッグ&ドロップしてください。

f:id:nn_hokuson:20180330200515j:plain

作成したスクリプトをアタッチできたら、Unityを実行してみましょう。次図のようにちゃんと平面で跳ね返りましたね!

f:id:nn_hokuson:20180330200541g:plain

複数の平面で当たり判定をしたい場合には、全ての平面に対してボールと衝突判定を行う必要があります。つまり平面の数が増えればそれだけ衝突判定の回数も増えるわけです。

f:id:nn_hokuson:20180330200556g:plain

平面だけでなくボールの数も増えると、その組み合わせ数はすぐに爆発します。ボールN個と平面M個で単純な組合せはN*M個ですから・・・

このままではリアルタイムでの計算はできない、高速化するための様々なアルゴリズムが考えられています。有名なものにはオクトツリーを用いる方法があります。ここでは説明しませんが、次のサイトや参考書で説明されているので、ぜひ読んでみてください

その15 8分木空間分割を最適化する!

【Blender】足を動かして学ぶIK入門

IKって便利そうだけど、設定がなんかヤヤコシイ・・・と感じている方が多いように思います。ボーンにIKの設定をするだけなんですが、ボーンの構造の複雑度に応じてIKの設定も大変になります。

f:id:nn_hokuson:20180320200753j:plain

最初はメッシュとの関連付けなどはせずに、素のボーンだけを使ってIKの設定と動きを理解しましょう!

単純なIKを設定する

まずは簡単なボーンの構造を使ってIKの理解を深めていきましょう。

オブジェクトモードでShift+Aを押してメニューを表示し、アーマチュア→単一のボーンを選択してください。ボーンが1つ追加されます。

f:id:nn_hokuson:20180320192813j:plain:w500

オブジェクトモードから編集モードに変更し、gキーでの移動とeキーでの押し出しを使って次のようなボーンを作ってください。

f:id:nn_hokuson:20180320193250g:plain:w250

上2つのボーンがそれぞれ太ももボーンとすねボーン、最後の一本がコントロール用のボーンという想定です。想像力大切(笑)

f:id:nn_hokuson:20180320193822j:plain

さて、それではIKを設定していきましょう。流れは次のとおりです。

  1. コントロール用ボーンを切り離す
  2. すねボーンにIKを設定する
  3. IKコンストレイントにコントロールボーンを指定する

まずはコントロール用のボーンをすねボーンから切り離しましょう。編集モードでコントロール用のボーンを選択し、ボーンのパネルから「接続」のチェックを外して、次に親の欄のバツボタンを押してください。「接続」→「親」という順番が大切です。

f:id:nn_hokuson:20180320194105j:plain

次にすねボーンにIKを設定します。「ポーズモード」ですねボーンを選択し、ボーンコンストレイントのパネルからトラッキング→インバースキネマティクス(IK)を選択してください。

f:id:nn_hokuson:20180320194401j:plain

最後にボーンコンストレイントの設定をします。ターゲットにArmature、ボーンにコントロール用ボーン(ここではBone.002)を指定し、チェーンの長さを「2」に設定します。

f:id:nn_hokuson:20180320194449j:plain

チェーンの長さを設定することで、コントロール用のボーンから何個さかのぼってIKの計算をするかを指定することが出来ます。ここではすねボーン(IKボーン)→太ももボーンと2つさかのぼってIKの挙動を計算するのでチェーンの長さを「2」にしています。

動作させると次のような感じになります。

f:id:nn_hokuson:20180320194556g:plain:w250

簡単なIKの設定方法がわかったところで、もう少し複雑なボーンのIKにも挑戦してみましょう。

足先まで含めたIKを考える

上では太ももとすねの2つだけのボーンでIKを設定しました。動物の場合はこれでも良いのですが、人間の場合は足先のボーンまで作らないと不自然な動きになってしまいます。

先程のボーンにつなげる感じで、足先ボーンも作りましょう。すねボーンの先端(コントロールボーンとの接続部分)を選択して、eキーで押し出し、足先ボーンを作ります。

f:id:nn_hokuson:20180320195011g:plain:w250

この状態でコントロールボーンを動かしてみましょう。足を持ち上げると同時に足先が回転してしまっているため、このままでは、非常に使いにくいですね。

f:id:nn_hokuson:20180320195102g:plain:w250

これは足先のボーンがIKを設定したすねのボーンの影響を受けていることが原因です。そこで一旦すねボーンと足先ボーンの接続を切ります。

編集モードで足先ボーンを選択し、ボーンパネルから「接続のチェックを外す」→「親をバツボタンで削除」の順番ですねボーンと切り離してください。ここでも順番が大切です。

f:id:nn_hokuson:20180320195415j:plain

足先ボーンがIKボーンと切り離せたところで、この足先ボーンはコントロール用のボーンの子要素にしましょう。これによりコントロールボーンの動きと同調して動くようになります。今削除した親の欄からコントロールボーン(Bone.002)を選択してください。

f:id:nn_hokuson:20180320195551j:plain

設定できたらポーズモードでコントロールボーンを動かしてみてください。

f:id:nn_hokuson:20180320195632g:plain

足首ボーンが足の上下とは関係なく地面と平行に保たれていますね!また、コントロールボーンを回転することで足首ボーンが回転することも確認しておきましょう。

猫が水の器をひっくり返してこぼすときの対処法

うちには猫が二匹いるんですけど、そのうちの一匹がとてもやんちゃで水の器をすぐにひっくり返そうとして水をこぼすのです。

犯人はこいつです。お千代、生後10ヶ月の♀

f:id:nn_hokuson:20180224121533j:plain:w300

お千代という淑女的な名前とは裏腹に、ものすごく暴れん坊です。名は体を表すなんて嘘じゃないですか、どうなってるんですか。

お千代がこんな感じ(↓)で水の器を頭突きでひっくり返そうとするので、床が水浸しになって困っていました。

f:id:nn_hokuson:20180224113729p:plain:w500

なぜ水の器を押そうとするのか、水をこぼしたいのか、それとも水で遊びたいのか、なんなんでしょうね〜〜!?水をこぼすのをなかなか止めてくれないので、自動給水器とかお盆とかいろいろ試したのですが、この記事では最終的に辿り着いた形態を紹介します!

水がこぼれない水の器

小林製薬ばりの良いネーミングは思いつかなかった。

ただ、ちゃんと機能は果たしてくれます。完成形はこんな感じです。

f:id:nn_hokuson:20180313204604j:plain:w500

必要なものは「猫壱のウォーターボール」「タッパー」「耐震マット」の3点。

f:id:nn_hokuson:20180313204351j:plain:w500

ウォーターボールは猫壱のものではなくても問題ないと思いますが、猫壱のウォーターボールは高さ、重さともに今回の工作にピッタリのサイズでした!持っていない方は是非試してみて下さい!(カワイイですし)

タッパーは猫壱のウォーターボールを使う場合は 15cm ✕ 20 cm ✕ 7cm 程度のものを使用しています。100均とかでも売っているやつでOKです。

では作り方です。まずはカッターでタッパーに穴を開けます。大きさは猫壱のウォーターボールの直径と同じぐらい。

f:id:nn_hokuson:20180313204423j:plain:w500

切り終わったらこんな感じになります。

f:id:nn_hokuson:20180313204458j:plain:w500

次にタッパーの底の4隅に耐震シールを貼り付け・・・るはずが、なぜかお千代に邪魔される(笑)

f:id:nn_hokuson:20180313204525j:plain:w500

なんとか耐震シールを貼り付けて床に設置すれば完成です。水を取り替えたいときは猫壱のウォーターボールだけ取り外して水を変えればOK。

フィールドテスト(ただしn=1)

さて、作った水がこぼれない水の器を設置したところ、早速遊びに来たお千代さん。人間の知能が勝つのか、猫のパワーが勝つのか・・・・

f:id:nn_hokuson:20180313204219j:plain

諦めて退散。うぉぉー!完全勝利です!!ようやくこれでこぼれた水を拭く作業から開放されました。

その他の解決策

自動給水器

プラスアクア、めっちゃオススメです!水の器をおされてひっくり返されることもなくなりました。なんなら、タッパーの工作せずにプラスアクア買ってもいいぐらい(笑)

nn-hokuson.hatenablog.com

しかも、水が流れる部分がちょうど猫の頭ぐらいの高さで飲みやすいのか、頻繁に水を飲むようになりました!尿路結石にならないためにも水はこまめに飲んでほしいので、このプラスアクアは買って損はなかったです!

Braava

お金持ちの人は、こんな工作をしている暇があればiRobotのBraavaを買いましょう。

文句も言わず、こぼれた水を掃除してくれる・・・はず?