Unityで主人公キャラが近づいてきたら追いかける敵キャラを作る

今回は主人公キャラが敵キャラに近づいたら主人公に近づいてくるようにしたいと思います。

前回、敵キャラクターが見回りをするというスクリプトを組みました。

Unityのゲームで作った敵キャラクターが見回りをするように、ランダムに設定した位置に移動させる機能を作成します。

まずはスクリプトをどうやって組むか考えてみます。

敵キャラクターが『主人公キャラクターが近づいてきた』、というのがわからなければいけません。
これにはコライダ(接触判定に使う)を使います。敵が持つコライダと主人公キャラが接触していたら近づいているとします。

コライダの範囲が主人公キャラを検知できる範囲になりますね。

次に敵キャラが主人公キャラクターを追いかける。というのは敵キャラが移動する目的地を主人公キャラクターの位置に変更すればいいような気がします。

ただ目的地(主人公キャラ)は動くので常に監視し変更しなければいけません。
また敵キャラの検知範囲から主人公が外れた場合は敵キャラはまた見回りに戻る為、新しい目的地を設定するようにします。

スポンサーリンク

敵キャラが主人公キャラを検知する範囲を作成する

まずは敵キャラにコライダを設定します。

敵キャラのボーンの階層の下にSearch Areaという空のオブジェクトを作ります。(右クリック→Create Empty)

敵キャラのボーンの子要素にサーチエリアを作る

Add Component→Physics→Box Collider

を選択し主人公キャラの検知範囲を修正します。Is Triggerにチェックを入れてください。
(ここではBox Colliderを選択していますが、Sphere Colliderの方がいいです)。

BoxColliderのIsTriggerにチェックを入れる

Is Triggerにチェックを入れるとスクリプト上で接触の検知は出来ますが、物体としては当たらないようになります。

例えば木などにコライダを設定すると、その部分に侵入は出来ませんが、Is Triggerにチェックを入れると侵入可能となり、木をすり抜けていきます。

主人公キャラクターを検知する範囲

上のような検知範囲を設定してみました。

広く設定されているように見えますが、実際は足音が確実に聞こえる距離までいかないと相手は気づきません。

つまり今回の範囲であれば相当接近しなければ気付かれません。

次に検知範囲に入って来たものが主人公キャラクターであるという判別が必要です。
その為に主人公キャラクターにタグを設定します。

ゲームオブジェクトにタグを設定する

タグはそのオブジェクトがどういう種類に属するかを判別する為のもので、自分で作成する事も出来ます。

主人公キャラにPlayerという元々あるタグを設定してみます。なければAdd Tagで追加してください。

主人公キャラクターにPlayerタグを設定する

タグを追加したので、SearchAreaオブジェクトにSearchCharacterスクリプトを追加し「masasi」君を検知出来るようにします。

サーチエリア(コライダ)に主人公が進入したか調べるスクリプト

SearchCharacterスクリプトはその名の通り検知するスクリプトです。

OnTriggerStayメソッドはスクリプトが設定されているオブジェクトのコライダが他のコライダと接触中に実行されるメソッドです。

そこで接触したコライダが何だったかをタグを使って判定します。

上記のように記述し、引数colに接触した他のコライダが設定されるようになります。
接触した他のコライダのタグがPlayerの時に敵キャラが主人公を追いかけるようにすればいいという事になります。

接触したのがPlayerだった時で、敵キャラであるゾンビの状態が追いかける状態でなければ追いかける状態にします。

とします。MoveEnemyスクリプトのSetStateメソッドは後で作るのでここでは置いておいて、
第1引数にchaseという文字、第2引数にcol.transformで接触したオブジェクトのTransformを渡しています。

それではMoveEnemyスクリプト内にSetStateを作ります。

敵キャラが今どの状態にあるのか?という状態変数stateを宣言し、その状態を表すのに列挙型を使います。

特定の値だけ入れる事が出来る列挙型という変数

列挙型は普通の変数とは少し扱いが変わります。
宣言した項目以外の選択をしないようにする事が出来ます。

と宣言し、これを変数などに入れて使います。

C#だと整数型を指定する事が出来るみたいなんですが、JavaScriptだとどうやればいいかわかりませんでした・・。
型を割り当てない時はint型の数値が割り当てられwalkには0、waitには1と順番に数値が割り当てられます。

↑のようにすると

walk
wait
chase

という文字が表示されます。

State内の状態にそれぞれ値を設定する事も出来ます。

