Unity 5で 2Dシューティングのチュートリアルを行う際の注意点(第05回)


Unity公式チュートリアル 2Dシューティング

第05回 当たり判定とアニメーションイベントとレイヤー


上記チュートリアルで、私がつまずいたところ、初心者がつまずきそうなところをピックアップして解説します。

まとめへ戻る≪第04回へ第06回へ≫



5.1 プレイヤーに当たり判定を付ける

PlayerにBox Collider 2Dをアタッチします。
SizeはX 0.02 Y 0.02で、(おおよそ)1ドットの大きさの当たり判定にします。

Hierarchyタブ内のPlayerを選択し、Inspectorの一番下に表示されている「Add Component」をクリック、Physics 2D -> Box Collider 2Dを選択して、アタッチします。
その後、Inspector内に追加された、Box Collider 2Dの中にあるSizeを書き換えます。


なお、ここで注意すべきことは、Sizeに 0.02を入れるとコライダーが機能しなくなる点です。
これはチュートリアルページの一番上のところに「Unity4.5.0でチュートリアルを行う方へ」という注意書きとして書いてあるとおり、0.05以上でないと機能しないようなので、「0.05」にしましょう。

なお、図のようにSceneの中のオブジェクトを中心に表示させたい時は、Hierarchyタブ内のオブジェクトをダブルクリックします。
そしてホイールを向こう側へ転がすと、オブジェクトを拡大表示させることができます。

トリガーにする

Playerをトリガーにします。Box Collider 2DのIs Triggerにチェックを付けてください。

Hierarchyタブ内のPlayerを選択し、Inspectorの一番下のBox Collider 2D内の「Is Trigger」にチェックを入れます。

5.2 エネミーに当たり判定を付ける

EnemyにPolygon Collider 2Dをアタッチします。ですが、コライダーの大きさがエネミーの大きさに会わず不自然なものとなってしまいます。

Hierarchyタブ内のEnemyを選択し、Inspectorの一番下に表示されている「Add Component」をクリック、Physics 2D -> Polygon Collider 2Dを選択して、アタッチします。

Polygon Collider 2Dを編集する

編集の仕方

コライダーの編集はShiftを押しながら行います。マウスをコライダーの緑色の線に合わせShiftを押します。そうすると緑色の■が表示されクリックするとその部分を頂点として動かすことが可能です。

このように記載されていますが、Unity5では、Shiftを押しても何も起こりません。
おそらくバージョンが上がった時に、やり方が変わってしまったのでしょう。
緑色の■を表示させるには、Inspector内のPolygon Collider 2Dのところにある「Edit Colider」ボタンをクリックします。

すると、マウスを領域の角に近づけた時に■が現われるので、それをドラッグして編集できます。
なお、あまりいじりすぎると領域の形がおかしな感じになるので、そこそこにとどめましょう。

トリガーにする

Enemyをトリガーにします。Polygon Collider 2DのIs Triggerにチェックを付けてください。

Hierarchyタブ内のEnemyを選択し、Inspectorの一番下のBox Collider 2D内の「Is Trigger」にチェックを入れます。

5.3 弾に当たり判定を付ける

コライダーをアタッチ


PlayerBulletの2つのBulletにはPolygon Collider 2Dを、EnemyBulletにはCircle Collider 2Dをアタッチします。

まず、説明では省かれていますが、チュートリアルの画像にあるように、プレハブのPlayerBulletとEmenyBulletをSceneへドラッグ&ドロップします。
具体的には、下の図のように、Projectタブ内のPrefabsフォルダ配下にあるPlayerBulletをSceneへドロップし、同様にPrefabsフォルダ配下にあるEnemyBulletもSceneへドロップします。

次に、PlayerBulletの2つのBulletにはPolygon Collider 2Dをアタッチします。
具体的には、まず、Hierarchyフォルダ配下のPlayerBulletの下にぶら下がっている2つのBulletを両方選択します(一つクリックして選択後、もうひとつをCtrlを押しながらクリック)。
そしてInspectorの「Add Component」をクリックし、Physics 2D -> Polygon Collider 2Dを選択して、2つのBulletに対し一度にPolygon Collider 2Dを追加します。


最後に、EnemyBulletにCircle Collider 2Dをアタッチします。
具体的には、まず、Hierarchyフォルダ配下のEnemyBulletをクリックで選択後、Inspector内の「Add Component」をクリックし、Physics 2D -> Circle Collider 2D を選択します。

