Unityで振り子運動を実装する

はじめまして、コンテンツ開発事業部の陳と申します。 普段はUnityを用いてゲームアプリの開発を行っています。 今回は直近のプロジェクトで振り子運動を実装した時の話を書こうと思います。

この記事を書こうとした経緯

開発中のゲームアプリに、振り子運動の要素がありますので、 いくつかの実装方法を試しました。 機能実装するときに、機能を完成させるだけではなく、 他にも色々なことを考えられると実感しましたので、 この記事を書こうと思いました。

実装方法1:アニメーション

最初はUnityのアニメーション機能を使って実装しました。 回転中心となるゲームオブジェクトにアニメーションで回転させることで、振り子のように動きました。

この方法は一番簡単で、短時間で実装できるメリットがあります。 しかしながら、動きは実際の振り子と違って、少し不自然な感じがします。 そして、今回のプロジェクトには振り子の速度を取得することも必要ですが、 この方法では簡単に速度を取得できません。

実装方法2:Hinge Jointを利用

Unityの物理エンジンにHinge Jointという要素があります。 一つのゲームオブジェクトを中心として、もう一つのゲームオブジェクトと繋ぐことができます。 振り子運動を実装するに最適だと思いましたので、 実際に実装してみます。 振り子のオブジェクトにHingeJoint2D要素を追加、 中心のゲームオブジェクトにRigidbodyも追加して、 HingeJoint2Dに回転中心を指定します。

自然な振り子運動ができました。 しかし、時間が経つと、段々振り子の運動角度が少しずつ減ってしまいました。 そして、振り子の速度も簡単に取得できません。

実装方法3:振り子運動の方程式をスクリプトに書き込む

Unity の Hinge Joint で実装した振り子が段々動かなくなる原因を調べてみたら、 この記事を見つかりました、 簡単にまとめると、 Hinge Joint で実装すると、運動エネルギーが少しずつなくなってしまいます。 正確に振り子運動をシミュレートするためには、物理エンジンを使わず、 実際の振り子運動の数式で加速度、速度を計算する必要があります。 振り子に影響を与える力(重力と張力)を設定し、 フレイム毎の計算結果で位置を更新する方法です。 実際に計算していますので、速度の取得が簡単です。 問題は数式が少し複雑で、実際に組み込みましたが、 思った通りに動かなくて、どこが間違っているのも分かりませんでした。 色々試してみましたが、解決できませんでした。

実装方法4:回転の方法で数式を変えてみた

方法3の問題はどうしても解決出来ませんでしたので、 また他の振り子の実装方法を考えました。 たどり着いたのは回転です。 これまでは直接X,Y座標を求めていましたが、 考え方を変えて、回転で座標を求めました。 中心座標と角度と距離で振り子の座標も計算できます。 実際に振り子の運動も回転の運動なので、 振り子の角度改変で実装してみました。

[SerializeField] Transform pivot; //回転中心
[SerializeField] Transform bob; //振り子
/* 変数宣告省略 */
public Vector3 GetCurrentVelocity()
{
    float r = Vector2.Distance(pivot.position, bob.position);
    Vector2 dir = bob.position - pivot.position;
    Vector2 velocityDir = new Vector2(-dir.y, dir.x);
    velocityDir.Normalize();
    return (r * angularVelocity * velocityDir);
}

void FixedUpdate()
{
    angularVelocity += angularAcceleration * Time.deltaTime;
    bob.RotateAround(pivot.position, Vector3.forward, angularVelocity);
    if (bob.position.x > pivot.position.x){
        angularAcceleration = -angularAccelerationValue;
    } else {
        angularAcceleration = angularAccelerationValue;
    }
}

実際の振り子運動の角度θと時間t の関係は θ = A * cos(t)、 角加速度は -cos 関数の形ですが、 今回は角加速度を固定の数値にしました。 速度の取得は角速度と回転の半径で計算する。 導入してみます。

自然の動きができて、速度の取得もできますので、この方法を採用しました。

終わりに

今回は振り子運動を実装するため、色々な方法を試しました。 一つの機能の実現にあたっては、一つの実装方法だけではありません。 場合により、最適の方法も違うでしょう。 実装方法を柔軟に変えることも重要と感じました。

tecotec.co.jp