↑も同じように

walk
wait
chase

という文字が表示されます。

あらかじめ宣言された値以外をstateに入れるとエラーになるので、特定の値しか入れたくない場合は列挙型が活躍します。

敵キャラクターの状態変更メソッドの定義

敵キャラクターの状態変更をするSetStateメソッドを定義します。

第1引数で受け取った文字列で分岐させています。それぞれの分岐処理で状態の変化等をしています。

stateに入れている「EnemyState.名前」はenum State{}で宣言されていなければエラーになります。

フィールド宣言とStartメソッド内の追加と変更

フィールド宣言にEnemyStateフィールドとStartメソッド内でSetStateメソッドを呼び出し状態を初期化する処理を入れます。

敵の状態を表すEnemyState型のstateと追いかける状態になった時に追いかけるキャラクターのTransformを入れておくplayerTransformを宣言します。

状態によって処理を分岐させる

次にMoveEnemyスクリプトのUpdateメソッド内の処理を状態によって変更します。

目的地まで歩く処理とキャラクターを追いかける処理は目的地を指定すれば同じ処理を記述する事になるので、追いかける状態である時だけ目的地を再設定し、実際に移動させる処理は同じ処理をしています。

目的地に着いたら、待ち状態に変更します。

待ち状態である時は一定時間その場で待ち、指定時間を超えたら巡回状態(walk)に変更します。

主人公を検知するスクリプトSearchCharacterを改造

MoveEnemy内のスクリプトの記述が終わったので、SearchCharacterに戻ります。

現時点では敵がキャラクターを追いかける状態になったらそのまま一生追い続けます。(^_^;)

そこでSearchAreaに設定したコライダからキャラクターが出ていったら敵キャラクターを待ち状態に変更するようにします。

OnTriggerExitはオブジェクトが接触状態から抜けた時に呼ばれるメソッドなので、その時のオブジェクトのタグがPlayerであったら、敵キャラの状態を待機状態に変更しています。

これで追いかける処理と検知範囲外に出たら見失って一定時間待機し、再度見回りに戻るというスクリプトが出来ました。
簡単なAIではありますが、結構使えると思います。

もっと簡単に敵キャラの移動処理を作るなら

Unityのナビゲーション機能を使って敵キャラクターを移動させます。ナビゲーション機能はあらかじめBakeされたフィールドを移動出来る機能です。

を使うと移動処理が簡単になります。
また、ナビゲーション機能を追加した後に

Unityのゲームで主人公キャラが壁等で見えない状態で敵の検知エリアに入っても敵は主人公を追いかけないようにします。

の機能を追加するとよりリアルな敵の動きが表現出来ます。

スクリプトがバラバラで解り辛かったので、修正を入れました。

オーバーロード機能は使う必要がなくなったので削除しました。(^_^;)

次回は敵キャラを最初から配置しておくのではなく自動で増やせるようにしたいと思います。

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます

