前回の記事「Unityゲーム制作研究(3):Third Person Character Controllerを改造してアクション(アニメーション)を追加する方法」では、ユニティーちゃんに武器を持たせて攻撃アクションと防御アクションができるようにしてみたのですが、今回はユニティーちゃんが一定の距離内に近づくと襲ってくる敵キャラを作ってみたいと思います。
この記事を書くにあたり使用した環境と必要となるスキルについて
この記事を書くにあたり、当方が使用した環境、及びこの記事の対象者のスキルを明記しておきたいと思います。
- OS: macOS Monterey 12.6
- Unity: 2021.3.5f1 Personal(バージョンが異なる場合、機能や画面の様子が異なる場合があります)
- 対象者: 前回の記事「Unityゲーム制作研究(3)」を終えた方、もしくは同程度のスキルをお持ちの方
© Unity Technologies Japan/UCL
※「ユニティーちゃん」はUnity-Chan License (UCL)の下に配布されており、当サイトでもこのライセンス条項に基づき使用させていただいております。
目次
- Asset Storeから敵キャラを入手してSceneに追加する
- NavMeshを設定する
- 敵キャラのAnimatorを設定する
- 敵キャラが反応するエリア等を設定する
- 必要なコードを記述する
- GameViewでPlayしてみよう!
- まとめ
1. Asset Storeから敵キャラを入手してSceneに追加する
今回選んだ敵キャラですが、宝箱に擬態した敵キャラにしてみました。もともとユニティーちゃんが無人島で宝を探すアドベンチャーゲームのようなイメージで作り始めているので、コンセプトにもピッタリな感じです。
Asset Storeにアクセスして「RPG Monster」と検索してみてください。「RPG Monster Partners PBR Polyart」というハイクオリティなFreeアセットを見るけることができると思います。というわけで、今回もまたFreeアセットのお世話になります。

「Add to My Assets」したら、早速Unityの「Window > Package Manager」を開いて、ProjectにImportしてみましょう。

ProjectにImportが完了したら「RPGMonsterPartnersPBRPolyart > Prefabs > ChestMonsterPBR」をSceneの適当な位置にドラッグしておきます。
2. NavMeshを設定する
敵キャラの準備が完了したら、ユニティーちゃん(Player)を襲ってくるように設定していきます。敵キャラが常にPlayerの方向と距離を検知し、障害物をきちんと避けながらこちらに向かってくるよう実装するのはかなり大変そうですが、Unityにはそのような目的で使える「Navmesh Agent(以下、NavMeshと表記します)」と呼ばれるAIコンポーネントが搭載されています。
まずはこのコンポーネントを追加しておきましょう。「Window > AI > Navigation」を追加しておきます。するとInspectorタブの隣にNavigationタブが開くことを確認してください。

このNavMeshですが、予め敵キャラ(Agent)の動ける範囲のポリゴンを検知させてあげる必要があります。この操作をベイク(Bake)といいます。
さらに、Bakeする前に障害物となるものを設定しておく必要もあります。障害物の設定ですが、Sceneには灯台やヤシの木、岩などが多数配置してあるので、それらを複数選択した状態で、Inspectorタブの「Static」設定を「Navigation Static」にしておきます。

この設定変更が完了したら、先ほどのNavigation タブ内にあるBakeボタンをクリックします。ちなみに、この処理には相応の時間がかかるので注意してください。Bakeが完了すると、地面(Terrain)がブルーに変化し、ポリゴンが設定されます。
ちなみに、後になって障害物となるものがさらに増えた場合ですが、再度Bakeしてやる必要があるので、この点も覚えておきましょう。
このようにNavMeshを使えば、簡単に敵キャラ(Agent)が、障害物をぐるっと回って追いかけてくるようにできるのです。難しいことはNavMeshがやってくれるのでゲーム制作が本当に捗りますね!
3. 敵キャラのAnimatorを設定する
さて今度は、敵キャラの動きをAnimatorを使って設定していきます。先ほどImportしたChestMonsterPBRには予めアクションが沢山設定されていますが、今回はひとまずその半分くらいしか使いません。下図はデフォルトです。

このデフォルト設定から下図のようになるよう改修してみてください。まず不要な導線(Transition)を削除し、必要なアクション(State)のみを上の方に並べて、新たに導線(Transition)で結んでやります。

