【Unity】2Dシューティングを作ってみる話 #24

記事をご覧いただき、誠にありがとうございます。
投稿主の無能です。

前回は、プレイヤーのHPが回復するアイテムの実装をしました。

今回は、HP回復以外のアイテムの実装と、アイテムがランダムで定期的に生成される処理を実装していきます。

アイテムがランダムで定期的に生成される処理

さて、お気付きの方もいらっしゃるでしょう。

この処理は、以前実装した敵が生成される仕組みとほぼ同じ処理になります。

これならすぐにアイテムの生成処理は実装出来そうですね。

では早速やっていきましょう。

アイテムの生成ポイントを作成する

敵の生成ポイントを作ったのと同様に、アイテムの生成ポイントを作成します。

「ItemGenerator」という空のオブジェクトを作成して、Sprite Rendererをアタッチします。

「ItemGenerator」という空のオブジェクトを作成

Sprite Rendererをアタッチ

ItemGeneratorを可視化した状態

そうしたら適当な位置へ配置します。

ItemGeneratorの位置

Sceneビュー

そしてSprite Rendererを非アクティブにします。

Sprite Rendererを非アクティブにする

Sceneビュー

そしてItemGeneratorに、「ItemGenerator」というスクリプトを新規作成してアタッチします。

「ItemGenerator」スクリプトを新規作成

作成したスクリプトをアタッチ

では早速ItemGeneratorスクリプトを編集しましょう。

このようになります。

ItemGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemGenerator : MonoBehaviour
{
    #region Variables
    #region var-ItemGenerateVariables
    [Header("アイテムの生成リスト")][SerializeField] GameObject[] items;
    [Header("アイテム生成の時間")][SerializeField] float genDelay = 2f;
    [Header("アイテム生成の間隔")][SerializeField] float genInterval = 2f;
    [Header("画面の余白(y軸方向)")][SerializeField] float marginY = 1f;
    #endregion

    #region var-Internal
    Vector2 min, max;       // 画面サイズ取得用
    #endregion
    #endregion

    #region Methods
    #region Start
    void Start()
    {
        // 画面サイズの最小値・最大値を取得
        min = Camera.main.ViewportToWorldPoint(Vector2.zero);
        max = Camera.main.ViewportToWorldPoint(Vector2.one);
        // アイテム生成の関数を一定間隔でリピート
        InvokeRepeating("ItemGenarate", genDelay, genInterval);
    }
    #endregion

    #region ItemGenarate
    // アイテムを生成
    void ItemGenarate()
    {
        // アイテムを生成
        Instantiate(items[Random.Range(0, items.Length)],                                               // GameObjectの配列enemiesからランダムで生成するアイテムを選択
                    new Vector2(transform.position.x, Random.Range(min.y + marginY, max.y - marginY)),  // x座標:EnemyGeneratorオブジェクトのpositionのx,y座標:取得した画面サイズの最小値+余白から最大値-余白までの間
                    Quaternion.identity);                                                               // 回転しない

    }
    #endregion
    #endregion
}

既に習得済みの内容なので、説明は割愛させていただきます。

要は敵の配列だったものをアイテムの配列にしたものです。
以前やった内容そのままなので簡単ですね。

ではHierarchyビューにあるItem_Healを削除します。

HierarchyビューにあるItem_Healを削除

そしてItemGeneratorの配列の項目にItem_Healを設定します。

ItemGeneratorの配列の項目にItem_Healを設定

ではゲームを実行して確認します。

エラーが発生した

先程とは打って変わって、エラーメッセージが表示されました。

エラーが表示された

エラーが発生したので、エラーの原因の特定と解決をしていきます。

エラーを分析してデバッグする

このコンソール画面はWIndow > General > Console で表示できます。

またはCtrl+Shift+Cキーで表示できますので、常に表示させておいておきましょう。

無能の場合はHierarchyビューの後ろに表示させています。

ここまで順調だったのに、何故エラーが表示されたのでしょうか。
詳しく見ていきましょう。

今回はVisual Studio側ではエラーが発生していないのに対して、Unity側でエラーが出ています。

Consoleビュー

エラーの見方で大事な部分は、最初に提示されているエラーです。

この場合はNullReferenceExceptionです。

NullReferenceException

まずはこのNullReferenceExceptionがどういったエラー内容なのか調べます。
(エラー内容は割愛します)

調べてエラーの内容が分かったら次の段階です。

このNullReferenceExceptionが直接的な原因になっていて、処理が中断されています。

エラーの原因の「:」(コロン)からリンクの前までの部分が、エラーの原因の詳細を表しています。

英語なので分かり辛いですが、Visual Studioのエラー一覧と同様なんだと考えると良いです。
エラーの原因の「:」(コロン)からリンクの前までの部分がエラーの原因の詳細を表している

この部分を翻訳してみます。

Google翻訳

たまにこのエラーの詳細部分で、Unityから○○じゃないですか?と聞かれている場合があり、それでエラーが解決することがあります。

そのような機会を逃さないためにも、またエラーの内容を正しく理解するためにも、この部分の翻訳はやっておきましょう。

そして、この直接的な原因となっている行が、最初のリンクで示されています。

最初のリンクがエラーの直接的な原因となった箇所

このリンクを踏むとVisual Studioのエラーの行へジャンプします。

リンクを踏むとVisual Studioのエラーの行へジャンプ

この時点でエラーの原因が解決出来れば良いのですが、事はそう単純ではない時があります。

