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

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

Unityで敵キャラが見回りをするように動き回るプログラム
Unityのゲームで作った敵キャラクターが見回りするように指定した場所を巡回していく機能を作成します

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

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

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

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

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

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

スポンサーリンク

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

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

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

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

Add Component→Physics→Box Collider

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

接触したのがPlayerだった時は敵キャラであるゾンビの状態を見回りから追いかける状態に変更します。

moveE.SetState(“chase”, col.gameObject);

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

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

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

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

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

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

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

↑のようにすると

walk
wait
chase

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

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

↑も同じように

walk
wait
chase

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

state = State.walk;

としてstateに入れる値を0や1にして判別する事も出来ますが、enumを使う事で見てどんな状態かわかりやすくなります。

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

主人公の状態変数をセット

状態によっては歩いている状態と追いかけている状態では同じようなスクリプトを書くことになるので、歩いている処理はひとまとめにWalk関数にまとめ、待機状態はWait関数にまとめるようにしました。

その動作を行う時に関数呼び出しをすればいいだけになり、処理の流れがわかりやすくなります。

第1引数で受け取った文字列で分岐させています。それぞれの分岐処理で状態の変化等をしています。
stateに入れている「State.名前」はenum State{}で宣言されていなければエラーになります。

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

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

歩いている時の処理を実行するWalk関数

状態がState.walkの時はすでに設定してある目的地を引数としてWalk関数に渡しています。
状態がState.chaseの時は取得したplayerCharaの位置を引数としてWalk関数に渡してます。

Walk関数内では状態がchaseの時だけ受け取った引数を目的地に再設定し、そこに移動するようにしています。

これでwalk関数が呼ばれるごとに主人公キャラの位置が目的地に再設定され追いかけて歩くようになります

しかし、実はまだ作っていない機能を使っています。

setPos.SetDestination(position)

で引数を渡しそこに目的地を設定するようにしていますが、すでに作成済みのSetDestinationは引数を受け取らない仕様になっていたはずです。

いつのまに引数を受け取れるようになったんだ?と言われそうですが、(通常のJavascriptでは引数ありの関数呼び出しで引数を渡さなくても大丈夫、厳密にはあまりよろしくないかも?)

実はもうひとつSetDestination関数を作成しました。

関数のオーバーロード機能を使う

同じ関数名があってはいけないと思われるかもしれませんが、実は可能です。

引数がないものと引数があるものと二つ作成します。
どちらの関数が呼ばれるかは引数の型と数で決まるので、

ランダムな位置を設定したい場合は引数なしで呼び出し
主人公の位置を設定したい場合は引数を一つ与えて呼び出します。

この引数の型と数の違いによって呼ぶ関数を変更する機能をオーバーロードと言います。(なんだか偉そう・・・)

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

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

OnTriggerStayはオブジェクトが接触している間ずっと呼ばれます。
状態がchaseの時は再設定する必要はないので除外しておきます。

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

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

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

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

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

Unityで主人公が敵から隠れた時は追いかけないようにする
Unityのゲームで主人公キャラが敵キャラから逃げ、壁等で見えなくなったら敵キャラクターは主人公を追いかけないようにします

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

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

コメント

  1. 名無し より:

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

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

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

      • 名無し より:

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

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

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

  2. 名無し より:

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

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

    function getState () : State {
    return state;
    }

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

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

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