Circle Collider 2DのRudiusを0.07にしてスプライトと同じ大きさにしましょう。

Hierarchyフォルダ配下のEnemyBulletをクリックで選択後、Inspectorの中のCircle Collider 2D内にあるRadiusを修正します。
すると、ちょうどドットの回りを囲んでいた緑の線が、ドットの周囲にフィットする感じに小さくなります。

同じ形状のコライダーを作成するのは手間がかかるので片方ができたらCopy ComponentでコピーしてPaste Component Valuesを使うようにしましょう。

これは、Bulletのコライダーの形状を編集する際のことを述べています。
まず、Hierarchyタブ内のPlayerBulletフォルダ配下に2つあるBulletのうち、どちらか一つを選択し、InspectorのPolygon Collider 2D内にある「Edit Colider」ボタンをクリックしてから、コライダーの形状を編集しましょう。
編集時はBulletをダブルクリックしてSceneの中央に寄せ、ホイールで拡大してから行うと楽だと思います。
良い感じに編集できたら、「Edit Colider」ボタンをクリックして編集モードを解除します。
その後、Inspector内の「Polygon Collider 2D」の文字のあたりで右クリックし、「Copy Component」を選択します。
すると、編集したコライダーの形状がクリップボードにコピーされるので、これをもうひとつのBulletにペーストします。
具体的には、もうひとつのBulletをHierarchy内で選択し、Inspector内の「Polygon Collider 2D」の文字のあたりで右クリックし、「Paste Component Values」を選択します。
これでコライダーの形状がもうひとつのBulletにコピーできました。

トリガーにする

コライダーをトリガーにします。PlayerBulletの2つのBulletとEnemyBulletのコライダーのIs Triggerにチェックを入れてください。

Hierarchyタブ内の2つのBulletを両方選択し、Inspectorに表示されるPolygon Collider 2D内のIs Triggerにチェックを入れます。
次に、Hierarchyタブ内のEnemyBulletを選択し、Inspector内に表示されるCircle Collider 2D内のIs Triggerにチェックを入れます。

ここまで出来たらPrefabを更新してそれぞれの弾をシーン上から削除しましょう。

Hierarchyタブ内の片方のBulletを選択し、Inspector内の上の方の「Prefab」の横にある「Apply」ボタンを押してPrefabを更新します。
同様の手順で、もう片方のBulletと、EnemyBulletもPrefabを更新します。


すべてのPrefabが更新できたら、Sceneから更新の終わったオブジェクトを削除します。
具体的には、HierarchyタブのPlayerBulletを選択後、Deleteキーで削除します。
HierarchyタブのEnemyBulletも同様に削除します。

5.4 スクリプトから当たり判定を検出する

まず、Spaceship.csにExplosionゲームオブジェクトを作成するためのコードを記述します。

ProjectタブのScriptsフォルダ配下にあるSpaceshipをダブルクリックし、MonoDevelopでSpaceship.csを編集します。
Spaceship.csをチュートリアルのソースの内容に置き換えます。
変更点は、爆発用のゲームオブジェクト変数explosionの追加と、爆発を生成するExplosion関数の追加です。
Ctrl+Sで保存してUnityエディタに戻ったら、ダイアログが出るので「Go Ahead!」を押し、スクリプトを自動修正してもらいましょう。

PlayerとEnemyのそれぞれ、インスペクター上でSpaceshipにExplosionのPrefabを格納してください。

Spaceship.csに変数を追加したことで、Inspector上に変数Explosionが表示されるようになります。
ここにExplosionのPrefabを格納していきます。


具体的には、HierarchyタブのPlayerを選択し、Inspector内に表示される Spaceship(Script)内にあるExplosionの枠に、ProjectタブのPrefabsフォルダ配下にあるExplosionをドラッグ&ドロップします。

同様に、HierarchyタブのEnemyを選択し、Inspector内に表示される Spaceship(Script)内にあるExplosionの枠に、ProjectタブのPrefabsフォルダ配下にあるExplosionをドラッグ&ドロップします。

Playerの当たり判定を検出するために、コードを追加します。

