プレイヤーを操作できるようにしてみよう。
リジッドボディの追加
方針に従って、リジッドボディ(Rigidbody)コンポーネントを追加する。コライダーはCubeを作った時点で自動的に追加されているので、追加は不要。
- 【階層(Hierarchy)】ビューから「Player」を選択する。
- メニューから【Component】→【Physics】→【Rigidbody】
リジッドボディ(Rigidbody)を追加したら、インスペクター(Inspector)で属性を設定する。
- Mass
- 物体の重さ。慣性や作用・反作用の影響の計算に使われる。1のままでよい。
- Drag
- 移動する際の空気抵抗。今回は不要なので0でよい。
- Angular Drag
- 回転する際の空気抵抗。回転しないのでそのままでよい。
- Use Gravity
- 重力によって落下するかを決めるフラグ。今回は重力は使わないのでチェックを外す。
- Is Kinematic
- 物理挙動を無効にする際にチェックを入れる。枠に跳ね返って欲しいのでそのまま。
- Interpolate
- Noneでよい。
- Collision Detection
- 衝突判定を連続的に行うかを設定する。オブジェクトを飛び越えてしまうぐらい高速にものを動かす際に利用する。今回はDiscreteのままでよい。
- Constaints
- 影響を与えない軸を決める。プレイヤーバーはX方向の移動以外は不要なので、Freeze PositionのX以外に全てチェックを入れる。
プレイヤーバーの移動処理
今回のプログラムは非常に軽いので、描画も処理も60fpsで処理出来そうである。よって、1回画面を描画するごとに1度呼び出される【Update()】関数内に処理を実装する。
描画や処理が非常に重くて、描画速度が30fpsを割るような場合は、FixedUpdate()にプレイヤ操作を実装した方が反応がよくなるかも知れない。
実装するにあたって、以下の点を確認しておこう。
- 左右ボタンの確認は、Input.GetAxis("Horizontal")で分かる。左の時、-1。右の時、1。押されていない時は0が返る。
- プレイヤーバーの移動速度は、インスペクターで設定できるようにしよう。VELという変数を作成し、1秒間に移動させたい距離を設定する。
- 移動先は自分で計算して、rigidbodyのMovePosition()関数を使って設定する。
それでは実装してみる。
- 【プロジェクト(Project)】ビューから「Scenes」フォルダ内の「Game」シーンをダブルクリックする。
- 【プロジェクト(Project)】ビューの「Scripts」フォルダを右クリックして、【Create】→【C# Script】を選択。
- 新規作成したスクリプト名を「CPlayer」にする。
- 作成した「CPlayer」をメモ帳で開いて「Unicode big endian」で上書き保存する。
- メモ帳を閉じたら【Unity】に戻って、「CPlayer」をダブルクリックして【MonoDevelop】で開く。
スクリプトファイルが出来たので、移動速度をインスペクターで設定するための変数「VEL」を作る。
- 「public class CPlayer: MonoBehaviour {」という行を探して、その下の行に以下のプログラムを記載する。
public float VEL = 400f;
- 以上で「float型(浮動小数点型)の変数「VEL」を、公開(public)変数として定義して、初期値に「400」を設定」という意味になる。
- 変数定義の頭に「public」をつけると、インスペクターにその変数が表示され、編集できるようになる。
- 400の後ろの「f」は、浮動小数値という意味。
- VELを、キー入力に掛けて、さらに処理時間をかければ1フレームで動かす距離が求めまる。
移動処理を追加する。
- 「void Update() {」という行を探して、その下の行に以下のプログラムを記載する。
Vector3 npos = transform.position;
npos.x = npos.x+VEL*Time.deltaTime*Input.GetAxis("Horizontal");
rigidbody.MovePosition(npos);
プログラムは以下のような意味になる。
Vector3 npos = transform.position;
- Vector3型のローカル変数nposを定義して、プレイヤーの現在座標(transform.position)を代入する。
- Vector3型は、x,y,zの3つのfloat型の値を持てる3次元のベクトル型変数である。
npos.x = npos.x+VEL*Time.deltaTime*Input.GetAxis("Horizontal");
- プレイヤーのx座標(npos.x)に、1フレーム分の移動距離を足して、新しい座標を計算する。
- VEL
- 1秒間の移動距離。
- Time.deltaTime
- 1フレームにかかった時間。この値をVELに掛けることで、1秒間の移動距離を、1フレームの移動距離に変換できる。
- Input.GetAxis("Horizontal")
- 左の時、-1。右の時、1。押されていない時、0を返す関数。
- VEL
以上できたら実行してみよう。左右キーでプレイヤーバーが移動し、枠にぶつかると自動的に止まることが確認できるだろう。
しかし、ちょっとプレイヤーバーの動きがおかしい。ボタンの操作に対してぴしっと動かず、慣性がついてしまっている。これは「Input.GetAxis()」が想定した値を返していないのが原因である。入力の設定を調整してみよう。
- メニューから【Edit】→【Project Settings】→【Input】を選択する。
- インスペクター(Inspector)に【Input Manager】が表示されるので、【Axes】→【Horizontal】を開く。
- 【Gravity】の項目があるので、これを【100】にする。
- ここで一度実行して、操作を確認して、何が変わったか確認してみよう。
- 実行を停止してから、【Sensitivity】の値を【100】にする。
以上で、入力の設定は完了である。これで、キー操作がプレイヤーバーの移動にすぐに反映されるようになった。
今回、移動に「rigidbody.MovePosition()」という関数を利用したが、チュートリアルなどでは「transform.positioin」を直接いじっていたりする。この違いは実際に試してみると分かる。
「rigidbody.MovePosition(npos);」を「rigidbody.transform.position = npos;」や「transform.position = npos;」に変更して、動作を確認してみよう。プレイヤーバーをフレームにぶつけた際にめり込むのが観察できる。positionを直接いじると、まず移動が行われ、その後に剛体シミュレーションが実行されるため、他の衝突物(コライダー)にめり込んでしまう。rigidbodyのMovePosition()を使うと、このような挙動を回避できる。
ボールをくっつける
ゲームが開始する時には、ボールがプレイヤーバーにくっついて動くようにしたい。自力で実装する場合は、プレイヤーの移動に合わせて、ボールの座標も移動させるような処理を書かねばならないが、階層構造を持つUnityではボールをプレイヤーの子オブジェクトにすることで、勝手にくっついて動いてくれるようになる。
ボールを登場させたり消したりを簡単に実現するには、ボールをプレハブ化しておくと便利である。プレハブ化しておくと、例えば複数のボールを出すようなことも簡単に出来るようになる。
- 【プロジェクト(Project)】ビューの「Prefabs」フォルダを右クリックして、【Create】→【Prefab】を選択して、新しいプレハブを作成する。
- 作成したプレハブの名前を「Ball」に変更する。
- 【階層(Hierarchy)】ビューの「Ball」をドラッグして、【プロジェクト(Project)】ビューに作成した「Ball」プレハブのところでドロップする。
これで、ボールのプレハブが出来上る
それでは、プレイヤーバーにボールをくっつけてみよう。「public float VEL = 400f;」の行を探して、下に以下のプログラムを追加する。
public GameObject prefBall = null; GameObject insBall = null;
- prefBall
- ボールのプレハブを記録しておくための変数。ボールを生成するのに利用する。「public」がついているので、インスペクターで編集できる。
- insBall
- 生成したボールのインスタンス(実体)を記録しておくための変数。ボールを発射する時に利用する。
次に、ボールをプレイヤーバーにくっつける関数を作ろう。「CPlayer」スクリプトの一番最後の「}」を見つけて、その上の行に以下のプログラムを追加する。
// ボールを生成して、プレイヤーバーにくっつける 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; }
プログラムは以下のような意味である。
Vector3 bpos = transform.position;
- ローカル変数としてVector3型の「bpos」を宣言して、プレイヤーバーの現在位置(transform.position)を代入する。
bpos.y += (collider.bounds.size.y+prefBall.collider.bounds.size.y)/2f;
- ボールがプレイヤーバーの上にくっつくような座標を計算する。
- bposのY座標(bpos.y)に、プレイヤーバーの高さ(collider.bounds.size.y)とボールの高さ(prefBall.collider.bounds.size.y)の合計を2で割ったものを加えると、求める座標が計算できる。
insBall = (GameObject)Instantiate(prefBall,bpos,Quaternion.identity);
- 「prefBall」からボールの実体を生成(Instantiate)する。
- 座標はbpos。
- 回転はなし(Quaternion.identity)。
insBall.transform.parent = transform;
- 生成したボールをプレイヤーバーの子オブジェクトにする。
- 生成したボールのインスタンス(insBall)の姿勢情報(transform)の親(parent)に、プレイヤーバーの姿勢情報(transform)を設定することで、子オブジェクトとすることが出来る。
以上で、ボールを生成して、それをプレイヤーバーの子オブジェクトにする関数が作れた。インスペクターでボールのプレハブをスクリプトに渡した上で、Start()関数から呼び出せば、ボールを生成することが出来る。
- 「void Start () {」という行を見つけて、その下の行に以下のプログラムを追加する。
createHoldBall();
- スクリプトを保存して【Unity】に戻る。
- 【階層(Hierarchy)】ビューから「Player」を選択する。
- 【プロジェクト(Project)】ビューの「Prefabs」フォルダ内にある「Ball」プレハブをドラッグして、インスペクターに表示されている「CPlayer(Script)」コンポーネントの要素の「Pref Ball」の項目にドロップする。
以上で設定完了となる。実行して、ボールがプレイヤーバーの上に生成されて、移動した時にくっついてくるかを確認しよう。
プレイヤーがボールを発射する操作が残っているが、これはボール側の命令を呼び出すことで実現させたいので、プレイヤーバーの実装は一旦、完了とする。
ここまでに作成した「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); } // ボールを生成して、プレイヤーバーにくっつける 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; } }