ここではこのようなアニメーションの流れにしてみました。
アニメーションの流れ:Prayerをエリア内に検知
IdleChest(箱が閉じた状態)→ IdleNormal(目覚めた状態)→ IdleBattle(攻撃待機) → Attack01(頭突き)→ Attack02(激しい頭突き)→ IdleBattle(攻撃待機)に戻る
アニメーションの流れ:Prayerがエリア外に退避
IdleBattle(攻撃待機) → WalkFWD(前進)* → SenseSomethingRPT(周囲を警戒)→ IdleChest(箱が閉じた状態)に戻る
* WalkFWD(前進)を含めてあるのは、Playerがエリア外に退避しても、惰性で幾らか前進してしまうため、帳尻を合わせるためです。
またPlayerを検知した場合のフラグとしてParameterを設定してやります。ここでは「Detection」という名称にしてみました。これはBool値とします。
IdleChest(箱が閉じた状態)→ IdleNormal(目覚めた状態)への導線(Transition)は、設定した「Detection」に対応させるためInspectorタブ内にあるConditions項目で「Detection」を「True」に設定しておくことが必要です。また「Has Exit Time」のチェックを外しておくことも忘れないでください。

IdleBattle(攻撃待機) → WalkFWD(前進)への導線(Transition)でも「Detection」に対応させる必要がありますが、こちらはInspectorタブ内にあるConditions項目の「Detection」を「false」に設定してやります。加えて「Has Exit Time」のチェックも外しておきます。

WalkFWD(前進)* → SenseSomethingRPT(周囲を警戒)の部分ですが、「Detection」が「false」になっても、数秒間惰性で前進してしまうため、その帳尻を合わせるために、タイムラインの設定で5秒程、前進するアニメーションが持続するようにしてみました。
4. 敵キャラが反応するエリア等を設定する
さて、今度は敵キャラ(ChestMonsterPBR)のInspectorに、反応エリア(Collider)を設定したり、スクリプトの関連付け等の設定をしたりしていきます。
ChestMonsterPBR(Inspector)の修正
当然、敵キャラ自身(ChestMonsterPBR)にもColliderの設定が必要になりますので、まずはそこから設定していきましょう。ここではCapsule Colliderを設定してあります。ただ、前述の「Attack02(激しい頭突き)」の時に、かなり前方に突出した頭突きをしてくるので、その範囲を考慮して設定する必要もあります。適宜検討の上実装してみてください。なお、設定値は下図を参考にしてみてください。

今回は敵キャラとして、NavMeshの設定を行なったわけですが、このキャラが敵キャラ(Agent)になるので、Nav Mesh Agentコンポーネントもaddしておきます。
- Speed(追いかけてくる速度)
- Angular Speed(回転速度)
- Acceleration(加速度)
上記の設定もお忘れなく。適宜最適と思われる値を入力してみてください。
敵キャラの動きを制御するスクリプトも必要となります。コードの内容に関しては後述しますが、ここではまず、Assetsフォルダ内に「Script」というフォルダを追加して、その中に空の「EmenyMove.cs」を追加しておきます。その後、Inspectorにもaddしておきます。
CollisionDetectorの追加
今度は、敵キャラ(Agent)がユニティーちゃん(Player)を検知した時のスクリプトを追加します。まず、Scene内にあるChestMonsterPBRを開いて、その中に「CollisionDetector」という空のオブジェクトを追加(Create Empty)しておいてください。

