今回はRigidbodyのAddForceメソッドで加える力と力のオプションについてみていきたいと思います。
大砲から弾を発射させる時にRigidbodyのAddForceメソッドを使ってなんとなく力を加えて弾を飛ばしていました。
ですが物理の計算を使って適した力を加えたい!という場合もあります。
そんな時の為に、高校の物理の授業で習う運動方程式から与える力を計算してみます。
サンプル用オブジェクトの作成
まずはサンプル用のオブジェクトを作成します。
ヒエラルキー上で右クリックから3D Object→Sphereを選択し、球を作成します。
次にSphereゲームオブジェクトを選択し、インスペクタのAdd ComponentからPhysics→Rigidbodyを取り付けます。
Rigidbodyの設定でMass(質量)を50にします。
重力は働かせないのでUse Gravityのチェックを外します。
サンプルの概要
サンプルのゲームオブジェクトに力を加えてX軸の方向に等速度で移動させるようにします。
AddForceの第1引数では加える力を指定しますが、第2引数ではオプションを指定する必要があります。
加える力は運動方程式F(力) = m(質量) × a(加速度)で計算出来るので、この式を使って加える力を計算します。
力の単位はN(ニュートン)
質量はkg(RigidbodyのMassに与える数値)
加速度は$$m/s^2$$
となります。
第2引数のオプションで指定出来るのは、
ForceMode.Force – 質量を使用してRigidbodyに継続的に力を加える
ForceMode.Impulse – 質量を使用してRigidbodyに即時に力を加える
ForceMode.Acceleration – 質量を無視してRigidbodyに継続的に加速を加える
ForceMode.VelocityChange – 質量を無視してRigidbodyに即時に速度変化を加える
の4つを指定出来ます。
指定したオプションによって第1引数で指定した力の意味が少し変わってきます。
今回のサンプルではどのオプションで指定したとしてもゲームオブジェクトを等速度(同じ速度)で移動するようにするので、第1引数の値を変更します。
力を加えるスクリプトの作成
Assetsフォルダ内で右クリックからCreate→C# Scriptを選択し、名前をRigidAddForceTestという名前にします。
SphereゲームオブジェクトにRigidAddForceTestスクリプトを取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RigidAddForceTest : MonoBehaviour { // 速さ[m/s] [SerializeField] private float speed = 10f; private new Rigidbody rigidbody; // Start is called before the first frame update void Start() { // 自身に取り付けたRigidbodyの取得 rigidbody = GetComponent<Rigidbody>(); } // Update is called once per frame void Update() { // Rigidbodyの速度を出力 Debug.Log(rigidbody.velocity); } private void FixedUpdate() { } } |
speedは移動する速さを設定します。
rigidbodyはRigidbodyコンポーネントの参照を入れますが、名前のrigidbodyは親クラスで既に定義されていて、親クラスのrigidbodyと混同する恐れがあるのでRigidbodyの前にnewを付けています。
newを付けずにrigidbodyという名前を別の名前にしてもいいです。
StartメソッドではRigidbodyコンポーネントを自身のSphereゲームオブジェクトから取得し、その参照をrigidbodyに入れます。
UpdateメソッドではRigidbodyで移動している速度を確認する為、Debug.Logメソッドを使ってRigidbodyのvelocity(速度)の値をコンソールに出力しています。
このスクリプトに処理を追加しています。
ForceMode.Forceを指定した時
まずはRigidbodyのAddForceの第2引数でForceMode.Forceを指定して力を加えた時に、等速度でSphereが移動する処理を作成します。
第2引数に何も指定しない場合もForceMode.Forceになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RigidAddForceTest : MonoBehaviour { // 速さ[m/s] [SerializeField] private float speed = 10f; private new Rigidbody rigidbody; // Start is called before the first frame update void Start() { // 自身に取り付けたRigidbodyの取得 rigidbody = GetComponent<Rigidbody>(); // 同じ速度で移動させる // 運動方程式F=ma(Fは力、mは質量、aは加速度)より加える力を計算 // ForceMode.Forceで与える力は質量*加速度なので質量*速度/時間を第1引数に与える rigidbody.AddForce(rigidbody.mass * Vector3.right * speed / Time.fixedDeltaTime, ForceMode.Force); } // Update is called once per frame void Update() { // Rigidbodyの速度を出力 Debug.Log(rigidbody.velocity); } private void FixedUpdate() { } } |
StartメソッドにRigidbodyのAddForceメソッドを使って力を加えています。
第2引数に注目するとForceMode.Forceを指定しているので、第1引数で与えた値は
質量 × 速度 ÷ 時間(または質量×加速度)
となります。
rigidbody.massが質量
Vector3.right(ワールドのX軸の方向)×速さで速度
Time.fixedDeltaTimeが時間
となります。
Rigidbody等の物理的な動作は固定フレームレートで行われるので、時間は固定フレームレートの時間であるTime.fixedDeltaTimeを指定しています。
実行してみると以下のように10m/sの速さでX軸方向にSphereゲームオブジェクトが移動します。
注意しなければいけないのは、AddForceメソッドは一度力を加えたら他のゲームオブジェクトから摩擦や外部の力(他のオブジェクトと衝突したり、重力によって落下)を受けたり、自身のRigidbodyのDrag(空気抵抗)等の数値を上げない限りずっとそのまま動く(X軸に10m/sの速さで動く)という事です。
つまり今回の場合はRigidbodyに力を加えているのはStartメソッドなのでゲームオブジェクトが登場した時に1回だけ力を加えていますが、その後ずっと動きます。
なので、UpdateメソッドやFixedUpdateメソッド内でRigidbodyに毎回力を加える必要はありません(こちらの場合X軸に加速して移動してしまいます)。
Updateメソッドはフレームレートが変動しますが、FixedUpdateメソッドは固定フレームレートなので指定した時間毎に実行されます(実行する処理が重すぎなければ)。なので、物理的な処理をしたい場合はFixedUpdateメソッド内に記述するといいです。
例えば以下のようにFixedUpdateメソッドで力を加える場合は1回力を加えたらそれ以降は力を加えないようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RigidAddForceTest : MonoBehaviour { // 速さ[m/s] [SerializeField] private float speed = 10f; private new Rigidbody rigidbody; // 1回だけ力を加える private bool already; // Start is called before the first frame update void Start() { // 自身に取り付けたRigidbodyの取得 rigidbody = GetComponent<Rigidbody>(); } // Update is called once per frame void Update() { // Rigidbodyの速度を出力 Debug.Log(rigidbody.velocity); } private void FixedUpdate() { if (!already) { return; } rigidbody.AddForce(rigidbody.mass * Vector3.right * speed / Time.fixedDeltaTime, ForceMode.Force); already = true; } } |
FixedUpdateメソッドで毎回力を加える場合は、加える力自体を調整することで等速度運動にすることもできます。
それについてはAddForceを使ったキャラクター操作スクリプトでやる予定です。
ForceMode.Impulseを指定した時
次はForceMode.Impulseをオプションに指定した時です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RigidAddForceTest : MonoBehaviour { // 速さ[m/s] [SerializeField] private float speed = 10f; private new Rigidbody rigidbody; // Start is called before the first frame update void Start() { // 自身に取り付けたRigidbodyの取得 rigidbody = GetComponent<Rigidbody>(); // ForceMode.Impulseで与える力は質量*速度になります。Impulseは即時に力が加わる爆発等の時に使います。 rigidbody.AddForce(rigidbody.mass * Vector3.right * speed, ForceMode.Impulse); } // Update is called once per frame void Update() { // Rigidbodyの速度を出力 Debug.Log(rigidbody.velocity); } } |
ForceMode.Impulseをオプションで指定した場合に加える力は
質量 × 速度(または質量×距離÷時間)
になります。
瞬時の力を表す運動量mv(質量×速度)を与えているんですかね。
なので、ForceMode.ForceでTime.fixedDeltaTimeで割っていた部分をなくした値を第1引数で与えています。
こちらを実行するとForceMode.Forceの時と同じように10m/sの速さでX軸方向にSphereが移動します。
ForceMode.Accelerationを使った時
次はForceMode.Accelerationを使った時です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RigidAddForceTest : MonoBehaviour { // 速さ[m/s] [SerializeField] private float speed = 10f; private new Rigidbody rigidbody; // Start is called before the first frame update void Start() { // 自身に取り付けたRigidbodyの取得 rigidbody = GetComponent<Rigidbody>(); // ForceMode.Accelerationは質量を無視してRigidbodyに継続して力を加えます。 rigidbody.AddForce(Vector3.right * speed / Time.fixedDeltaTime, ForceMode.Acceleration); } // Update is called once per frame void Update() { // Rigidbodyの速度を出力 Debug.Log(rigidbody.velocity); } } |
ForceMode.Accelerationの場合に与える力は質量を無視するので、
速度 ÷ 時間(または距離÷時間²)
となります。
これはForceMode.Forceで質量を掛けていないのと同じです。
ForceMode.VelocityChangeを使った時
ForceMode.VelocityChangeを使った時です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RigidAddForceTest : MonoBehaviour { // 速さ[m/s] [SerializeField] private float speed = 10f; private new Rigidbody rigidbody; // Start is called before the first frame update void Start() { // 自身に取り付けたRigidbodyの取得 rigidbody = GetComponent<Rigidbody>(); // ForceMode.VelocityChangeは質量を無視して即時に力を加えます。 rigidbody.AddForce(Vector3.right * speed, ForceMode.VelocityChange); } // Update is called once per frame void Update() { // Rigidbodyの速度を出力 Debug.Log(rigidbody.velocity); } } |
ForceMode.VelocityChangeを使った時の力は
速度
です。
これはForceMode.Impulseの時に掛けていた質量を取り除いた形になっています。
ジャンプ機能を作ってみる
RigidbodyのAddForceを使ってSphereのジャンプ機能を作ってみます。
ヒエラルキー上にPlane等を使って床を作成しておきます。
またスクリプトを取り付けるゲームオブジェクトにはRigidbodyコンポーネントを取り付け、Massに50を設定し、Use Gravityにチェックを入れて重力を働かせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class RigidAddForceTest2 : MonoBehaviour { // 速さ[m/s] [SerializeField] private float speed = 10f; private new Rigidbody rigidbody; // ジャンプトリガー private bool jumpTrigger; // 接地しているかどうか private bool isGrounded; // Start is called before the first frame update void Start() { // 自身に取り付けたRigidbodyの取得 rigidbody = GetComponent<Rigidbody>(); } // Update is called once per frame void Update() { // Rigidbodyの速度を出力 Debug.Log(rigidbody.velocity); // 接地していなければ以降の処理をしない if (!isGrounded) { return; } // Jumpボタンを押したらジャンプ if (Input.GetButtonDown("Jump")) { jumpTrigger = true; isGrounded = false; } } private void OnCollisionEnter(Collision collision) { isGrounded = true; } private void FixedUpdate() { // ジャンプがトリガーされていれば上向きの力を加える if (jumpTrigger) { rigidbody.AddForce(rigidbody.mass * Vector3.up * speed / Time.fixedDeltaTime, ForceMode.Force); //rigidbody.AddForce(rigidbody.mass * Vector3.up * speed, ForceMode.Impulse); //rigidbody.AddForce(Vector3.up * speed / Time.fixedDeltaTime, ForceMode.Acceleration); //rigidbody.AddForce(Vector3.up * speed, ForceMode.VelocityChange); jumpTrigger = false; } } } |
このスクリプトはジャンプと着地をするだけの機能です。
jumpTriggerはジャンプボタンを押したかどうかです。
isGroundedが接地しているかどうかです。
Updateメソッド内で接地していない時はreturnを使ってそれ以降の処理をしないようにします。
Jumpボタンを押した時にjumpTriggerをtrueにし、isGroundedをfalseにします。
OnCollisionEnterメソッドはコライダが他のコライダと衝突したら呼ばれます。
呼ばれたらとりあえず着地したことにしてisGroundedをtrueにします。
これはだいぶ簡略化した接地判定で、空中に他のゲームオブジェクトがあって衝突したとしても着地と判断してしまいます。
Rigidbodyに力を加える処理はFixedUpdateメソッド内に記述した方がいいのでそこに処理を追加します。
jumpTriggerがtrueの時にVector3.upで上向きの方向を取得し、それに速さを掛ける事で上向きの速度を求めています。
Rigidbodyに力を加えた場合は他の力を受けたり空気抵抗等がないとそのまま動き続けてしまいますが、今回の場合はRigidbodyコンポーネントのUse Gravityにチェックを入れているので重力が下向きにかかります。
つまり、上向きにジャンプしても重力でどんどんRigidbodyのvelocityの値が減っていく事になり、上向きに移動して最高点に達したら下向きに落下するという動きになります。
力を加えるのはジャンプした時で、ジャンプが出来るのは接地している時だけなので、その時のRigidbodyの上向きのvelocityはほぼ0です。
なのでその時に上向きに力を加えても元のvelocityはほぼ0なので加速度的に上向きの力が加わって移動するということはありません。
終わりに
今まではRigidbodyのAddForceに適当な力を加えていましたが、場合によっては正確な値を加えたい事もあると思ってこの記事を書きました。
キャラクターの移動処理を作るとした場合、
RigidbodyのAddForceを使って作れば物理の影響を受けるキャラクターが作れそうですが、一度力が加わるとそれに抵抗する力がない限り止まらないので、移動キーを押した時に速度を計算し、FixedUpdate内でRigidbodyのAddForceメソッドを使って力を加えて移動させると、どんどん加速度的に移動してしまいます。
なので、加える力を変更して一定速度で移動するようにしなければいけません。
他のやり方として、RigidbodyのMovePositionを使った場合はtransform.position = newPositionのように位置をワープしている処理になります(IsKinematicにチェックが入っていないと補間が働かない、ただIs Kinematicにチェックが入っている場合はコライダが衝突しない(^_^;))。
なので移動元と移動先の間は何もしないかもしれません。
他には、Rigidbodyのvelocityを直接操作すれば移動処理は作れますが、velocityを直接操作すると非現実的な物理シミュレーションになる為、これもワープをしている感じになります(物理特性を使っていない)。
Rigidbodyの物理特性を使ったキャラクター移動処理を作ろうと思うと結構大変です。(^_^;)
これについてはまた別の記事でやりたいと思います。