その時に調べるのが、次のリンクです。

最初のリンク(先頭のリンク)が直接的な原因となっている行で、その後のリンクは関連してエラーで停止している行のリンクになります。

原因が分からない場合は次のリンクを踏む

次のリンクを踏んだ

そして、二つのリンクの共通部分や処理の流れを見出します。

何故関連部分も調べるのかと言うと、直接的な原因は先頭のリンクではあるけど、直接的な原因を引き起こしているのが関連部分だった、という事も有り得るからです。

今回は共通項は変数ぐらいなので、あまりエラーの原因とは関りが薄そうです。

やはり最初のエラーのリンクに原因がありそうです。

では、Unityで行った事から相違点を挙げていきましょう。

エラーの前後を考えると、アイテムにスクリプトをアタッチした辺りからで良さそうです。

行った事・相違点
  • アイテムをプレハブ化した
  • アイテムをHierarchyビューから削除した
という事になります。

どうやらこの辺りに原因がありそうです。

アイテムのプレハブのInspectorビューを見てみましょう。

アイテムのプレハブのInspectorビュー

プレハブ化する前に設定したコントローラー関連がNoneになっています。

これが原因だと思えるものは、些細なものでも片っ端から挙げていきましょう。

これで
・プレハブ化する前に設定したコントローラー関連がNoneになっている
という変化を見付けることができました。

次にHierarchyビューからアイテムを削除したことについてですが、これは関係がなさそうです。

理由は、アイテムが無い状態でもゲームがこれまで進行してきたからです。

ゲーム内でアイテムを取得した際にアイテム自身が破棄されますが、それでもエラーなどはでていないからです。

このように、原因っぽいものを片っ端から挙げていくと説明しましたが、それを明確に否定出来る理由があれば選択肢から外しましょう。

どうやらこのコントローラー関連がNoneになっているのが原因のようです。

ではコントローラー関連をアタッチし直せば解決…ですが、そうは上手くいきません。

やってみると分かりますが、プレハブにコントローラー関連が設定できません。

これは困った…になりますよね。

でもここまで習得してきた皆さんは、この問題を解決できます。

そう、スクリプトで所得すれば良いんです。

ではItemControllerを開いて、コントローラー関連を取得しているStart関数の部分を見てみましょう。

GameManagerは取得しているけど、PlayerControllerは取得していません。

これがエラーの原因となっているかも知れないので、PlayerControllerを取得する処理を追記します。

ItrmController.cs - Start関数内へ追記

// プレイヤーコントローラーを取得
player = GameObject.FindWithTag("Player").GetComponent<PlayerController>();

この追記をGameManagerを取得している行の下に追記します。

では保存して、ゲームを実行して確認します。

アイテムを取得してもエラーが出なくなった

アイテムを取得しても、エラーが表示されず正常な処理になりましたね。

やはり原因はコントローラー関連を取得できていなかったことだったようです。

エラー分析・デバッグの流れ

エラーが発生したら、今回のような場合は以下の流れになります。

  • エラーの内容を調べる
  • エラー内容は○○Exceptionとなっている部分(Exceptionは例外の意)
  • Exception以降はエラーの詳細なので翻訳する
  • 直接的な原因となっている行のリンクを踏んでエラー行を修正する
  • 関連する行がある場合はそのリンクも踏んで確認する
  • Unityでエラー前後に行った事から仮説を列挙する
  • 仮説を一つずつ解決する(明確な否定ができればOK)
  • ゲームを実行して検証する
問題解決の一助になるのが、スクリプト内のコメントや処理の流れです。

見やすいコード、つまり可読性と言っていますが、エラーが発生する、処理を追加するなどスクリプトをいじることが多くなります。

ましてや自分の書いたコードでさえエラーになり得ます。

何処からか持ってきたコードを理解していないままコードのコピペなんてやったら、自分仕様のゲームが他の人の仕様にピッタリ合うわけがありません。

そのためエラーになります。

ですので、コードの中身を理解しないままコピペするのではなく、きちんと理解した上でコピペするようにしましょう。

あわせて、エラー発生や追加の実装などで追記したり修正したりしますが、面倒臭がらずにその都度可読性を維持できるように整理しましょう。

可読性の高いコードは次の追記のモチベーションや作業効率を上げてくれます。

エラーが解決出来たら、そのエラー解決の流れは自分の生きた知識になります。

こんなことあったっけ、という経験則も身に付きますので、エラーは恐れるものではなく、実装しようとしている処理が前進している証拠だと思ってください。

処理を実装しようとしなければエラーは出ないし、ゲームもそこまで止まってしまうので、確実に前進しています。

ですので解決に向けて頑張っていきましょう。

余談ですが、人にエラーの原因を聞く場合は
  • 何をしたいか(どんな処理を実装したいか)
  • 何を行ったか(どんな処理を書いたのか)
  • どんなエラーが出たか
  • 自分で立てた仮説を検証した結果がどうだったか
くらいは最低限やっておいて、言葉で説明できるようにしましょう。

教えてくれる人も同じ道を辿ります。

そのエラーとなる仮説が少ない方がよりスムーズに解決できるはずです。

エラーが出たので、その解決に向けての流れを説明してみました。
皆さんのエラー解決の一助になれば幸いです。

その他のアイテムの実装は次回になります。

まとめ

今回は
  • アイテムがランダムで定期的に生成される処理を実装した
  • エラーを解決した
という事をやりました。

次回はその他のアイテムを実装したいと思います。

では、また次回!

コメント