コメント

  1. R.K. より:

    最近こちらのサイトで学習させていただいています。
    基本知識もままならないのですがここまでスムーズに来れました。
    敵キャラクターの状態変更メソッドをどこに書けばいいのかわかりません。
    教えてください。

    • こんにちは(^^)/

      敵キャラクターの状態変更メソッドは敵キャラクター操作スクリプトに書いていたような気がします・・・。

      なので敵キャラクター操作スクリプトをMoveEnemyとしていれば

      ↑のような構成になっていると思われます。

      他のメソッド等と同じような感じで定義しています(ここでは他のメソッドは記述していません)。

      • R.K. より:

        コードまでありがとうございます!
        試してみたところ解決できました。
        今後も参考にさせていただきます!

  2. 春の水 より:

    始めまして、昨日からこのサイトを通じてUnityを学んでいます。
    サイトのレイアウトやスクリプトの解説が分かりやすく大変助かっております。

    しかし、本章で躓いたため質問させていただきます。
    本サイトになぞったつもりではありますが、敵キャラクターが主人公を追従しませんでした。

    現在はそのデバッグをしている最中です。

    まずは、以下のようにして
    敵キャラに接触してきた主人公キャラのタグをチェックし、playerであるかどうか確認していたのですが、なにも表示されませんでした。したがって、まずはここを解決している最中です。

    public class SearchCharacter : MonoBehaviour {

    void OnTriggerStay(Collider col) {
    // プレイヤーキャラクターを発見
        Debug.Log(col.tag);
    if(col.tag == “Player”) {
        // 省略
    }
    }
    }

    敵キャラのボーンの階層下にBox ColliderでSearchAreaを生成していますし、Is Triggerにはチェックを入れています。

    もちろん主人公キャラにはタグをセットしていますが、player以外のタグでもコンソール上になにも表示されませんでした。

    どのようにしたら接触時にタグが認識されるのでしょうか?
    お忙しいとは思いますが、ご教示いただければ幸いです。

    ps

    一点気になった点がございます。

    【敵キャラクターの状態変更メソッドの定義】章の
    // 追いかける対象をセット
    player = obj;

    ここの箇所ですが、playerは誤植でplayerTransformという理解でよろしいですか?

    • 春の水 より:

      連投失礼します。
      さきほど、こちらの質問をしたものです。

      申し訳ございませんでした。
      あれから引き続きデバッグをしていたところこちらのスペルミスが原因であることがわかりました。

      訂正前: void onTriggerStay(Collider col)
      訂正後: void OnTriggerStay(Collider col)

      MonoDevelopでコーディングしているのですがビルド時に警告等のメッセージは出ないのですね。

      お騒がせしました。失礼します。

    • こんにちは、ブログを拝見して頂きありがとうございます。(._.)

      スクリプトの書き換えをチョコチョコしていた時にフィールド名を変更し、ブログの記事ではそこを直さずにいたりして間違った記述になってしまった箇所がいくつかあるようです。

      間違えていた箇所は修正しておきました。

      エラーについては解消された?ということでそれについてはスルーということで・・・・(*‘∀‘)

  3. 匿名 より:

    最近Unityを勉強し始めて、毎日こちらで楽しく学ばせていただいています。
    大変わかりやすいご説明でここまで順調に進んできていたのですが、ここで躓いてしまいました。
    どうしてもSearch AreaのOnTriggerStayのところで「NullReferenceException: Object reference not set to an instance of an object」エラーが出てしまいます。

    Startで「moveE = transform.parent.GetComponent(MoveEnemy);」も入れてあるのですが、実行時にGetState()の部分でこのエラーが出てしまいます。
    MoveEnemyのメソッドにアクセスしなければこのエラーは出ず、正しく反応するようなのですが。。。
    何か前提になっていることが抜けている可能性あるでしょうか?

    • ありがとうございます(._.)

      moveEはMoveEnemyスクリプトを取得して参照を入れておくフィールドで、このスクリプトを設定しているSearchAreaゲームオブジェクト?の親の要素にMoveEnemyが設定されている必要があります。

      ↑のような感じですね。

      その為、MoveEnemyスクリプトが親のゲームオブジェクトに設定されていない場合にそのようなエラー内容が表示されると思います。

      コンポーネントの取得方法については

      https://gametukurikata.com/program/getcomponent

      を参照して頂くと階層を辿ってコンポーネントを取得する方法がわかるので、親の要素以外にMoveEnemyを設定した場合に参考にして頂けると思います。

      またはフィールドをprivateからpublicにするかC#なら[SerializeField]、JavaScriptなら@SerializeFieldアトリビュートを指定して宣言し、インスペクタであらかじめ敵キャラクターに設定したMoveEnemyを設定してしまうと簡単です。

      ↑のように宣言するとインスペクタでMoveEnemyを設定する事が出来るので、SearchAreaを持つ敵キャラクターに設定したMoveEnemyスクリプトをインスペクタで設定してあげると、インスタンスが設定されていないというエラーは解消されると思います。

      moveEはインスペクタで設定する為、Start関数等でMoveEnemyスクリプトを探す必要がありません。

      • 匿名 より:

        ご返信ありがとうございました。
        いただいたアドバイスを参考にいろいろ試してみたところ、うまくいきました!
        void Start () {
        moveE = transform.parent.GetComponent(MoveEnemy);
        Debug.Log(“Test”);
        }
        こうやってみたらLogの掃き出しすらしませんでした。
        そこでもう一度「void Start () {」の行を追加で入力しなおして、今までの行を消してみたところ動きました。
        大文字小文字も一致していたので、何か余分な空白などがあったのかなぁと思っております。こんなことも気を付けるべき点なんだなと勉強になりました。
        引き続きこちらで勉強させていただきます。ありがとうございました!

  4. はしもと より:

    関数のオーバーロードをしている部分で以下のようなエラーが出ます。

    Assets/02. Scripts/MoveEnemy.js(109,38): BCE0017: The best overload for the method ‘SetPosition.SetDestination()’ is not compatible with the argument list ‘(UnityEngine.Vector3)’.

    どのように解決するのがベストでしょうか?

    • 同じエラーが出た事がないので、MonoDevelopの.Netのバージョンがわたくしのよりも高いので出てるエラーかもしれませんね。

      わたくしのパソコンでは新しいUnityにバージョンアップ出来ないので、その確認が出来ません。

      オーバーロードをしている関数の定義と、それを呼び出しているスクリプトはどんな風に記述しているかを書いて頂くと何かわかるかもしれません。

      また無理にオーバーロードを使わずとも、見回り用の関数をSetRandomPositionという名前にし、キャラクターを追いかける関数をSetChasePositionという名前にして、

      ↑のように別の関数として扱って問題ありません。

  5. レモン より:

    なるほど、確かに複数コライダを付けて一つのスクリプトで処理しようとすると、それぞれ別個に扱いたい場合にOnTriggerStayやOnTriggerExit内でどのコライダに反応したのか振り分けられないですね。納得しました!丁寧なご回答ありがとうございます。

  6. レモン より:

    こんにちは、最近Unityを勉強し始めた初心者です。

    SearchAreaを敵キャラのボーンの階層の下に別に作る理由について教えていただけないでしょうか?敵キャラ(z@walk)に直接BoxColliderをAddComponentしてMoveEnemy内にOnTriggerStayとOnTriggerExitを記述し処理を振り分ければ、SearchAreaスクリプトを作成せずに済み、スクリプトが一つにまとまるので一覧性が高まって良いのかなと思ったのですが、そうしない意図についてご説明いただけないでしょうか?

    それともUnityでゲームを作るにあたってはこのような処理をそれぞれ細く分けた方が後々楽になってオススメということでしょうか?もしよろしければご意見を頂けますと幸いです。

    • おそらくほとんどの場合でMoveEnemyにコライダの検知であるOnTriggerStayやOnTriggerExitを付けて判別しても問題はないと思われます。

      わたくしが空のゲームオブジェクトに検知範囲を別にして取り付けた理由としては、「敵が主人公を検知する用に取り付けたコライダ」と「敵自身の当たり判定のコライダ」を別個に扱いたかったからです。

      OnTriggerStayやOnTriggerExitの処理を書いたスクリプトをキャラクターに取り付けた場合、コライダに他のコライダが侵入したり出て行った時に発生する為、

      主人公検知エリア用のコライダだけでなく、CharacterControllerコンポーネントのコライダが他のエリアに侵入した時も発生します。

      幽霊のような敵キャラクターを作り、物理的に当たらないとすると主人公キャラクターで幽霊を検知したいと思った時に、検知用コライダと主人公の当たり判定用のCharacterControllerのコライダの両方で判定されてしまう可能性があります。

      一応説明は付けましたが・・・、ただ単純に別個に扱いたかったからかもしれませんね。(^_^;)

      わたくしの作り方が正しいというわけでもないので、試してみて問題なければあえて同じにする必要はないので色々試してみてください。

  7. 名無し より:

    無事動きました!お忙しい中ありがとうございました。

    余談ですが
    Search Areaの方で「’getstate’ is not a member of ‘MoveEnemy’」
    とエラーが出て悩んだ結果、MoveEnemyに

    function getState () : State {
    return state;
    }

    と記述したら直りました。これで正解かは不安ですが・・・。

    • 記事内の処理が抜けていたようです。
      ご報告ありがとうございます。m(__)m
      記事は修正しておきました。

      処理はその記述で問題ないと思います。
      この頃の記事は関数名の最初の文字が小文字になっているのでそれも直しておきました。

  8. 名無し より:

    コードを全文載せてくださると助かります。

    • コード全文を載せると見てる方にはすごく便利になる事はわかってはいるのですが(実際全部載せてる記事が多い)
      諸々の理由により今後どうしようか迷っている所です。

      申し訳ありませんがコードを追加、修正してスクリプトを作成して頂けると助かります。
      面倒だとは思いますがよろしくお願いしますm(__)m

      • 名無し より:

        各スクリプトの宣言の部分がどうしてもわからないのです。
        お願いします、どうか教えてください。

        • Search Areaスクリプトの宣言、所得部分は

          MoveEnemyはすでに元のスクリプトがないので以下のようになっています。
          不要な変数等は削除してください。