Unityでゲームを作ってみたい。
そう思って始めたものの、実際にゲームを完成させるまでには、たくさんの機能や考え方を理解する必要があります。
前回は、Unity Hubを使ったプロジェクト作成や、Unityエディターの基本的な画面構成、シーンビューの操作方法について確認しました。
今回は、いよいよゲーム制作に入ります。
Roll a Ballの舞台となるステージを作成し、プレイヤーとなるボールを配置して、実際に操作できるところまで進めていきます。
まだシンプルな形ではありますが、ここから少しずつ「ゲーム」と呼べるものに近づいていきます。
初心者ならではの視点で、なぜその設定をするのか、どのような仕組みで動いているのかも確認しながら進めていきます。
Contents
今回の内容
今回は、ゲームとして動かすための土台作りを進めていきます。
- ゲームステージの作成
- プレイヤーとなるボールの配置
- 物理挙動の追加
- キーボード操作の実装
- カメラの追従設定
前回は、まだ何も配置されていない状態でした。
今回は、床や壁を作り、その上をプレイヤーが転がるという、ゲームの基本となる部分を作っていきます。
シーンを作成する
Unityでは、「シーン」という単位でゲーム画面やステージを管理します。
シーンには、ゲームオブジェクトを配置して、1つの画面やステージを構成していきます。
今回は1つのシーンだけで完結しますが、規模の大きなゲームでは、タイトル画面、ステージ画面、メニュー画面など、複数のシーンを切り替えながらゲーム全体を構成します。
それでは、今回使用するシーンを作成していきましょう。
プロジェクトウィンドウからScenesフォルダーを開きます。
空いている場所で右クリックし、
Create > Scene > Scene
を選択します。
新しいシーンが作成されたら、名前をMainStageに変更します。
作成したシーンをダブルクリックして開きましょう。
シーンビューの見た目は変化しませんが、ヒエラルキーウィンドウを見ると、現在開いているシーンがMainStageになっていることが確認できます。
ステージを作成する
それでは、玉転がしゲームの土台となるステージを作っていきます。
まずは床から作成します。
ヒエラルキーウィンドウ左上の+ボタンから、
3D Object > Plane
を選択します。
作成されたオブジェクトの名前をFloorに変更します。
続いて、Transformを確認します。
もしPosition、Rotation、Scaleの値が初期状態ではない場合は、インスペクター右上のメニューからResetを選択して初期化します。
今回は床を広く使いたいため、Scaleをすべて2に変更します。
次に、周囲を囲う壁を作成します。
ヒエラルキー上で右クリックし、
3D Object > Cube
を選択します。
名前をWest Wallに変更します。
Transformをリセットしたあと、以下のように設定します。
| 項目 | 値 |
|---|---|
| Position X | -10 |
| Scale | 0.5 / 2 / 20.5 |
作成した壁を複製して、反対側の壁を作ります。
Ctrl + Dで複製し、名前をEast Wallへ変更します。
Position Xを10に変更します。
さらに複製して、前後方向の壁も作成します。
- North Wall:Position Zを10、Rotation Yを90
- South Wall:Position Zを-10
これで四方向を囲むステージが完成しました。
ゲームオブジェクトとコンポーネント
ここまでで、床と壁が完成しました。
ただ、ここでUnityの基本的な考え方について、少し確認しておきましょう。
Unityでは、シーン上に配置されるすべてのものをゲームオブジェクトと呼びます。
床や壁、プレイヤー、カメラ、ライトなども、すべてゲームオブジェクトです。
そして、ゲームオブジェクトには、それぞれ役割を与えるための情報が追加されています。
この情報のことをコンポーネントと呼びます。
例えば、先ほど作成したFloorを選択して、インスペクターウィンドウを確認してみましょう。
「Transform」や「Mesh Filter」、「Mesh Renderer」、「Mesh Collider」といった複数のコンポーネントが追加されています。
Transformは、位置や回転、大きさを管理するためのコンポーネントです。
Mesh Rendererは、ゲームオブジェクトを画面上に表示するためのコンポーネントになります。
Mesh Colliderは、他のオブジェクトとの当たり判定を作るためのコンポーネントです。
一方、Main Cameraを見てみると、「Camera」や「Audio Listener」といったコンポーネントが追加されています。
つまり、ゲームオブジェクト自体は単なる入れ物であり、どのようなコンポーネントを追加するかによって、その役割が決まります。
カメラの機能を持たせることもできますし、ライトとして使用することもできます。
逆に、コンポーネントを削除すれば、その機能を失わせることもできます。
ゲームオブジェクト+コンポーネントという考え方は、今後ゲーム制作を進めるうえで非常に重要になるので覚えておきましょう。
続いて、作成した床と壁を整理しておきます。
ヒエラルキー上で右クリックし、
Create Empty
を選択します。
空のゲームオブジェクトが作成されるので、名前をStageに変更します。
空のゲームオブジェクトには、Transform以外のコンポーネントはありません。
今回は、このStageを親として、床と壁をまとめます。
Floor、West Wall、East Wall、North Wall、South Wallを複数選択し、Stageへドラッグします。
ヒエラルキー上で1段右にずれて表示されれば、親子関係が作成されています。
これは単純なグループ化と考えることもできます。
親となるStageを移動すると、子になった床や壁も位置関係を保ったまま一緒に移動します。
これで、ステージ部分の完成です。
Ctrl + Sで保存しておきましょう。
プレイヤーを作成する
続いて、操作するプレイヤーを作成していきます。
今回は、玉転がしゲームなので、ボールをプレイヤーとして使用します。
ヒエラルキー上で右クリックし、
3D Object > Sphere
を選択します。
作成されたオブジェクトの名前をPlayerに変更します。
Transformをリセットしたあと、床に埋まらないようにPosition Yを0.5に設定します。
ここで、一度ゲームを実行してみましょう。
画面上部中央にある再生ボタンをクリックします。
ゲームが開始されると、自動的にGameビューが表示されます。
まだ何も動きませんが、シーンビュー上では、実行中でもオブジェクトを選択して位置を変更することができます。
ただし、これはゲーム内の操作ではありません。
あくまでエディター上でオブジェクトの状態を変更しているだけです。
試しにPlayerを持ち上げてみましょう。
その状態でゲームを停止すると、Playerは元の位置へ戻ります。
これは、実行中に変更した値は、基本的にゲーム終了時に破棄されるためです。
もしゲーム終了後も状態を保存したい場合は、別途保存する仕組みを作る必要があります。
では、Playerに物理的な動きを追加していきましょう。
Rigidbodyを追加する
Playerを選択し、インスペクター下部にあるAdd Componentをクリックします。
検索欄にRigidbodyと入力し、追加します。
ここで注意したいのは、追加するのはRigidbody 2Dではないという点です。
今回は3D空間でゲームを作成しているため、3D用のRigidbodyを使用します。
追加後、もう一度ゲームを実行してみましょう。
シーンビューでPlayerを持ち上げて離すと、重力によって落下することが確認できます。
先ほどまでは、ただ位置情報を持った球でした。
しかし、Rigidbodyを追加したことで、物理演算の対象になりました。
これで、ゲーム内のオブジェクトとして動くための準備が整いました。
プレイヤーを動かす
ここまでで、プレイヤーとなるボールに物理的な挙動を追加することができました。
次は、キーボード入力によって、このボールを操作できるようにしていきます。
まずは、入力を受け取るための仕組みを追加します。
Playerオブジェクトを選択し、インスペクターからAdd Componentをクリックします。
検索欄にPlayer Inputと入力し、追加します。
警告が表示されますが、今回はそのまま進めます。
続いて、プレイヤーを動かすためのスクリプトを作成します。
プロジェクトウィンドウからAssetsフォルダーを開き、その中にScriptsフォルダーを作成します。
フォルダー内で右クリックし、
Create > Scripting > MonoBehaviour Script
を選択します。
作成されたスクリプトの名前をPlayerControllerに変更しましょう。
ファイルをダブルクリックすると、コードエディターが起動します。
Unityのスクリプトは、C#というプログラミング言語で記述します。
新しくスクリプトを作成すると、基本となるひな形が自動で生成されます。
まずは、この構造を簡単に確認しておきましょう。
usingについて
先頭にあるusingから始まる記述は、名前空間を読み込むための宣言です。
名前空間とは、関連する機能をまとめたコードのグループのようなものです。
例えば、MonoBehaviourやGameObjectなど、Unityで使用する基本機能はUnityEngineという名前空間にまとめられています。
今の段階では、「必要な機能を呼び出すための準備」と考えておけば問題ありません。
クラスについて
次に、波括弧{ }で囲まれた部分を見てみましょう。
このまとまりをブロックと呼びます。
スクリプト全体を囲んでいるブロックはクラスという役割を持っています。
クラスとは、プログラムの機能をまとめるための入れ物です。
この中に、ゲームオブジェクトの状態や動きを記述していきます。
スクリプト作成時につけたファイル名は、そのままクラス名として作成されます。
もしファイル名とクラス名が異なる場合は、ここでPlayerControllerへ変更しておきましょう。
メソッドについて
クラスの中には、さらにメソッドと呼ばれるブロックがあります。
初期状態では、
- Start
- Update
という2つのメソッドがあります。
メソッドとは、実行する処理をまとめたものです。
Startはゲーム開始時に一度だけ実行されます。
Updateはゲーム実行中、毎フレーム呼び出されます。
ここまでが、スクリプトの基本的な構造になります。
入力処理を追加する
それでは、プレイヤー操作の処理を追加していきます。
まず、入力機能を使用するため、先頭に以下を追加します。
using UnityEngine.InputSystem;
これで、新しい入力システムの機能を利用できるようになります。
次に、StartメソッドとUpdateメソッドの間へ、新しくOnMoveメソッドを作成します。
void OnMove(InputValue movementValue){
このInputValueという部分は、メソッドへ渡される情報の種類を表しています。
このように、メソッドの括弧内に書かれているものをパラメーターと呼びます。
パラメーターは、メソッドが実行されるときに外部から渡される値です。
今回は、キーボード入力の情報を受け取るために使用します。
入力値を保存する
受け取った入力情報を保存するため、変数を作成します。
クラスの中、Startメソッドより上に以下を追加します。
float movementX;
float movementY;
これは変数の宣言です。
変数とは、値を入れておくための箱のようなものです。
今回は、左右方向と前後方向の入力値を保存するための箱を用意しています。
floatは、数値を扱うための型のひとつです。
小数を扱うことができる数値型で、速度や位置など、ゲーム制作ではよく使用されます。
続いて、OnMoveメソッドの中に以下を追加します。
Vector2 movementVector = movementValue.Get<Vector2>();
movementX = movementVector.x;
movementY = movementVector.y;
入力された2方向の情報を取得し、それぞれX方向、Y方向へ分けています。
これで、プレイヤーの入力値をスクリプト内で利用できるようになりました。
Rigidbodyを取得する
次に、プレイヤーへ力を加えるため、Rigidbodyを取得します。
クラスの一番上に、以下を追加します。
Rigidbody rigidbody;
これは、Rigidbodyを保存しておくための変数です。
続いて、Startメソッド内へ以下を追加します。
rigidbody = GetComponent<Rigidbody>();
GetComponentを使用することで、同じゲームオブジェクトに追加されているRigidbodyコンポーネントを取得できます。
取得したRigidbodyを、先ほど作成した変数へ保存しています。
ボールへ力を加える
最後に、入力値を使ってボールを動かします。
Updateメソッドを削除し、代わりにFixedUpdateを作成します。
void FixedUpdate()
{
Vector3 movement = new Vector3(movementX, 0.0f, movementY);
rigidbody.AddForce(movement);
}
FixedUpdateは、一定間隔で呼び出されるメソッドです。
物理演算を扱う処理では、UpdateよりもFixedUpdateを使用するのが一般的です。
ここでは、入力値を3次元の座標へ変換し、Rigidbodyへ力として与えています。
Unityでは、Y軸が高さ方向になるため、前後左右の移動にはXとZを使用します。
これで、プレイヤーを動かすための基本処理が完成しました。
では、Unityに戻って確認してみましょう。
「Player」オブジェクトに、作成した「PlayerController」スクリプトをドラッグします。
インスペクターを見ると、スクリプトがコンポーネントとして追加されたことが確認できます。
それでは、実行してみましょう。
キーボードの「W」「S」で前後に、「A」「D」で左右に移動できるようになりました。
ただ、少し動きがゆっくりですね。
移動速度を変更できるようにしてみましょう。
スクリプトに戻ります。
変数の定義部分の一番下に、次の一文を追加します。
public float speed = 1;
publicというキーワードを付けることで、この変数を外部からアクセスできるようになります。
Unityでは、publicな変数はインスペクター上にも表示されるため、ゲームを実行しながら調整しやすくなります。
また、宣言と同時に「1」という初期値を設定しています。
続いて、力を加える部分を変更します。
rigidbody.AddForce(movement * speed);
これで、入力された移動量に速度を掛け合わせることができます。
Unityに戻ってみましょう。
インスペクターを見ると、「Speed」という項目が追加されています。
この値を「10」に変更してみます。
それでは、もう一度実行してみましょう。
先ほどよりも、かなり動かしやすくなりました。
このように、スクリプト側で直接値を変更するのではなく、インスペクターから調整できるようにしておくことで、ゲームの調整作業がしやすくなります。
さて、ここまででプレイヤーを操作するところまで完成しました。
次は、カメラをプレイヤーに追従させる設定をしていきましょう。
カメラの設定
では、Unityに戻って動作を確認してみましょう。
「Player」オブジェクトに作成したスクリプトをドラッグします。
インスペクターを見ると、スクリプトがコンポーネントとして追加されたことが確認できます。
それでは実行してみましょう。
キーボードのWSキーで前後、ADキーで左右に移動できるようになりました。
ただ、少し動きがゆっくりですね。
移動速度を変更できるようにしてみましょう。
スクリプトに戻ります。
変数の定義部分の一番下に、以下のように追加します。
public float speed = 1;
publicというキーワードを付けていますが、これは「アクセス修飾子」と呼ばれるもので、変数へアクセスできる範囲を指定するものです。
publicを付けることで、この変数はインスペクター上から変更できるようになります。
また、今回は変数の宣言と同時に初期値として「1」を代入しています。
続いて、FixedUpdate内の処理を変更します。
rigidbody.AddForce(movement * speed);
入力によって作成した移動方向に、先ほど追加した速度値を掛け合わせています。
Unityに戻って確認してみましょう。
インスペクターを見ると、「Speed」という項目が追加されています。
この値を「10」に変更します。
もう一度実行してみましょう。
先ほどよりも、かなり軽快に動くようになりました。
このように、スクリプト内の値をインスペクターから調整できるようにしておくことで、コードを書き換えなくてもゲームの挙動を調整できます。
さて、これでプレイヤーを操作するための基本部分が完成しました。
次は、カメラをプレイヤーに追従させる設定を行っていきましょう。
カメラの設定
このように、カメラがプレイヤーの移動に追従するようにしていきたいと思います。
まず、ヒエラルキーから「Main Camera」を選択します。
インスペクターで、Position Yを「10」、Rotation Xを「45」に設定します。
これで、ステージ全体を見渡せるようになりました。
次は、プレイヤーを追いかけるようにしたいですね。
単純に考えると、カメラを「Player」オブジェクトの子にすればよさそうです。
実際に試してみましょう。
……おっと、少し想定とは違う動きになりました。
「Player」オブジェクトの回転に合わせて、カメラまで一緒に回転してしまっています。
一度、元の状態に戻しておきましょう。
このような場合は、スクリプトを使ってカメラの位置を制御する方法が一般的です。
プロジェクトウィンドウの「Scripts」フォルダー内で、新しいスクリプトを作成します。
名前は「CameraController」としておきましょう。
まず、2つの変数を宣言します。
public Transform player;
Vector3 offset;
playerには追従対象となるプレイヤーの位置情報を、offsetにはカメラとプレイヤーの位置の差分を保持します。
続いて、Startメソッド内に以下の処理を追加します。
offset = transform.position - player.position;
これは、ゲーム開始時点でのカメラとプレイヤーの位置の差を計算しています。
次に、UpdateメソッドをLateUpdateへ変更します。
そして、中身を以下のように書き換えます。
void LateUpdate()
{
transform.position = player.position + offset;
}
カメラなどの描画に関わる処理は、ほかのオブジェクトの移動が完了したあとに行う必要があります。
LateUpdateは、通常の更新処理の後に実行されるメソッドなので、このような追従処理に適しています。
プレイヤーの位置に、最初に計算した差分を加えることで、カメラの位置を毎フレーム更新しています。
それでは、Unityに戻りましょう。
「Main Camera」に作成したスクリプトをアタッチします。
インスペクターに表示された「Player」スロットへ、「Player」オブジェクトをドラッグして割り当てます。
実行して確認してみましょう。
カメラが回転することなく、プレイヤーの移動に合わせて追従するようになりました。
次は、プレイヤーが取得するアイテムを作成して、さらにゲームらしくしていきます。
次回
今回は、カメラの設定まで進めました。
プレイヤーを操作できるようになり、カメラもプレイヤーを追従するようになったことで、ゲームとしての基本的な形が少しずつ見えてきました。
次回は、プレイヤーが集めるアイテムを作成し、実際に取得できる仕組みを実装していきます。
さらに、プレハブを使ったオブジェクトの複製や、アイテムを円形に配置する方法などについても見ていきます。
次回も、引き続き「Roll a Ball」を作りながら、Unityの基本を学んでいきましょう。
まとめ
今回は、Unityで簡単な玉転がしゲームを作りながら、カメラの設定まで進めました。
ステージを作成し、プレイヤーを配置して操作できるようにしたほか、物理演算を利用したプレイヤーの移動や、カメラをプレイヤーに追従させる処理も実装しました。
今回の内容をまとめると、以下のようになります。
- Unityの基本的なインターフェースと操作方法
- シーンとゲームオブジェクト
- コンポーネント
- Rigidbodyによる物理挙動
- C#スクリプトの基本
- Input Systemを使ったプレイヤー操作
- カメラの追従処理
一見すると、ゲームを作るためにはたくさんの知識が必要に思えますが、実際には一つひとつの機能を組み合わせていくことで、少しずつゲームらしいものが形になっていきます。
次回は、プレイヤーが集めるアイテムを作成し、ゲームとしての目的を追加していきます。
それでは、また次回。
コメント
コメントを投稿