Unity:ツインスティックシューターを作ろう!_3:敵と表示系

・簡単な敵を作ろう

 2周間間が空いてしまった。早く制作日誌を書かないと、やったことを忘れてしまう。前回までで移動とショットができたので次は敵を作ろう。実際にはあちこち散発的に進めていたので実際の手順とは異なるが、まず敵2種類用意した。1つはただひたすらプレイヤーの位置へとゆっくり移動し続けるだけの敵。接触以外にはなにも攻撃をしてこない。もう1体は弾を受けると起動してプレイヤーのいた場所に向けて高速で突進してくる敵だ。
 どちらもプレイヤーのprefabをTransformとして取得して、transform.LookAtで毎フレームプレイヤーに向き続け、Vector3.forwardで進むだけだ。突進する方は起動した瞬間にサーチを切っている。
 プレイヤーとその弾と敵、あるいは敵同士はレイヤーの判定で物理が衝突判定される。ただし弾と敵が接触したら敵にダメージを与え、敵同士だったら何もしない(押し合いはするが)、そんなふうに処理が分かれる必要がある。やり方はいくらでもあると思うが、今回は当たった相手にお互い自分が何者であるかを伝え、各自が挙動を決める方法を取った。

    //何かに当たった時の処理
   void OnCollisionEnter(Collision collision)
   {
       //ここでは当たった相手にプレイヤー側の弾であることを知らせる(レイヤー番号7)
       collision.gameObject.SendMessage("Damage", 7);
   }

   public void Damage(int hitDamage)
   {
       //何に当たっても弾は消滅
       Destroy(gameObject);

       //ヒットエフェクトを発生させる処理
   }

 例えば上のコードは弾の場合で、SendMessage関数で接触するものに必ず存在するDamageという関数に「自分はプレイヤーの弾です」と伝えている。逆に当たった相手からも情報が弾に伝えられる。プレイヤーの弾は何に当たっても相手を問わず、ヒットエフェクトを発生させて消滅する。
 では敵の方はというと、こんな感じ。

    public void Damage(int hitDamage)
   {
       //死んでたら全てのダメージ処理を中断する(連続死亡判定を避けるため)
       if(deadFlag == true)
       {
           return;
       }

       if(hitDamage == 7) //プレイヤーの弾を受けたらダメージ
       {
           enemyHp--;
       }
       else if (hitDamage == 11) //プレイヤーのボムを受けたら死亡
       {
           enemyHp = 0;
       }
       else if (hitDamage == 12) //爆発を受けたら大ダメージ
       {
           enemyHp -= 3;
       }

       //死亡確定したら
       if (enemyHp <= 0)
       {
           //死亡フラグを立てて行動停止
           deadFlag = true;

           //メインスクリプトのゲームマネージャーに敵の得点とスコア上昇倍率を伝える

           //ノックバックフラグを立てて物理(FixedUpdate)で吹っ飛び挙動させる

           //死亡のエフェクトとゲームオブジェクトの破壊を時間差で実行させる
           StartCoroutine("DeathExplosion");
       }
   }

 当たった相手によって処理を変えている。(こういう分岐だったらswitch文とかの方がいいのかもしれない)プレイヤーの弾が当たれば体力を減らす。これが敵同士の接触だとDamage関数は呼ばれるが中では何も処理しない、というふうになっている。
 昔Unity公式で2Dシューティングを作るチュートリアルがあって、そこでは「弾が当たった相手をTagだか何かで調べて、敵だったら相手を消す」なんて処理が書かれていた。当時スクリプト超初心者の僕でもその処理がマズイであろうことは容易に想像できたのを思い出す。敵に体力があったら? 弾に種類があったら? そんな作りをしていたらあっという間に崩壊する。
 情報はやり取りしても、自分の処理は可能な限り自分で行うようにするのがコードを複雑にしないために大切だとなんとなく理解している。「生殺与奪の件を他人に握らせるな!!」ってアレだ。話をややこしくする。
 ところで、SendMessage関数は現在では使用を推奨されないことは知っているんだけれど、新しいメッセージシステムとやらがまだ良くわかっていないのでひとまず知っている方法で実装してしまった。

・敵の発生

 敵の発生はデバッグ用ボタンでランダムに生成しているだけなので特筆することはない。ただ、今回一つ思いついて試したことがあった。

スクリーンショット 2021-10-04 021447

 ゲーム中で敵や弾を発生させるのに、instantiateするprefabをProjectから直接持ってくるのではなく、一旦ゲーム中のシーンファイル内に置いたものを使用した。なぜそんなことをしたかというと、ゲーム中で生成したprefabにはゲームを制御するスクリプトと関連させたり、プレイヤーを参照して行動したりする。
 だがそのためにprefabを生成する度にfindやGetComponent処理を行うのは割と手間だし、処理負荷だって多分無視はできない。
 そこで予めシーンにprefabを読み込んでおき、他のスクリプトやプレイヤーとの接続を先に済ませてしまう。この設定済みprefabをinstantiateすることで手間と負荷を減らそうという寸法だ。正直負荷の方はわからないが、コードで処理する手間は大いに省ける。

スクリーンショット 2021-10-04 234646

 この頭に青いラインが付いている項目は他のGameObjectを取得するなど、そのprefab単体では設定しておくことができない。ゲームのシーンファイル中に配置しておくことで起動よりも先に設定を済ませることができる。

//敵を発生
GameObject cloneEnemyObject = Instantiate(enemyPrefab, spawnPos, Quaternion.identity, parentObject);
cloneEnemyObject.SetActive(true);

 複製してアクティブをONにすれば、あとは通常通りinstantiateしたものと変わらない。

・UIと得点表示

 UIについては以前作ったコンテタイマーのUI作成とそう変わらない。今回は文字にテキストメッシュプロを使用しているのでテキストのセットの仕方が違うくらいだろうか。

スクリーンショット 2021-10-05 005506

 あとは敵の死んだ位置に得点を表示するくらいだが、これは敵の座標をメインカメラのスクリーンの座標に変換してくれる関数を使うだけだ。

Vector2 pointDispTransform = RectTransformUtility.WorldToScreenPoint(Camera.main, enemyPos);

 これで敵を倒して得点が入るようになり、最低限のゲームの仕組みがそろった。次は敵を増やしたり足りていないものを追加していこう。

この記事が気に入ったらサポートをしてみませんか?