ProjectタブのScriptsフォルダ配下にあるPlayerをダブルクリックし、MonoDevelopでPlayer.csを編集します。
Player.csをチュートリアルのソースの内容に置き換えます。
変更点は、OnTriggerEnter2Dの追加です。
Player.csをCtrl+Sで保存したら、Unityエディタに戻りましょう。
#今回は、「Go Ahead!」のあるボタンは出ないはずです。

5.5 爆発の制御

アニメーションのループ設定


Explodeのアニメーションファイルを選択し、インスペクター上に表示される「Loop Time」のチェックを外してください。

ProjectタブのAnimations/Explosionフォルダ配下にあるExlopdeを選択し、Inspectorに表示される「Loop Time」のチェックを外します。

5.6 爆発した後のゲームオブジェクト削除

爆発のアニメーションがループしなくなったとしても、爆発のゲームオブジェクトは残り続けます。


そこでアニメーションが終わった後、スクリプトのDestroy関数を使用してゲームオブジェクトを削除します。


Explosion.csファイルを作成し、ExplosionのPrefabにアタッチしてください。

まず、ProjectタブのScriptsフォルダを選択後、Scriptsフォルダを右クリックしてCreate -> C# Scriptを選択し、生成されたスクリプト(NewBehaviourScript)を、Explosionにリネームします。
その後、今作ったExplosionをダブルクリックしてMonoDevelopで開き、内容をチュートリアルのソースで置き換えます。
ソースをCtrl+Sで保存したら、Unityエディタに戻ります。


次に、ProjectタブのPrefabsフォルダ配下のExplosionを選択し、Inspector内のAdd Componentボタンをクリック、Scripts -> Exlosionと選択して、Explosionをアタッチします。

アニメーションイベント

AnimationEventを追加します。図5.17を見ながらイベントの追加を行ってください。

チュートリアルの図5.17にある
1.Add Eventボタンを押す
2.画像の位置までドラッグ
まではそのままできるのですが、
3.OnAnimationFinish()を選択
だけは、どうしたら画像にある「Edit Animation Event」ダイアログが表示されるのか、記述されていません。

これは、2でドラッグしたつまみをダブルクリックすると、表示させることができます。

「Edit Animation Event」ダイアログが表示されたら、「(No Function Selected)」ボタンをクリックし、表示される選択肢から「OnAnimationFinish」を選択します。
選択後、ダイアログを×ボタンで閉じます。

イベントの追加が終わったらExplosionのゲームオブジェクトを削除します。

Hierarchyタブ内のExplosionを選択し、Deleteキーで削除します。


※このときは、削除前にプレハブの更新をしなくても、問題ないようです(削除後プレハブから再度ゲームオブジェクトを生成し、アニメーションイベントを確認しても、ちゃんと変更が反映されています)。

5.7 弾とエネミーが削除されるエリアを作る

今のままだと発射した弾やエネミーは延々と画面の外へ移動してしまいます。
そこで、弾やエネミーが削除される範囲を作成しましょう。
空のGameObjectを作成し、名前をDestroyAreaとしました。さらにBox Collider 2Dをアタッチします。
Is Triggerにチェックを入れ、Sizeは X 9 Y 7 とします。

メニューから「Game Object -> Create Empty」を選択し、空のGameObjectを作成します。
Hierarchyタブに「GameObject」が追加されるので、これをDestroyAreaにリネームします。
リネームするには、GameObjectをクリック後、もう一度クリックするか、F2を押します。

次に、作成したDestroyAreaを選択後、Inspectorの「Add Component」ボタンをクリック、Physics 2D -> Box Collider 2Dと選択し、Box Collider 2Dを追加します。
Box Collider 2DのIs Triggerにチェックを入れ、サイズを編集します。
ただ、注意して欲しいのは、チュートリアルの画像とSizeの位置が変わっているところです。
チュートリアルの画像では、Sizeは(Centerの)上にありますが、Unity5では、Sizeは(Offsetの)下にあるので注意してください。

このDestroyAreaに弾が当たり、DestroyArea外に出てしまったら削除されるようにします。
DestroyArea.csを作成し、DestroyAreaにアタッチします。

ProjectタブのScriptsフォルダを右クリックし、Create -> C# Scriptを選択し、追加されたものをDestroyAreaにリネームします。
そしてそれをダブルクリックし、MonoDevelopでDestroyArea.csを編集します。
DestroyArea.csの内容は、チュートリアルのソースにまるごと置き換えてしまって構いません。
ソースが置換できたらCtrl+Sで保存し、Unity画面に戻りましょう。

