えにあです。 前回は玉転がし作成ゲームのプレイヤーオブジェクトである球体と、プレイグラウンドの作成を行いました。 今回は、ゲームのユーザによる入力に基づき、プレイヤー(球体)に物理的な力を加えて動かせるようにしていきます。
目次
プレイヤーに必要な動作を考えよう
まず、プレイヤーにどのような動きをさせるかを考えましょう。
プレイヤーはゲームエリア全体を転がることができ、端まで行くと壁にぶつかります。
プレイヤーは常に地面に設置していて、宙に浮くことはありません。
また、アイテムに触れたら接触したアイテムを取得できるようにします。
これには物理演算が必要です。 物理演算を使用するには、ゲームオブジェクトにRigidBodyコンポーネントをアタッチする必要があります。
リジッドボディ
物理シミュレーションによって、モデル同士の衝突判定(コリジョン)を行う際に設定する代表的な要素。リジッドボディはレンガや岩など、それ自身が変形しない硬い材質のモデルに適用する。
リジッドボディをアタッチしよう
コンポーネントをアタッチするには、まずアタッチされるゲームオブジェクトを選択します。
ここでは、プレイヤーを動かすのが目的ですのでプレイヤーゲームオブジェクトを選択します。 プレイヤーゲームオブジェクトを選択した状態でトップメニューから「コンポーネント」->「物理」->「リジッドボディ」を選択します。
または、インスペクターで「コンポーネントを追加」->「リジットボディ」で追加することもできます。
追加するとインスペクターにリジットボディの項目が表示されますね。
コンポーネントは順番を変更することができます。
コンポーネントの右側の「︙」をクリックして、上に移動/下に移動で変更できます。
コンポーネントの順序はゲームには一切影響ありませんが、整理して見やすくすることができます。
プレイヤーを動かすためのスクリプトファイルを作成しよう
プレイヤーを移動させるには、ユーザのキー入力を取得し、入力に応じてプレイヤーに力を加え、シーン内でプレイヤーを動かすようにする必要があります。
これはプレイヤーゲームオブジェクトにスクリプトをアタッチすることで実現します。 まず、プロジェクトビューでスクリプトアセットを格納するためのフォルダを作成しましょう。
フォルダの作成手順は以前と同じです。
トップメニューから「アセット」->「作成」->「フォルダ」で作成します。
作成するフォルダの名前は「Scripts」にします。
NOTE:
私は小文字でscriptsにしてしまいましたが、デフォルトで存在するScenesなどに合わせるために大文字から始めるのが良いと思います。
次に、新しいC#スクリプトを作成します。 スクリプトを作成する方法はいくつかありますが、一つはトップメニューから「アセット」->「作成」->「C#スクリプト」を選択する方法です。※別の方法を使いますので実行しなくて良いです。
ですが、今回はプレイヤーにスクリプトをアタッチするのが目的ですので、プレイヤーオブジェクトを選択して、インスペクターで「コンポーネントを追加」->「新しいスクリプト」と選択するのが効率的です。 これを行うと、スクリプトの作成とアタッチを同時に行ってくれますので、今回はこの方法を利用します。
PlayerControllerという名前をつけて新しいスクリプトを作成しましょう。
InspectorにPlayerControllerが表示されましたね。
プロジェクトビューを見てみましょう。
作成したPlayerControllerがプロジェクトのルートフォルダに置かれていることが分かります。
整理するために、先ほど作成した「Scripts」フォルダの下に移動させましょう。 ファイルの移動はプロジェクトビューでドラッグアンドドロップするだけです。
Scriptsフォルダの中にPlayerControllerが置かれたことを確認してください。
プロジェクトビューでスクリプトを選択するとインスペクターにソースコードが表示されますが、インスペクター上で直接編集することはできません。
編集する場合はファイルを開く必要があります。
インスペクターの上側右の方にOpenというボタンがありますね。
このボタンを押すとエディタが起動し、ファイルを編集することができます。
エディタは何でも構いませんが、Windows標準のメモ帳で編集していきます。
NOTE:
公式のチュートリアルではMonoDevelopというエディタを使っています。
このエディタを使うと、変数名や関数名の入力補完をしてくれるようです。
しかし、Unity2018以降はこのEditorが廃止された模様です。 今回はメモ帳で書きますが、入力補完なしでは開発効率が悪いので今後エディタ便利なエディタを探す予定です。VSCodeが利用できると良いな。
プレイヤーを動かすためのスクリプトを書いていこう
まずスクリプトでどのような処理を書くべきか考えてみましょう。
このスクリプトではフレームごとにユーザの入力をチェックし、その入力に従ってプレイヤーオブジェクトを動かします。
入力チェックは、スクリプトのどこに記述すればよいのでしょうか? UpdateメソッドまたはFixedUpdateメソッドのどちらかです。
Updateメソッドはフレームをレンダリングする前にコールされ、ゲームのコードの大部分がここに記述されます。
それに対してFixedUpdateメソッドは、物理演算を実行する直前に呼び出されます。 物理演算のコードはここに記述されます。 リジッドボディに力を加えてボールを動かす行為は物理演算です。 そのため、今回はFixedUpdateメソッドに処理を書いていきます。
まずはデフォルトで記述されていたStartメソッドやUpdateメソッドを削除し、FixedUpdateメソッドを追加しましょう。コードは以下のようになります。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { // 物理演算の度にコールされます。 void FixedUpdate () { } }
Unityでユーザ入力を扱うにはInputクラスを利用します。
Unityの標準クラスのドキュメントは以下のURLに用意されています。
Unity スクリプトリファレンス
今回利用するInputクラスのドキュメントはこちらです。
UnityEngine.Input - Unity スクリプトリファレンス
Inputクラスの概要を読むと、このクラスを使用して入力で設定された軸を読み取りモバイルデバイスでマルチタッチ/加速度計データにアクセスできることが分かります。
ドキュメントの下の方に目を移すと、スタティックフィールドとスタティックメソッドの一覧があります。 スタティックフィールドは情報を保持します。 例えば、touchCountにはタッチ回数が保存され、gyroにはデフォルトのジャイロスコープへの参照が保存さます。
スタティックメソッドは様々な機能を提供します。 今回はユーザ入力を受け取るためのGetAxisメソッドを使っていきます。
メソッド名のリンクをクリックすると、メソッドの詳細を見ることができます。
GetAxisの詳細はこちらです。
コードのスニペットも用意されていますね。
Unity - Scripting API: Input.GetAxis
このスニペットを参考に、FixedUpdateメソッドを以下のように変更します。
void FixedUpdate () { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical= Input.GetAxis("Vertical"); }
変数moveHorizontalとmoveVerticalには、それぞれ水平軸(X軸)と垂直軸(Z軸)方向の入力が設定されます。 入力はキーボードから与えられます。 プレイヤーゲームオブジェクトはリジットボディを通して物理演算エンジンとデータのやり取りを行います。 プレイヤーの入力に従ってリジットボディに力を加え、シーン内のプレイヤーオブジェクトを動かします。
これを実現するためには、プレイヤーゲームオブジェクトにアタッチされているPlayerControllerスクリプトから、同じくプレイヤーゲームオブジェクトにアタッチされているリジットボディコンポーネントにアクセスする必要があります。
NOTE: オブジェクトに対してコンポーネントをアタッチしている、という関係性になっています。 プレイヤー(球体)がオブジェクトで、スクリプトもリジットボディもオブジェクトにアタッチされたコンポーネントです。
では、PlayerControllerに、リジットボディの参照を取得するコードを追加していきましょう。 Unityにはリジットボディを扱うためのRigidbodyクラスが用意されています。 まず、PlayerControllerクラスにRigidbody型のプライベートフィールドを作成します。
この変数はStartメソッドの中で、GetComponentメソッドを使って初期化します。 コードは以下のようになります。 ※実はこのコードにはミスがあります。後ほど修正します。
public class PlayerController : MonoBehaviour { private RigidBody rb; void Start () { rb = GetComponent<RigidBody>(); } ~ 中略 ~ }
startメソッドはスクリプトがアクティブになる最初のフレームで呼び出されます。 たいていの場合、ゲームの最初のフレームと同一です。 (※どのような場合に異なるのかは現時点ではわかりませんでした)
GetComponentメソッドの詳細は割愛しますが、このメソッドを利用することで、このスクリプトと同じオブジェクトにアタッチされているコンポーネントを取得することができるようです。
NOTE:
コンポーネントを取得する方法はほかにもあるようですが、このチュートリアルでは紹介されていませんでした。他のオブジェクトにアタッチされているコンポーネントを取得する方法も当然あると思われます。現時点では最小の方法だけ把握していれば問題ないでしょう。
さて、これでリジッドボディへの参照を取得することができました。 次に、このリジッドボディに力を加える処理を追加していきます。
まずこちらにあるRigitbodyクラスのドキュメントを見てみましょう。 UnityEngine.Rigidbody - Unity スクリプトリファレンス
ドキュメントのPublic関数にAddForceメソッドがありますね。このメソッドでリジッドボディに力を加えられます。
AddForceメソッドのドキュメントはこちらです。
Rigidbody-AddForce - Unity スクリプトリファレンス
AddForceメソッドのシグネチャを見ると、Vector3型のforceと、ForeceMode型のmodeの二つの引数が必要なことが分かります。
public void AddForce (Vector3 force, ForceMode mode= ForceMode.Force);`
Vector3の詳細は割愛しますが、簡単に説明するとVector3は(x, y, z)の3つの数値をまとめたものです。 3D空間での移動や力の値など、軸ごとの値が必要な場合に利用されます。
ForceModeについても割愛しますが、力のタイプを Acceleration、Impulse、VelocityChange に変えることができるようです。公式チュートリアルでも詳細な言及はありませんでした。
では、初期化したRigidbodyオブジェクトを使って力を加える処理を書いてみましょう。 FixedUpdateメソッドのコードを以下のように修正します。
void FixedUpdate () { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical= Input.GetAxis("Vertical"); Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical); rb.AddForce(movement); }
AddForceメソッドの第二引数は省略可能ですので、ここではVector3型の第一引数だけを設定します。 このVector3型の値には「プレイヤーを移動させるための力の大きさ」を設定する必要があります。
x軸方向の力は、Input.GetAxis("Horizontal")で取得でき、既にmoveHorizontal変数に設定されています。
y軸方向の力は常にゼロです。玉は常に接地しているからです。
z軸方向の力は、Input.GetAxis('Vertical")で取得でき、既にmoveVertical変数に設定されています。
つまり、移動の力の大きさは、new Vector3 (moveHorizontal, 0.0f, moveVertical);
で表現できます。
これをmovement変数に代入し、AddForceの引数として渡せばOKです。
ボールを動かしてみよう
スクリプトを保存してUnityに戻ります。 プロジェクトビューでPlayerControllerを選択した状態でコンソールタブを開いてみてください。 以下のエラーが表示されています。
RegidBodyが見つからない、というエラーが出ていますね。
もう一度ドキュメントを見てみるとクラス名は「RegidBody」ではなく「Regidbody」が正解です。
修正して保存します。今度はエラーが消えました!
では、プレイモードに変更して実際にボールを動かしてみましょう。
プレイモードへの変更はLEGOのチュートリアルで散々やりましたね。
シーンビューの上の▲を押してプレイモードに変更します。
矢印キーで上下左右に動かしてみてください。 ゆっくりではありますが、ボールが指定した方向に動いたでしょうか?
なんとなくゲーム開発に近づいてきましたね!
だんだんと面白くなってきました。
今回はここまで!