敵当たり判定とコルーチンを用いた弓矢の生成&射出処理の作成

Unity タワーディフェンスゲームの作り方


Unityの本格ゲーム制作講座はこちら
【30日間の全額返金保証付き】

前回(第6回)では、ウェーブを作って、敵オブジェクトの出現パターンを制御する仕組みを用意しました。

前回の記事↓

敵の生成処理の作り方 ウェーブの作成とレベルデザインまで
前回(第5回)では、経路に沿って実際に敵オブジェクトが移動する仕組みなどを作りました。 前回の記事↓ 今回は敵オブジェクトの作成を制御する仕組みを作っていきます。 Waveの概念も入ってきてあなたのレベルデザインの見せ所になります...

見た目だけは大分ゲームらしくなってきましたが、まだ敵を倒すことが出来ません。 今回は敵の当たり判定(Collider2D)の作成、弓(Bow)と矢(Arrow)の作成をやっていきたいと思います。

通常、ゲームを作る場合はプレイヤー(遊ぶ人)の手触りの部分ー
例えばシューティングゲームなら自機の移動や弾を撃つところ -
などから作り始めることが多いのですが、タワーディフェンスは「守る」ゲームなので敵が来ないと始まらないため、このような順番になっています。

【Unity入門の森の最新ゲーム開発講座もお届け】

敵オブジェクトに当たり判定を付ける

敵オブジェクトを攻撃(探索)するためには、画像だけでは駄目で、当たり判定(Collider2D)レイヤーをセット必要があります。

用意した敵オブジェクトのprefabが Assets/Prefabs の中にあると思います。

まず、このEnemy (1) に当たり判定(Collider2D)を追加したいです。

既に作ってあるプレハブに何かComponentを追加するには「プレハブ編集モード」に入る必要があります。

Enemy(1) をダブルクリックしてください。

すると、HierarchyビューとSceneビューが、このようにダブルクリックしたEnemy(1)のコンテンツだけになると思います。

これが「プレハブ編集モード」です。  

敵キャラの当たり判定(Collider2D)の追加

この状態で Enemy(1) を選択し、InspectorビューAddComponentをします。

この敵は四角いので、BoxCollider2D というものを追加します。

この薄い緑色の線が当たり判定です。
ですが見ての通り実際の画像に比べてあたり判定が大きくなってしまっているので調整しましょう。

InspectorのBox Collider 2DEdit Collider というボタンを押します(画像内赤枠)

するとSceneの方のCollider表示に緑の小さい■がつきます。

これをドラッグすることで、Sizeを変更することができます。画像に合わせて調整をしてください。

(※もしくは、Size の X に直接 0.6 と入れてもよいです。マウス操作が苦手な方はこちらの方法で)

敵キャラクターのレイヤーの指定

次に、レイヤーの指定をします。

Inspectorの上部に Layer という項目があります。何も設定していないので、「Default」になっているはずです。

このLayerをクリックしてドロップダウンからEnemyを選択します。

すると、以下のダイアログも表示されると思います。 これは、子要素を持つオブジェクトのレイヤーを設定しようとすると必ず出てきます。

子要素のLayerも一緒に変更するかどうか。
を尋ねられているので、Yes change children を選択します。

Layerの設定が終わりましたら、プレハブ編集モードから抜けます。

プレハブ編集モードから抜けるにはHierarchyビューの「<」またはSceneビューの「Scenes」をクリックします。

その他全てのEnemyオブジェクトに当たり判定とLayerの設定を行いましょう。

同じ要領で、Enemy(2)にもColliderを付け、LayerをEnemyに変更します。

最後に、Enemy にもColliderを付けたいのですが、Enemyは四角ではなく楕円なので、BoxCollider2D ではなくCopsulCollider2Dを付けます

こちらもちょっと画像とColliderがあっていないので、Edit Colliderボタンを押して修正します。

(※もしくは、Size に直接 X 0.6、Y 0.97 と入れてもよいです) そして、忘れずにLayerをEnemyに変更しておいてください。

これで、攻撃をする準備が出来ました。

弓(Bow)の作成 コルーチンを用いて指定時間ごとに処理を行う

次に攻撃をするためのユニット、弓(Bow)を作りましょう。まず、弓の空オブジェクトを作っていきます。

Hierarchyビューに、新規オブジェクト(CTRL+SHIFT+N)を作成し、新規ScriptもAddComponentしておきます(いつもの流れですね)

  • 名前:Bow
  • スクリプト名:Bow

なお、Transform の Position は

  • X : -2
  • Y : -2
  • Z : 0

にしておいてください。 次に弓の画像として、Assets/Images/item_bowを使います。

このitem_bowHierarchyビューの先ほど作ったBowオブジェクトの子要素になるようにドラッグアンドドロップします。 この子要素として配置されたitem_bowは

  • Transform を Reset(PositionのX,Y,Zを0に)
  • Order in Layer を 10

にしておきます。

今から作成するのは弓なのですが、矢がないとちょっと分かりづらいですね。

というわけで、矢の画像も追加しましょう。