次に、DestroyAreaゲームオブジェクトに、DestroyArea.csをアタッチします。
具体的には、HierarchyのDestroyAreaをクリック後、ProjectタブのScript配下にあるDestroyAreaをInspectorの枠にドロップします(ここはチュートリアルの画面の通りです)。

レイヤーで当たり判定の制御

レイヤーの設定


レイヤーを使うために、レイヤーの登録を行います。Edit → Project Settings → Tags and Layersを選択してください。


今回はレイヤーを6つ用意します。

1: Player
2: Enemy
3: Bullet (Player)
4: Bullet (Enemy)
5: DestroyArea

「ここでレイヤーを6つ用意します」とありますが、上記の通り、5つ用意します。
このとき、レイヤー名は、チュートリアルからコピペしましょう。
後ほど、このレイヤー名をソースの中で使用するので、打ち間違うと正しく動作しなくなります。

以下の画像のようにそれぞれのGameObject、Prefabにレイヤーを設定しましょう。

画像の通り、以下のGameObject、Prefabをひとつひとつ選択し、それぞれに対応するレイヤーを設定していきます。

GameObject 対応レイヤー
DestroyArea DestroyAreaレイヤー
Enemy Enemyレイヤー
Player Playerレイヤー
Prefab 対応レイヤー
Enemy Enemyレイヤー
EnemyBullet Bullet (Enemy)レイヤー
Player Playerレイヤー
PlayerBullet Bullet (Player)レイヤー

このうち、子要素のあるEnemy、PlayerBulletは、レイヤー変更時に子要素にも変更を適用するかどうかを尋ねるダイアログが出ます。
筆者はとりあえず「Yes, change children」を押しておきました。

上記の設定も、GameObject/Prefabでかぶっているものは、GameObjectだけおこなってPrefabに適用(Apply)すれば良いようにも思いましたが、筆者はとりあえず別々に変更を行いました。

スクリプトでレイヤー制御

Layer Collision Matrixによって、ある程度の当たり判定の制御は行うことが可能になりました。


ですが今回は、特定の場合だけ当たり判定を無視したいという状況が発生します。プレイヤーが爆発してしまっている理由で話したような時です。
DestroyAreaに当たった時、OnTriggerExit2Dは呼び出したいけどOnTriggerEnter2Dは呼び出したくない!


今回はその方法の1つとして、スクリプトで対処する方法をご紹介します。


レイヤー情報をgameObjectから取得することが可能です。取得できるのは数値なので、数値からProject SettingsのTags and Layersで設定したレイヤー名を取得します。


ここで気をつけて欲しいのはプレイヤーはエネミーとエネミーの弾2つに当たり判定があるということです。

ProjectタブのScriptsフォルダ配下にあるPlayerをダブルクリックし、MonoDevelopでPlayer.csを編集します。
チュートリアルのソースにまるごと置き換えて構いません。
ソースの変更が終わったら、Ctrl+Sで保存し、Unity画面に戻りましょう。
ソースの変更内容は、以下の通りです。

・OnTriggerEnter2D(当たり判定のあるものにぶつかった瞬間呼び出される)の中でレイヤー名を取得し、必要な場合のみ必要な処理をするよう修正

この変更により、DestroyAreaとの「接触」の場合は完全に無視されるようになっています。
これにより、初めからDestroyAreaの当たり判定内にいるPlayerが、いきなり爆発して消滅することがなくなります。

エネミーの当たり判定

エネミーをプレイヤーの弾に当たったら爆発させ、同時にエネミーを削除します。


コードの追加はプレイヤーと同じくDestroyAreaを考慮し、spaceship.Explosion()を呼び出してExplosionのプレハブから爆発アニメーションを作成します。


そして最後にDestroyでエネミーを削除します。

ProjectタブのScriptsフォルダ配下にあるEnemyをダブルクリックし、MonoDevelopでEnemy.csを編集します。
チュートリアルのソースにまるごと置き換えて構いません。
ソースの変更が終わったら、Ctrl+Sで保存し、Unity画面に戻りましょう。
ソースの変更内容は、以下の通りです。

・OnTriggerEnter2D関数を追加し、Bullet (Player)レイヤーとの接触時のみ、爆発及びゲームオブジェクトの削除を行う

