こんにちは。
ブロックとブロックがぶつかったらくっつくみたいなゲームを作りたいと思っています。
くっつくというのを実装するために、物理演算でAddForceとか摩擦力とかバネとかで何とかならないかなと実験した結果について書きたいと思います。
結論から言うと、unityの物理法則は何だかハンドリングが悪く、ブロックをくっつける目的では物理を切る方向で考えようと思いました。
詳しくは下記の実験結果をご覧ください!
目次
物理演算を使った静止摩擦力
ブロックには重力がかかっていて、あまりたくさんのブロックをつなげると重量で折れるみたいな事を考えていました。
そこで、まずは静止摩擦力を使ってブロック同士を押し付けた状態でくっつくというのを実装しようとしてみました。
一見うまくいっているように見えるのですが、よく見ると押し付けた力でお互いにめり込んでしまっているのが分かります。
それに加えて、完全に静止せずに重力に引きずられてジリジリと下に動いていきます。
これはどのように作っているかというと、RigidbodyとboxColliderを各オブジェクトに付けてあり、さらに衝突判定用のboxColliderも親オブジェクトに付けています。
まずは衝突判定用のコライダーにぶつかってキューブがキューブの横に移動し、そのあと、そのキューブに別のキューブに向かう力をかけてあげる事で、摩擦力を発生させています。
キューブの衝突判定と、横移動は下記のスクリプトで行っています。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Gravity : MonoBehaviour { OtherBlock otherBlock; private void Awake() { otherBlock = GameObject.Find("originalblock").GetComponent<OtherBlock>(); } private void OnCollisionEnter(Collision other) { if (other.gameObject.CompareTag("block")) { Vector3 pos = this.transform.position; Vector3 otherpos = other.transform.position; if(otherpos.x - pos.x < 0) { pos.x -= 10.0001f; }else { pos.x += 10.0001f; } otherBlock.AddForce(-pos.x); other.transform.position = pos; other.transform.parent = this.transform; } } }
そして otherBlock.AddForce(-pos.x);で呼ばれるメソッド本体は以下のようになっています。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class OtherBlock : MonoBehaviour { Vector3 force; float speed = 0; // Update is called once per frame public void TurnOff() { GetComponent<BoxCollider>().enabled = false; } public void AddForce(float x) { speed = x *100; } private void FixedUpdate() { Rigidbody rb = this.GetComponent<Rigidbody>(); // rigidbodyを取得 Vector3 force = new Vector3(speed, 0.0f, 0.0f); // 力を設定 rb.AddForce(force); // 力を加える } }
これを見ると、Update()で常にフレームごとに力を書ける処理を行っているので、たくさんのキューブができた時に処理が重くなる可能性に気づきました。
ちなみにこの時の静止摩擦力、動摩擦力はMAXにしてあります。
それでも、キューブが完全に静止しないので、何だか気持ち悪く、別の方法を考えることにしました。
物理演算を使ったSpring Joint
そこで、Unity勉強会に相談したところ、ばねを利用してはどうかという助言をプログラム勉強会で頂いたので、Unityでばねを実装できるかどうかを調べてみました。
UnityのSpring Jointを使う
実験1 バネを使ってみる
早速解説しているサイトがあったので真似してやってみました。
使い方としては非常に簡単で、ばねの下になるキューブにSpringJointコンポーネントを付けて、ConnectedBodyに上のキューブを入れるだけです。
もちろん物理法則を使うので、Rigidbodyは必須です。
現在、上のキューブよりかなり下にぶら下がっているのですが、上のキューブにくっついて欲しいと思いました。
これをばね係数Springをいじっても改善されませんで、上の記事をよく読んだところ、AutoConfigureConnected Anchorのチェックを外して、ConnectedAnchorの位置をキューブの底面にしてあげる必要がありました。
デフォルトでは、AnchorとConnected Anchorの位置がほぼ一緒だったのですね。
きちんと底面にアンカーを合わせてあげるとそこを中心にばねが上下します。
しかし、BoxColliderを付けているにもかかわらずボックスがすり抜けてしまいます。
私の意図としてはガツンと当たって止まるというのが理想なのですが。
enable Collisionのチェックボックスがあるので、これにチェックを入れると止まるのかもしれませんね。
実験2 複数のバネコンポーネントを付加してみる
BoxColliderを付けているにもかかわらずボックスがすり抜けてしまうのは、あくまで両方をばねの2点として扱うという仕様で、上のキューブのboxcolliderは無視されるのかなと思いました。
そこで、上のキューブの子オブジェクトとして同じ大きさのキューブを同じ座標に準備し新たにBoxColliderを付けてあげました。
これによって衝突が起きることになります。
そして、キューブの側面にももう1つキューブをばねでつなげてみました。
これで下方向と横方向両方にキューブがくっつくはずです。
一応くっついたんですが、全く安定しないという、、、なかなか難しいですね。
で、あれば、ばねを二カ所に設置してあげればうまくいくかと思い、1つのオブジェクトに二つのSpringJointを付けてあげたのですが、結果は同様で、安定しませんでした。
実験3 子オブジェクトにバネを仕込んでみる
2点間の距離が近すぎて一本のばねと同様に扱われているのかもしれないと思いました。
よって、次に考えられるのは、2点を別々のキューブで作成するということですね。
親オブジェクトを大きなキューブにして、子オブジェクトのばねを付けてあげるというのを考えました。
何故か片方のばねは反応していませんね。
これは色々といじってみましたが、原因不明だったので、新たに動いている方のバネをコピーして作り直しました。
すると、見事にうまいことばねがくっつくようになりました!
これを利用すれば、くっつくブロックができるに違いないと思いました。
実験4 4点拘束:4つのバネを仕込む
そこで、今度は四点拘束にしてみました!
これでピッタリとくっついてくれるに決まっています。
しかし、、、実験してみると、、、
上手くいきそうですが、、、、
どこ行くねん!
何故か推進力が働いているようです。
実験5 4点拘束:4つのバネを仕込んで片方を固定する
推進力が働いてしまっていたので、片方のキューブの場所を固定してみます。
ぶつかった時の跳ね返りがいけないのでしょうか。
なかなか止まってくれません。
Physics Material のBouncinessを0にしてみましたが、結果は同様、、、
物理演算を使った吸着まとめ
Unityの物理演算にはかなり癖があるようです。
必ずしも思ったような結果が得られませんでした。
ブロックをくっつける目的では失敗したので、物理を切る方向でゲームを進めたいと思っています。
それでは、また!