Assets/Images/item_arrowを使います。
このitem_arrowHierarchyビューの先ほど作ったBowオブジェクトの子要素になるようにドラッグアンドドロップします。

この子要素として配置されたitem_arrowは

  • Transform を Reset(PositionのX,Y,Zを0に)
  • Order in Layer を 11

にしておきます。

Hierarchy上はこのような階層になります。

では、Bowスクリプトの中身を書いていきます。 このBowに必要な要件としては

  • 一定間隔で周囲の敵を探す
  • 矢(Arrow)を撃つ

になります。
まだ、矢(Arrow)は作ってませんので、この「一定間隔で周囲の敵を探す」を実装していきます。

この「一定間隔」で処理を行いたい場合、Update関数で上手く時間を計測して処理をするように書くこともできますが、コルーチンという機能を使うと直感的に処理を書くことが出来ます。

Bowスクリプトを以下のように書き換えます。

  まず、Update関数は必要ないので削除しました。

次に、Start関数内で、StartCoroutineを呼んでいます。

これが、先ほど書いた「コルーチン」という機能の開始をする処理です。

普通の関数とコルーチンの違いは、ざっくり言うと

  • 普通の関数 : 処理を開始すると終了まで(returnまで)処理は止められない
  • コルーチン:処理の中断が出来、また中断した場所から再開が出来る。

といった特徴になります。

「処理の中断」と「再開」がピンとこないかもしれませんが、StartCoroutineによって開始された処理、SearchAndShot関数を見ると

なかでは while(true) が使われています。

これは終了条件が無いので無限ループします。
しかし、17行目で yield return という見慣れない構文が出てきます。この1行が「処理の中断」です

Unityでのコルーチンの中では yield return が出てきたら、そのタイミングで一度処理が中断されます。

では、「再開」は?というと、そのyield returnの後ろの

new WaitForSeconds(1.0f);

がそれを指定しています。

この new WaitForSeconds(1.0f)1.0秒待つ という意味で、転じて、1.0秒待ったら再開する。 という意味になります。

補足:その他にもyield return の後ろに書けるものがあります。
yield return null;
とすれば1フレーム待つことになりますし、
yield return new WaitForEndOfFrame();
とすると 今のフレームが終わる(描画まで終わる)のを待ちます。

これはスクリーンショットの取得などで使用したりします また
yield return new WaitUntil( () => Input.anyKey );
このように、ラムダ式(正確にはdelegate)という記述を使って、条件を指定する方法もあります。(この場合は、何かキー(マウスボタン)が押されるまで待つ という意味になります。)

敵を探すプログラム実装 一定範囲の敵を探して当たり判定を行う

では、弓から範囲内の敵を探すプログラムをBowスクリプトに追加していきます。

先ほど作った SearchAndShot関数の中を以下のように書き換えます。

この

で使われている Physics2D.OverlapCircle 関数が、2Dでの円の当たり判定結果を返却をしてくれる関数になり、結果はローカル変数 collider に格納しています。

この関数の引数はいろんなパターンがあるのですが、今回は Physics2D.OverlapCircle( 円の中心位置,半径,対象レイヤー) を使用しており、それぞれ

  • 円の中心位置:Bowオブジェクトの位置そのもの(transform.position)
  • 円の半径:2.0f
  • 対象レイヤー:Enemyレイヤー → LayerMask.GetMask(“Enemy”)

を指定しています。

そして、もしどの敵オブジェクトも範囲内いない場合は collider が null になるため、逆に「nullじゃなければ処理を行う」というif文を書いています。

本当はここで矢(Arrow)を撃つのですが、まだ矢(Arrow)はないので、代わりに敵をDestroyしてしまうことにしましょう。

これで、矢の範囲内に入った敵がDestroyされ消えればOKです。

矢(Arrow)の作成 敵に向かって飛んでいき当たったら敵HPを減らす

では次に敵に飛んでいく矢(Arrow)を作っていきます。

Assets/Images/item_arrow を Hierarchyビューにドラッグアンドドロップしてオブジェクト化し、以下のように設定、スクリプトの追加をします。

  • オブジェクト名:Arrow
  • SpriteRenderer → Order in Layer : 20
  • Add Component → New Script : Arrow

Arrowスクリプトは以下のように修正をします。

  Arrowオブジェクトは、

どの敵オブジェクトに向かって飛ぶかが外(Bow)から指定できるようにし、

指定敵オブジェクトの位置へと向かうベクトルを求めて、移動させています。(この辺りは、タワーディフェンス講座 5/10 のEnemyの移動と同じ仕組みですね)

そして、敵との距離(magnitude)がある程度小さく(ここでは0.7f)なったら、敵に当たったとみなします。

敵に当たったら、当然敵にダメージを与え、敵のHPが0になったら敵を削除(Destroy(targetEnemy.gameObject))し、矢も削除(Destroy(gameObject))します。

ArrowオブジェクトのPrefab化

作ったArrowオブジェクトはPrefab化しておきます。
Assets/Prefabs にドラッグしてPrefab化をしたら、Hierarchyビュー上のArrowオブジェクトは削除してしまってください。