5.8 PlayerBulletゲームオブジェクトの削除

Bulletゲームオブジェクトの削除が行われますが親となるPlayerBulletが削除されていません。このまま弾を撃ち続けるとPlayerBulletがゴミとして残り続けてしまうため削除しなければいけません。


PlayerBulletの削除する方法はいくつか考えられますが、PlayerBulletは特に他に影響を与えるゲームオブジェクトではありません。


従って、最も簡単なn秒後に削除という方法で対処します。

ProjectタブのScriptsフォルダ配下にあるBulletをダブルクリックし、MonoDevelopでBullet.csを編集します。
チュートリアルのソースにまるごと置き換えて構いません。
ソースの変更が終わったら、Ctrl+Sで保存し、Unity画面に戻りましょう。
ダイアログが出るので「Go Ahead!」をクリックして、ソースを修正させましょう。

ソースの変更内容は、以下の通りです。

・lifeTime秒後に自分自身を削除する処理を追加

PlayerBulletは1秒後に、EnemyBulletは5秒後に削除するようにしましょう。

ProjectタブのPrefabsフォルダ配下にあるPlayerBulletとEnemyBulletをそれぞれ選択し、表示されるInspectorのBullet(Script)内にあるSpeedを、それぞれの値に編集します。



番外編:うまくいかなかった時はどうする?


説明の通りにやっているつもりでも、どこかやるべきことが抜けたりすると、チュートリアルの流れの通りには動かなくなります。


筆者も「エネミーの当たり判定」をやったあとで、本来ならば、EnemyがPlayerの弾に当たると爆発して削除されるはずなのですが、何故か爆発せずにすり抜けていき、正しい動作になりませんでした。
レイヤーの設定等、細かく見直したのですが、問題が見つからず、途方に暮れました。


こういう場合に有効なのは、「デバッグすること」です。
筆者の場合は、Playerの弾に当たっているのにEnemyが爆発しないことが問題だったので、Enemy.csのOnTriggerEnter2Dの中にブレークポイントを張り、デバッグしてみました。

ブレークポイントは、ソース編集画面の左端(行番号の左の縦線の左側)をクリックすると、設定/解除できます。
デバッグを行うには、MonoDevelop画面の左上にある右向き三角のボタンを押します。
すると、下図のようなダイアログが出て、しばらくすると「PID|Process Name」の下にUnity画面のプロセス情報が表示されるので、右下の「Attach」を押します。

この状態にしたうえで、Unity画面に戻り、実行ボタンを押すと、デバッグができます。
ブレークポイントに処理が到達すると、MonoDevelopに制御が移ってゲームの処理が中断します。
MonoDevelopの画面を見ると、下図のようにブレークポイントで止まっていることがわかります。

ここでF10キーを押すと、処理を1行ずつ進めることができます。
F11キーを押すと、関数呼び出しのある行ではその関数にジャンプできます。
F5を押すと、処理を再開します。
これらの操作は、メニューのRunの下にまとまっています。


筆者の場合、爆発の行までは到達していたのですが、なぜかその下のエネミーの削除の処理に進みませんでした。
爆発の行でF11を押し、Spaceship.csに定義されているExplosion関数内に入ってみたところ、Instantiateを呼び出しているところの引数のexplosion変数の値がnullになっていました。
#変数の値は、変数のところにマウスを持っていくか、下部の「Locals」で見ることができます。

explosion変数には、本来Explosionゲームオブジェクトが入っているはずですが、ここがnullだったために、処理がここで落ちていたようです。
おそらくInstantiate実行時に例外が発生し、それがどこかでトラップされ、爆発処理もその後につづくEnemyの削除処理も行われないまま、ゲームの流れだけは続行していたのだと思われます。


デバッグを中断し(左上の■ボタンを押す)、Unity画面でゲームも停止して、HierarchyタブのEnemyを選択しInspectorを確認したところ、Spaceship(Script)内のExplosion変数の中身が「None (Game Object)」になっていました。
本来、ここには、第05回の流れの中で、ProjectタブのPrefabsフォルダ配下にあるExplosionをドラッグ&ドロップして設定しておかなければならなかったのですが、その行程を飛ばしてしまっていたようです。


Explosionを正しく設定すると、敵がPlayerの弾に当たった時に、ちゃんと爆発して消えるようになりました。




まとめへ戻る≪第04回へ第06回へ≫