tanaka's Programming Memo

プログラミングについてのメモ。

(5)ボールを動かす

ボールの処理は自分で実装すると大変だが、リジッドボディ(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;
	}	
}