はじめに
この記事は、Unity初心者が2Dゲームの作成に慣れることを目的としたチュートリアルとなります。前回は背景の表示までを作りました。
- 【Unity2D】Unityで2Dミニゲームを作るチュートリアル(第1回) ←前回
- 【Unity2D】Unityで2Dミニゲームを作るチュートリアル(第2回) ←今ココ
- 【Unity2D】Unityで2Dミニゲームを作るチュートリアル(第3回)
今回は敵を表示して動かすところまでを作っていきます。
チュートリアル(第2回)
敵の生成
敵(たこ焼き)を作成します。Projectビューの「Assets/Sprites」フォルダにある tako スプライトを、Sceneビューにドラッグ&ドロップします。
すると「tako」ゲームオブジェクトが作成されます。Hierarchyビューの「takou」オブジェクトを選択して、もう一度クリックすると名前が変更できます。
そうしたら名前を「Enemy」に変更します。なお、選択した状態で[F2]を押すことでも名前を変更できます。名前を変更できたらInspectorビューのTransform > Position の値を(X, Y, Z) = (0, 0, 0)にして原点に移動させておきましょう。
もし画面外に配置していた場合は、Positionの値を変更することで画面内にたこ焼きが表示されるようになります。次に「Enemy」オブジェクトを選択した状態で、Inspectorビューの「Add Component」ボタンをクリックします。
そうすると追加するコンポーネントのダイアログが表示されます。コンポーネントには「Physics 2D」を選び、次に表示されるコンポーネントから「Rigidbody 2D」を選択します。
そうすると、Inspectorビュー内に「Rigidbody 2D」コンポーネントが追加されます。「Rigidbody 2D」とは物理的な挙動をシミュレートするコンポーネントです。なお、このようにゲームオブジェクトにコンポーネントを追加することを「アタッチ」と呼びます。今後「アタッチ」という単語が出たら、それは「コンポーネントをゲームオブジェクトに追加すること」という意味となります。
ではこの状態でゲームを開始してみます。Unityエディタの画面上中央にある再生ボタンをクリックします。
するとGameビューにある「たこ焼き」が落下していきます。見逃してしまった場合は、再生ボタンを押して停止し、もう一度再生ボタンを押して挙動を確認します。ただ、たこ焼きが自然落下するのは、万有引力の重力の法則に従った物理的に正しい挙動とはいえ、今回作るゲームでは必要ないものなので、これを無効にします。Inspectorビューから、Rigidbody 2Dの「Gravity Scale」の値を「0」にします。
これで重力を無効にできます。そうしたら再生ボタンを押してゲームを開始します。するとたこ焼きが落下しなくなります。
スクリプトの追加
たこ焼きの動きをスクリプトで制御します。スクリプトとはゲームの挙動を言葉で記述したものとなります。スクリプトを作成する準備として、まずProjectビューからAssetsフォルダを右クリックして、Create > Folderでフォルダを作成します。フォルダ名は「Scripts」とします。
フォルダを作成できたら素材フォルダの「Materials/minigame/Scripts」フォルダにある、2つのスクリプト「Token.cs」「Util.cs」を、UnityエディタのProectビューの「Assets/Scripts」フォルダにドラッグ&ドロップします。
「Token.cs」「Util.cs」のアイコンは別のアイコン画像になっているかもしれませんが、そこはあまり気にしなくて大丈夫です。ドラッグ&ドロップして追加すると、Projectビューにはこのように表示されます。
これらのスクリプトは、ゲームオブジェクトをスクリプトで制御する際に、簡単な記述で制御できるようにした便利なスクリプトとなります。
なおToken.cs
についてのスクリプトの説明は、以下のページで解説しています。
Enemyスクリプトの追加
続いて敵(たこ焼き)のスクリプトを作成します。Projectビューの「Assets/Scripts」フォルダを右クリックして、「Create > C# Script」を選択します。
そうしたら「Enemy」スクリプトをダブルクリックします。そうすると対応するエディタが立ち上がります。標準ではUnity付属の「MonoDevelop」が起動します。ここからスクリプトを編集します。スクリプトは以下のように記述します。
using UnityEngine;
using System.Collections;
/// 敵
public class Enemy : Token
{
/// 開始
void Start()
{
// ランダムな方向に移動する
// 方向をランダムに決める
float dir = Random.Range(0, 359);
// 速さは2
float spd = 2;
SetVelocity(dir, spd);
}
}
Update関数や余計なコメントは消しました。修正箇所ですが、継承元となるクラスをMonoBehaviourから「Token」へ変更しています。Tokenクラスとは先ほど追加したToken.csスクリプトに定義されているクラスです。そしてStart関数にランダムな方向に移動する処理を記述しています。Start関数とはオブジェクトのインスタンス化(生成)時に呼び出される関数です。この関数に処理を記述することで、初期状態の情報を設定することができます。そしてRandom.Rangeというのは、指定した引数の範囲をランダムで返す関数です。ここでは「0」と「359」を指定しているので、0~359の値をランダムで返すことになります。角度の扱いについて説明します。
角度は0~360の値を取り、左回りで回転します。360度でちょうど一回転します。つまりこのスクリプトでは、どの方向に進むかはランダムでわからない、という記述となっているわけです。そして次の行では、速さを2として、移動パラメータの設定関数 SetVelocity を呼び出しています。
ではUnityエディタに戻って、再生ボタンを押して実行してみます(※実行ボタンを押した時にSceneビューにメッセージが表示されるエラーとなる場合は、コラム「エラーになる場合の対処方法」を参照してください)。
しかし何も起きないですね……。これはEnemyオブジェクトにEnemyスクリプトがアタッチされていないためです。「アタッチ」というのは、ゲームオブジェクトにコンポーネントを追加する、という意味でしたよね。スクリプトコンポーネントをアタッチするには2つの方法があります。
- 「Add Component」ボタンをクリックして該当のスクリプトを選ぶ
- ゲームオブジェクトに、スクリプトをドラッグ&ドロップする
今回はドラッグ&ドロップする方法でアタッチします。EnemyスクリプトをEnemyオブジェクトの上にドラッグ&ドロップします。
すると、EnemyオブジェクトにScriptコンポーネントが追加され、Enemyスクリプトがアタッチされます。
では実行してみます。再生ボタンを押して実行すると、たこ焼きがランダムな方向に移動し始めます。
コラム:エラーが発生する場合
スクリプトを編集して実行した際に、エラーとなって動作しないことがあります。例えば「Enemy」スクリプトのStart関数で速度を設定していた関数 SetVelocity を SetVelocty(iを忘れた)と入力したとします。その状態でUnityエディタに戻って実行すると、「All compiler errors have to be fixed before you can enter playmode!」というエラーメッセージが表示されます。
人はエラーが起きると焦ってしまいますが、ここはクールに大人の対応をしましょう。エラーが発生すると、Unityエディタの左下にエラーの赤い丸が表示されます。
エラーが起きたら、この赤い丸をクリックしてみてください。そうするとエラーなどのログを表示するコンソールウィンドウが出現します。
英語のメッセージなので、読むと頭が痛くなりそうですが、書いてあることはそんなに難しくありません。
Assets/Scripts/Enemy.cs(15,5): error CS0103: The name 'SetVelocty' does not exist in the current context
まず注目するのはファイル名です。最初に「Assets/Scripts/Enemy.cs」とあり、Enemyスクリプトで何かエラーが起きているのだな、ということがわかります。そしてその後ろの括弧にはエラーが起きた行数と列数が表示されます。つまり、Enemyスクリプトの15行目の5列目に定義している何かがエラーを起こしている、ということがわかります。その後のメッセージもSetVelocty
なんてものはないよ、という意味となります。
エラーになる原因は大きく分けて2つです。コンパイルエラーとランタイムエラーです。コンパイルエラーとはスクリプトの文法の誤りを指摘してくれるエラーです。例えば以下のものです。
- 存在しない関数を指定した
- 存在しない型を定義した
- 括弧「(」の対応が合っていない(閉じ括弧がない)
- 中括弧「{」の対応が合っていない(閉じ括弧がない)
- 式や定義の終わりに「;(セミコロン)」がない
- 全角スペース文字がプログラム中に存在する
たいていはタイポ(入力した文字のミス)であることが多いです。それに対してランタイムエラーは実行時のエラーです。例えば以下のようなものです。
- 登録しないコンポーネントを参照しようとした
- 生成していないオブジェクトを参照しようとした
- 存在しないファイルを参照した
- 配列の領域外にアクセスした
- その他例外が発生した
よくあるのが Rigidbody 2D をアタッチしていない状態で、速度パラメータにアクセスしてnull参照例外となるパターンです。
ただエラーメッセージを見てもエラーの原因がよくわからないことがあります。その場合はログを出力します。たとえば以下のように記述するとコンソールウィンドウにログを出力できます。
Debug.Log("ログの出力");
例えば、ある関数が本当に呼び出されているかどうかを調べるためには、その関数にログを入れます。実行してコンソールウィンドウにそのログが出力されるかどうかを確認します。またこのログには数値や文字を自由に結合して表示できます。
float val = 1.25f;
Debug.Log("valの値:" + val);
このようにすると、"valの値:1.25"というログが出力されます。
それと、スクリプトエディタにMonoDevelopを使っていると、日本語コメントでエラーが発生することがあるようです。その場合は行末に「.(ピリオド)」などを入れるとエラーを回避できます。
// ランダムな方向に移動する.
// 方向をランダムに決める.
float dir = Random.Range(0, 359);
// 速さは2.
float spd = 2;
SetVelocty(dir, spd);
少し面倒ですが、このように「.」をつけると正常に実行できることがあります。
画面の端で跳ね返るようにする
たこ焼きが移動するようになりましたが、そのまま画面から出て行ってしまいます。これでは困るので画面端に到達したら、跳ね返って逆方向に移動するようにします。
Enemyスクリプトを開いて、Update関数を追加します。
/// 敵
public class Enemy : Token
{
……(中略)
/// 更新
void Update()
{
// カメラの左下座標を取得
Vector2 min = GetWorldMin();
// カメラの右上座標を取得する
Vector2 max = GetWorldMax();
if (X < min.x || max.x < X)
{
// 画面外に出たので、X移動量を反転する
VX *= -1;
// 画面内に移動する
ClampScreen();
}
if (Y < min.y || max.y < Y)
{
// 画面外に出たので、Y移動量を反転する
VY *= -1;
// 画面内に移動する
ClampScreen();
}
}
}
Start関数の下あたりに追加しました。このスクリプトの説明ですが、最初の処理で、カメラ内での左下・右上のワールド座標を取得します。Unityは3D空間にオブジェクトを配置するので、スクリーン座標ではなくワールド座標系が基準となります。
次の部分で、画面外に出たら移動方向を反転して画面内に押し戻すようにしています。なお大文字の「X / Y」はオブジェクトのX座標とY座標を表します。そして大文字の「VX / VY」はオブジェクトのX方向・Y方向への移動量を表します。「V」は速さの意味を表す単語「Velocity」の頭文字をとったものとなります。
ではUnityエディタに戻って、再生ボタンを押し、たこ焼きが画面端で跳ね返るのを確認します。ですがスプライトのサイズを考慮していないので、画面端にめり込んでから跳ね返ってますね。これを直します。再びEnemyスクリプトを開いてStart関数を修正します。
/// 開始
void Start()
{
// サイズを設定
SetSize(SpriteWidth / 2, SpriteHeight / 2);
// ランダムな方向に移動する
// 方向をランダムに決める
float dir = Random.Range(0, 359);
// 速さは2
float spd = 2;
SetVelocity(dir, spd);
}
Start関数の最初でサイズを設定するようにしました。「SpriteWidth」「SpriteHeight」はそれぞれスプライトの幅と高さの値を表します。SetSize関数で指定するサイズは、幅と高さを半分にした値となるので、2で割って半分にしています。
修正できたら、Unityエディタに戻って実行します。すると画面端でめり込まずに移動方向が反転するようになります。
クリックすると消滅するようにする
たこ焼きをクリックすると消滅するようにします。消滅判定をするにはコリジョンの追加が必要となります。コリジョンを追加するには、Enemyオブジェクトを選択し、InspectorビューからAdd Componentをクリックします。
そうするとコンポーネント選択画面が表示されるので、「Physics 2D > Circle Collider 2D」を選びます。
Circle Collider 2Dとは、円形コリジョンとなります。コリジョンを追加できたら、HierarchyビューのEnemyオブジェクトをダブルクリックします。そうするとSceneビュー上に配置されているEnemyオブジェクトが拡大して表示されます。
背景が明るいのでわかりにくいかもしれませんが、緑色の丸い円がコリジョンのサイズとなります。この緑色の円がたこ焼きを囲むようになっていればOKです。もしサイズが正しくなければ、InspectorビューからCircle Collider 2Dコンポーネントの値を修正します。
ここのRadius(半径)の値が「0.32」になっているとちょうどよいサイズとなります。
ではコリジョンの設定ができたので、クリック判定を実装します。クリック判定を取るには、OnMouseDownイベントを実装します。Enemyスクリプトを開いて OnMouseDown 関数を追加します。
/// クリックされた
public void OnMouseDown()
{
// 破棄する
DestroyObj();
}
OnMouseDown関数は、そのオブジェクトがマウスでクリックされた時に呼び出される関数です。このような何らかのきっかけで呼び出される関数は「イベント」と呼ばれたりします。
OnMouseDownの実装を説明すると、DestroyObjという関数を呼び出しています。これはオブジェクトをメモリから破棄する関数となります。これもTokenクラスの独自実装の関数です。
ではUnityエディタに戻って、再生ボタンを押して実行してみます。たこ焼きをクリックして消滅するようになればOKです。
たこ焼きを複数配置する
たこ焼きを複数します。複数配置するには、現在のEnemyオブジェクトをそのまま複製してもよいのですが、「プレハブ」という仕組みを使います。プレハブというのはゲームオブジェクトを再利用しやすくなる仕組みです。
ゲームオブジェクトをそのまま複製すると、別のゲームオブジェクトとなってしまいます。ですがプレハブ化すると元となるゲームオブジェクトは共通となるので、パラメータを変更したい場合はプレハブを修正すればすべてのゲームオブジェクトにそのパラメータが反映されるようになります。
ではEnemyオブジェクトをプレハブ化します。まずはプレハブを保存するフォルダを作成します。ProjectビューのAssetsフォルダを右クリックして、Create > Folderでフォルダを作成します。そして名前を「Resources」とします。そして、「Resources」フォルダを右クリックしてCretae > Folderでさらにフォルダを作成します。名前は「Prefabs」とします。
フォルダ階層は「Assets/Resources/Prefabs」となりました。ここにプレハブを作成していきます。
HierarchyビューのEnemyオブジェクトを選択して、ProjectビューのAssets/Resources/Prefabsフォルダにドラッグ&ドロップします。
そうするとPrefabsフォルダに、Enemyプレハブ(水色の箱アイコン)が作られます。
プレハブが作られたら、Enemyオブジェクトは削除します。削除するにはEnemyオブジェクトを選択してDeleteキーを押したり、右クリックして「Delete」を選ぶことで削除できます。
Enemyオブジェクトを削除したら、Enemyプレハブを選択して、Sceneビューにドラッグ&ドロップで配置します。
とりあえず4つほど配置してみました。図のようにばらけて配置するとよいでしょう。Scenenビューはマウスのホイールを上下に動かすことでズームインしたりズームアウトできます。また配置したオブジェクトを移動するには、オブジェクトの真ん中あたりをクリックすると、ドラッグして移動することができます。
配置できたら、実行して動作を確認します。
次回
次回で、演出周りとシーンの遷移を作って完成とします。