BowからArrowを生成する仕組み 敵方向に向けて矢を飛ばす

では、作成したArrow Prefabを、Bowから生成できるようにします。以下のように書き換えましょう。

まず8行め、

生成する ArrowプレハブをInspectorビューで設定できるように、メンバ変数を追加しています。

そして、今は範囲内に入った敵オブジェクトをDestroyして削除してしまっていますが、これを

  • 敵の方向に向く
  • arrowPerfabを生成(複製)する
  • Arrowにどの敵に向かって飛ぶかセット

に変更しています。

これが、発見した敵の方向を向く処理です。

Quaternion.FromToRotation ( もともと向いている方向ベクトル,向かせたい方向ベクトル) を指定する関数になります。

弓は右を向いているので Vector3.Right を、 向かせたいのは敵の方向のため、敵の方向へのベクトル(第5回参照、引き算で求めるんでしたね)を指定するため
collider.transform.position - transform.position をセットします。

次に、Arrowプレハブから、実際にArrowオブジェクトを生成する処理です。

この時、transform.position と transform.rotation を渡しているため、生成されたタイミングで場所はBowと同じ場所、向き(回転)もBowと同じ向き=敵の方を向いた状態 となります。

そして、Arrowオブジェクトは「targetEnemy = 目標とする敵」をセットすることで、その方向へ飛ぶようにスクリプトを組んであるため、collider(範囲内の当たったオブジェクト)から GetComponent<Enemy>() で、Enemyオブジェクトを抽出して、渡しています。

InspectorでArrowをセット

では、Bowを選択し、InspectorでArrow Prefabをセットします。

では、UnityEditorで再生して確認をしてみましょう。

無事矢が飛び、敵が倒せるようになりました!(※上手くいっていない方はScriptが保存されているかどうか、InspectorでPrefabがセットされているか等を確認してください)

+α 演出追加 親オブジェクト変更とスクリプト無効化で矢を刺さったままにする

※ここは余裕が無い方は飛ばして頂いて構いません。

Arrow(矢)が飛ぶようになりましたが、黄色や紫の敵のようにHPが1以上ある場合はどれくらいダメージを与えたか分からないですよね。

HPゲージを付けるのももちろん正解の一つですが、今回はちょっと変わった表現として「矢が刺さったまま」にしてみます。

刺さっている矢の数=与えたダメージの数。というわけです。

Arrowスクリプトの、Destroy(gameObject);で矢そのものを削除している箇所を以下のように修正します。

  この、transform.SetParent で、親オブジェクトを対象のEnemyにしています(正確にはEnemy の Transform)。

こうすることで、このArrowオブジェクトは当たったEnemyオブジェクトの子オブジェクトになります。

次にArrowオブジェクトのUpdate関数が呼ばれ続けると、既に当たった敵に向かって移動し続けてしまうので enabled = false; として、スクリプトそのものを無効化しています。

これにより、Update関数は呼ばれなくなるため移動も止まり、Enemyの子オブジェクトになっているためまるで刺さったようになります。

 

BowオブジェクトのPrefab化

さて、弓(Bow)から無事矢(Arrow)が発射され、敵も倒せるようになりました。

ただ、大量の敵が迫ってくるのに弓(Bow)一つでは困りますよね。

Bowオブジェクトは複数配置することになるのでPrefab化をしておいてください。

Assets/Prefabs にドラッグしてPrefab化をしたら、Hierarchyビュー上のBowオブジェクトは削除してしまってください。

Prefabも増えてきましたね

おさらいと次回予告

今回は当たり判定の設定と、弓矢の作成をしました。

ついに攻撃が出来ました。 次回はこの弓矢をプレイヤーが配置出来るようにしていきます。

次回の記事↓

プレイヤーの管理処理の作成 HPやゴールドの表示・弓矢のレベルアップ処理の作成
前回は弓(Bow)と矢(Arrow)を作成し、敵(Enemy)に攻撃できるようになりました。 前回の記事↓ 今回はプレイヤー(Player)を作成し、弓矢の配置やUI(HPなど)の表示をやっていきます。 プレイヤー(Player)...

【Unity入門の森の最新ゲーム開発講座もお届け】

コメント

  1. 匿名 より:

    弓矢を作成した段階で実行を押すとUnity自体がフリーズしてしまい先に進めません。
    弓矢のオブジェクトを削除すると動くのでBowScriptに問題があるのだと思いますがエラーが出ないため解決策が分かりません。
    どうすればいいでしょうか?解決策などがあれば教えて頂けると幸いです。

    • ばこ@Unity入門の森 より:

      最後までちゃんとできるはずですし、できた読者さんがいるので過去の記事含めて作ってる途中でどこかミスしてるのかなと思います。
      この質問だとちょっと答えようがないです汗

  2. TK より:

    Physics2Dのところですが、弓の向きを固定し、円の範囲を変えて「自分より前の長方形の形」にする関数はありますか?
    イメージだとこんな感じです↓

    ➡:弓とその向き
    □:範囲

    □□□□
    ➡□□□
    □□□□

    お手数をおかけしますがよろしくお願いします。

タイトルとURLをコピーしました