ボールの処理は自分で実装すると大変だが、リジッドボディ(Rigidbody)とコライダー(Collider)の設定を正しく行うと、初速を与えてあげればあとは勝手に動いてくれる。【Unity】の醍醐味の一つである。それではボールを動かしてみよう。
ボールにリジッドボディを設定
ボールのプレハブにリジッドボディ(Rigidbody)のコンポーネント(Component)を追加する。
- 【プロジェクト(Project)】ビューから「Prefabs」フォルダ内の「Ball」プレハブを選択する。
- メニューから【Component】→【Physics】→【Rigidbody】を選択。
リジッドボディを設定したら、インスペクター(Inspector)から以下のように設定しよう。
- Mass
- ボールの重量。とりあえず1のままにしておく。
- Drag
- 空気抵抗。不要なので0のまま。
- Angular Drag
- 回転時の空気抵抗。不要なので0にする。
- Use Gravity
- 重力落下させないので、チェックを外す。
- Is Kinematic
- 物理法則を適用するのでチェックを外したまま。
- Interpolate
- Noneのまま。
- Collision Detection
- Discreteのまま。
- Constraints
- Z軸は使わないので、【Freeze Position】【Freeze Rotation】両方の【Z】にチェックを入れる。
ボールを発射
まだ細かい設定が残っているが、一先ずボールを発射させてみよう。ボール用のスクリプトを作成して、shotBall()というボールを発射させる関数を作る。発射する方向と速度はインスペクターで設定できるようにする。
スクリプトを作成しよう。
- 【プロジェクト(Project)】ビューの「Scripts」フォルダを右クリックして、【Create】→【C# Script】を選ぶ。
- 作成したスクリプトのファイル名を「CBall」にする。
- 「CBall」スクリプトをメモ帳で開いて、文字エンコードを「Unicode big endian」に変更しよう。操作方法は(4)の記事を参照。
- 「CBall」スクリプトを開いて、クラス名が「CBall」になっているかを確認する。「NewBehaviourScript」のままだったら「CBall」に修正する。
グローバル変数を作成しよう。
- 「public class CBall : MonoBehaviour {」という行を探して、その下の行に以下のプログラムを追加する。
public float INIT_DEGREE = 75f; public float INIT_SPEED = 250f;
ボールを発射するshotBall()という関数を作成する。
- 「CBall」スクリプトの最後の「}」を探して、その上の行に以下のプログラムを追加する。
/** ボールを発射する*/ void shotBall() { Vector3 vel = Vector3.zero; vel.x = INIT_SPEED*Mathf.Cos(INIT_DEGREE*Mathf.PI/180f); vel.y = INIT_SPEED*Mathf.Sin(INIT_DEGREE*Mathf.PI/180f); rigidbody.velocity = vel; }
とりあえずゲームの開始と同時にボールを発射させる。
- 「void Start() {」という行を探し、その下の行に以下のプログラムを追加する。
shotBall();
以上でスクリプトは出来上がり。[Ctrl]キー+[S]キーで保存する。
作成したスクリプトを、ボールプレハブに追加しよう。
- 【Unity】に戻る。
- 【プロジェクト(Project)】ビューの「Scripts」フォルダ内の「CBall」をドラッグして、「Prefabs」フォルダ内の「Ball」にドロップする。
以上ができたら、実行して動きをみてみよう。
リジッドボディとコライダーを調整する
ボールは打ち出せたが、ブロックにぶつかっても跳ね返ってくれない。これはボールやブロックの「弾性係数」が0で、粘土のように跳ね返らないオブジェクトになっているからである。ぶつかった速度で跳ね返らせるには、「弾性係数」を1にする必要がある。
このような衝突時の挙動を決めるパラメータは「Physic Material」というアセット(Asset)で設定する。今回はすべてのオブジェクトが同じ衝突の性質をもつので、共通の一つの「Physic Material」を作成して使いまわすことにしよう。
ボールとブロックの弾性係数を設定する
- 【プロジェクト(Project)】ビューの余白をクリックして、何も選ばれていない状態にする。
- メニューから【Assets】→【Create】→【Physic Material】を選択する。
- 作成した「New Physic Material」の名前を「PhysicMaterials」に変更する。
それではパラメータを設定しよう。
- インスペクターで弾性係数を表す【Bounciness】を1にする。
これで100%跳ね返る属性が作れた。これをボールとブロックのプレハブに設定する。
- 【プロジェクト(Project)】ビューから「PhysicMaterials」をドラッグして、【プロジェクト(Project)】ビューの「Prefabs」フォルダ内の「Ball」と「Block1」と「Block2」にドロップしよう。
設定が完了したら動きを確認してみよう。ブロックに跳ね返るのが確認できるだろう。
プレイヤーバーの設定を行う
プレイヤーバーを操作してボールを受け止めようとするとプレイヤーバーにくっついてしまう。プレイヤーバーでも跳ね返るように、プレイヤーバーにも「PhysicMaterials」をドラッグ&ドロップする。設定が出来たら動作を確認しよう。
ちゃんと設定したはずなのだが、変化が見られない。これは、プレイヤーバーもリジッドボディ(Rigidbody)のため、ボールと同じ質量(Mass)のプレイヤーバーに運動エネルギーが移ってしまうためである。そこで、ボールが周りの物体に力を与えられないようにボールの重さを0にしよう。
- 【プロジェクト(Project)】ビューの「Prefabs」フォルダ内の「Ball」を選択する。
- インスペクター(Inspector)から「Rigidbody」コンポーネントの「Mass」を見つけて「0」を設定する。
- 以下のように「1e-07」となればよい。
実行して、プレイヤーバーで跳ね返してみよう。今度はうまく跳ね返るだろう。
以上の設定をしても跳ね返り方がおかしい場合、「PhysicMaterials」の「Dynamic Friction(動摩擦係数)」と「Static Friction(静摩擦係数)」を「0」にする。Unity3.4.2f2だとこの設定が必要になる。
あとは、壁にぶつかった時にボールの速度が遅くなったりするので、「FrameL」「FrameR」「FrameT」にも「PhysicMaterials」を設定しよう。
以上でボールの動きの実装完了である。プレイヤーバーの角にボールをぶつけたりしてみよう。物理演算が行われているので、それらしい挙動をするはずである。
スペースキーでボールを発射
ボールの発射と移動が組み込めたので、当初の予定通り、最初はボールはプレイヤーバーにくっついて動いて、スペースキーを押したら発射するようにしよう。
まずは、ゲームの開始と同時にボールを発射していた処理を消す。
- 「CBall」スクリプトを開く。
- 「Start()」関数に書いた「shotBall();」という行を削除する。
- 上書き保存する。
プレイヤーがボールを発射するようにする。
- 「CPlayer」スクリプトを開く。
- 「void Update() {」という行を探して、その下の行に以下のプログラムを追加する。
// ボールの発射 if (insBall != null) { if (Input.GetButton("Jump")) { insBall.SendMessage("shotBall"); insBall = null; } }
プログラムの意味は以下の通り。
if (insBall != null) {
- プレイヤーバーにボールがくっついている時は「insBall」にはボールのインスタンスが入っているので、null以外になる。このチェックをすることで、プレイヤーバーにボールがくっついているかを判定していることになる。
if (Input.GetButton("Jump")) {
- 【Jump】ボタンが押されているかを判定するif文。
- 【Jump】はキーボードのスペースキーに割り当てられているので、スペースキーを押した時にif文が成立することになる。
insBall.SendMessage("shotBall");
- 「SendMessage()」文は【Unity】独特の命令である。
- 「insBall」には、ボールプレハブのインスタンス(実体)が入っている。
- ボールプレハブには「CBall」スクリプトが登録されている。
- 「SendMessage()」は指定のゲームオブジェクト(今回でいうとボール)にくっついているスクリプト内の関数を呼び出す命令である。
- よって、上記のプログラムで「Ball」が持っている「CBall」スクリプト内の「shotBall()」関数を呼び出すことが出来る。
insBall = null;
- ボールを発射した後に、スペースキーが押されても再度ボールを発射しないようにするための処理。
動かして動作を確認してみよう。スペースキーを押すまでボールが留まっているようになった。しかし、ボールがくっついている状態でプレイヤーバーを左右に操作しても、ボールがちゃんとくっついてこない。
ボールはプレイヤーバーの子オブジェクトになっているのだが、リジッドボディが有効になっているため、プレイヤーバーが動いても外部から力が加えられなければ動かないのである。ボールにくっついている時は物理法則を無効にする【Is Kinematic】をtrueにすることで、素直にくっついて動くようになる。
- 【CPlayer】スクリプトを開く。
- 「void createHoldBall() {」から始まる行を探して、「}」で閉じられている行の直前に以下のプログラムを追加する。
// ボールの物理処理を無効にする insBall.rigidbody.isKinematic = true;
ボールを発射した後に「isKinematic」を外す。
- 「void Update() {」から始まる行を探して、その中の「if (Input.GetButton("Jump")) {」という行を探す。
- 見つけた次の行に、以下のプログラムを追加する。
insBall.rigidbody.isKinematic = false;
- 上書き保存して【Unity】に戻る。
動かして、ちゃんとボールがプレイヤーバーにくっついて動くのを確認しよう。
以上で、ボールを跳ね返す処理の実装は完了である。以下にこの時点までの「CBall.cs」と「CPlayer.cs」を示す。非常に短い上に、ボールが動き回るプログラムが一切書かれていないのが衝撃的である。
CBall.cs
using UnityEngine; using System.Collections; public class CBall : MonoBehaviour { public float INIT_DEGREE = 75f; public float INIT_SPEED = 250f; // Use this for initialization void Start () { //shotBall(); } // Update is called once per frame void Update () { } /** ボールを発射する*/ void shotBall() { Vector3 vel = Vector3.zero; vel.x = INIT_SPEED*Mathf.Cos(INIT_DEGREE*Mathf.PI/180f); vel.y = INIT_SPEED*Mathf.Sin(INIT_DEGREE*Mathf.PI/180f); rigidbody.velocity = vel; } }
CPlayer.cs
using UnityEngine; using System.Collections; public class CPlayer : MonoBehaviour { public float VEL = 400f; public GameObject prefBall = null; GameObject insBall = null; // Use this for initialization void Start () { createHoldBall(); } // Update is called once per frame void Update () { Vector3 npos = transform.position; npos.x = npos.x+VEL*Time.deltaTime*Input.GetAxis("Horizontal"); rigidbody.MovePosition(npos); // ボールの発射 if (insBall != null) { if (Input.GetButton("Jump")) { insBall.rigidbody.isKinematic = false; insBall.SendMessage("shotBall"); insBall = null; } } } // ボールを生成して、プレイヤーバーにくっつける void createHoldBall() { Vector3 bpos = transform.position; bpos.y += (collider.bounds.size.y+prefBall.collider.bounds.size.y)/2f; insBall = (GameObject)Instantiate(prefBall,bpos,Quaternion.identity); insBall.transform.parent = transform; // ボールの物理処理を無効にする insBall.rigidbody.isKinematic = true; } }