追加できたら、今度はSphere Colliderもaddしておきます。これはユニティーちゃん(Player)を検知するエリアとなります。Radiusがその半径となりますが、ここでは「5」と設定しました。適宜最適な値を入力してみてください。
先ほど「Assets > Script」に「EnemyMove.cs」を追加しましたが、ユニティーちゃん(Player)検知時のスクリプトとして「CollisionDetector.cs」も追加しておきます。忘れずにInspectorにもaddしておいてください。
5. 必要なコードを記述する
それでは、最後に仕上げとしてコードを記述していきましょう。
EmenyMove.cs
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyMove : MonoBehaviour
{
private NavMeshAgent _agent;
private Animator _animator;
private bool _hasAnimator;
private int _animIDDetection;
private void Start()
{
// NavMeshAgentを保持
_agent = GetComponent<NavMeshAgent>();
// 検知用パラメーターIDを設定
_animIDDetection = Animator.StringToHash("Detection");
}
private void Update()
{
_hasAnimator = TryGetComponent(out _animator);
}
// CollisionDetectorのonTriggerStayにセットし、衝突判定を受け取るメソッド
public void OnDetectObject(Collider collider)
{
// 検知したオブジェクトに「Player」のタグがついていた時
if (collider.CompareTag("Player"))
{
// Playerを追いかける
_agent.destination = collider.transform.position;
if (_hasAnimator)
{
// 検知用パラメーターIDの値を変更
_animator.SetBool(_animIDDetection, true);
}
}
}
// CollisionDetectorのonTriggerExitにセットし、衝突判定を受け取るメソッド
public void OutDetectObject(Collider collider)
{
// 検知したオブジェクトに「Player」のタグがついていた時
if (collider.CompareTag("Player"))
{
if (_hasAnimator)
{
// 検知用パラメーターIDの値を変更
_animator.SetBool(_animIDDetection, false);
}
}
}
}
10、18行目:
_animIDDetectionという変数(int)はAnimatorで設定したパラメーター「Detection」を受け取るためのものです。Start関数で初期化してUpdate関数で常にその値を同期させます。
27行目:
OnDetectObject関数は、Playerのエリア内の侵入(CollisionDetector.cs側のOnTriggerStay)を検知して、Playerの追跡を開始させます。また_animIDDetectionを介して「Detection」の値を「Ture」にする役割も持っています。
44行目:
OutDetectObject関数は、Playerのエリア内の退避(CollisionDetector.cs側のOnTriggerExit)を検知し、_animIDDetectionを介して「Detection」の値を「false」にする役割も持っています。
CollisionDetector.cs
using System;
using UnityEngine;
using UnityEngine.Events;
[RequireComponent(typeof(Collider))]
public class CollisionDetector : MonoBehaviour
{
[SerializeField] private TriggerEvent onTriggerStay = new TriggerEvent();
[SerializeField] private TriggerEvent onTriggerExit = new TriggerEvent();
private void OnTriggerStay(Collider other)
{
// onTriggerStayで指定された処理を実行する
onTriggerStay.Invoke(other);
}
private void OnTriggerExit(Collider other)
{
// onTriggerExitで指定された処理を実行する
onTriggerExit.Invoke(other);
}
// UnityEventを継承したクラスに[Serializable]属性を付与することで、Inspectorウインドウ上に表示できるようになる。
[Serializable]
public class TriggerEvent : UnityEvent<Collider>
{
}
}
8、9行目:
この設定はInspector側との紐付けに必要な記述です。
11行目:
OnTriggerStay関数は判定対象がエリア内にいるかどうかを検知します。
17行目:
OnTriggerExit関数は判定対象がエリア外に退出したことを検知します。

CollisionDetector.csの記述が完了したら、上図を参考にEmenyMove.cs内のOnDetectObject関数とOutDetectObject関数に、敵キャラ(ChestMonsterPBR)との紐付け設定を行います。この設定を忘れると、敵キャラが一切反応しませんので注意してください。
6. GameViewでPlayしてみよう!
これで作業は完了です。では早速動作テストしてましょう!ユニティーちゃんが敵キャラのエリア内に侵入すると、敵キャラがニョキっと目を覚まして攻撃してくるでしょうか?またエリア外へ脱出した時に、周りをキョロキョロ警戒してから、再び眠りにつくか確認してみてください!

ゲームコントローラーで操作したい場合
この動作テストはキーボードでも操作できますが、ユニティーちゃんのコントロールに使用している「Starter Assets – Third Person Character Controller」は、市販のゲームコントローラーにも対応しています。以下の製品で問題なく動作することを確認できました。
※上記のリンクはamazonへのリンクとなっています。
7. まとめ
さて、いかがだったでしょうか。敵キャラが登場して攻撃してくるようになったので、一段とゲームらしくなってきました。とはいえ、まだ攻撃アクションをするのみです。次回は、ユニティーちゃんと敵キャラの戦闘で体力が減るロジックなどを実装していきたいと思います!
© Unity Technologies Japan/UCL
※「ユニティーちゃん」はUnity-Chan License (UCL)の下に配布されており、当サイトでもこのライセンス条項に基づき使用させていただいております。