Raspberlyのブログ

Raspberlyのブログ

Unityネタをメインとした技術系ブログです。にゃんこ大戦争や日常なども。そろそろブログタイトル決めたい

【アセット紹介】SensorToolkit 2 でオブジェクトを検知する【Unity】

今回はアセットの紹介をしていきます。
紹介するのはSensorToolkit 2
オブジェクトを検知するセンサー機能を提供するアセットです。

 

 

セール情報

現在アセットストアではメガバンドルセールを開催中!

assetstore.unity.com

超お得に多くのアセットを入手できるチャンス!
今回紹介する「SensorToolkit 2」も含まれています。

 

どんなアセット?

オブジェクトの検知に特化したアセットです。

SensorToolkit は、Raycasts、Overlaps、Trigger Colliders などのビルトイン Unity 関数を強力に抽象化したものです。スタンドアロンのセンサー コンポーネントをゲーム オブジェクトに追加し、必要に応じて構成できます。センサーにクエリを実行して、検出対象を特定し、ターゲットの可視性や形状などの追加情報を提供できます。

皆さんがステルスゲームなどで、敵がプレイヤーを検知する仕組みを作りたい場合どうしますか?
二点間のTransformを計算したりColliderを使って近くにPlayerがいるか確認し、その後にRayを飛ばして間に障害物がなければPlayerを発見するといった処理を書いたりするのではないでしょうか。
SensorToolkit 2を使えばそれらの処理を簡単に実装できます

 

 

旧バージョン

過去に前バージョンの紹介記事を投稿しています。
メジャーアップデートにより古いバージョンは使用できなくなったのでご注意ください

raspberly.hateblo.jp

 

拡張性の高い設計への変更といくつかの新機能が追加されています。
ドキュメントはこちら

micosmo.com

 

 

 

開発環境

Unity2021.3.12f1

SensorToolkit 2 ver2.4.1

 

 

 

デモシーンの確認

アセットインポート完了時はこんな感じ。

 

サンプルシーンがいくつか用意されています。


Fundamentalsシーンではアセットにある全てのセンサーが紹介されているので、
インポート後最初に見ることをオススメします。

 

 

センサーとコンポーネントの紹介

Fundamentalsシーンを元に各センサーを簡単に紹介します。
ドキュメントからも詳細が確認できます。

 

Ray Sensor

Rayを飛ばして衝突したオブジェクトを検出するセンサーです。UnityのRayに近い。
レイを完全に遮るレイヤーを設定したり、遮断される斜面の角度を指定できます。

形状をRayから変更するとボリュームのある衝突判定を行うことができます。

最も良い使い方は、Obstructed By Layersに衝突させたいオブジェクトのレイヤーを指定することです。

 

Arc Sensor

Ray Sensorの曲線バージョンです。曲線であること以外はほとんど同じ。

セグメントに対して複数のRay Sensorを呼び出すということをしているので、セグメントをなるべく少なくするのがポイント。
使いどころが難しいですが、公式ドキュメントではキャラクターがジャンプできるかどうかの判定で使用していました。

 

 

Range Sensor

範囲内のオブジェクトを検出するセンサー。

形状はSphere、Box、Capsuleが用意されています。

 

 

Trigger Sensor

OnTriggerEnterOnTriggerExitでオブジェクトを検出するセンサー
センサーがアタッチされたオブジェクトにはColliderと、IsKinematicが有効になったRigidbodyが必要です。
センサーの範囲などはColliderの大きさで調整します。

Range Sensorと似たような役割を持ちますが、こちらの方が多機能で少し複雑です。
UnityのTriggerとColliderによる衝突判定に少し慣れていないと難しいかも。

 

Range Sensorは使える形状が3種類と制限がありましたが、
OnTriggerEnterOnTriggerExitが呼ばれるColliderであればどんなものでも機能します
SensorToolkit 2には円錐状のFOV Colliderが含まれているため、視界内にいるかどうかの判定にぴったりです。

 

 

慣れている方なら推察できる通り、OnTriggerEnterOnTriggerExitを使って検出していることから、検出中のオブジェクトが非アクティブになると判定が取れなくなります。


SensorToolkit 2では、この問題の対処方*1としてSafeModeが用意されています。
このSafeModeが有効だとOnTriggerStayも併用して判定します。

 

SafeModeを使いたくない場合は、非アクティブ前にオブジェクトを遠くまで移動(OnTriggerExitを呼ばせるため)させるというひと手間を加える必要があります。

 

 

Signal Proxy

オブジェクトがセンサーに検出された時、別のオブジェクトを検出対象としてSensorに指示するコンポーネント
センサーではなく、検出される側のオブジェクトにアタッチして使用する。
これは複数のColliderを持つオブジェクトに対して使う。

 

例えば、手足のColliderがSensorに検出された時、手足ではなくルートオブジェクトをSensorに伝えたりなど。

 

 

LOS Sensor(Line of Sight Sensor)

オブジェクトを視認できるを判定し検出するセンサーです。
オブジェクトに複数のRayを飛ばし、それがどれくらい遮られているかを計算して、視認の有無を決定します。
これは他のSensorを併用して判定します。併用しているSensorに検出されたオブジェクトにのみRayを飛ばして視認性を計算します。

 

UserSignalsを使って、Sensorを使わず特定のオブジェクトだけ最初から紐づけて視認性をチェックしたり。

 

最大距離と視野角に制約をつけ、視野角ギリギリや遠くのオブジェクトの視認性にマイナス補正をかけたりできる。

 

 

Boolean Sensor

複数のセンサーをマージするセンサー
複数のセンサーを取得し、OR判定、AND判定でオブジェクトを検出したりできます。

 

 

他にSteering SensorとNavMesh Sensorもある

 

実際に試してみる (NPCの視界を作る)

実際にSensorToolkit 2 を使って、「Playerを視認したら追跡するNPC」を作ってみます。

 

ステージの作成

Navigationで移動させるので、NavMeshをBakeしておきます。
視認の有無がわかりやすいよう、Cubeを変形させて壁として配置しています。

 

 

プレイヤーの作成

適当なプレイヤーを作成します。今回はユニティちゃん(URP)を使用します。

raspberly.hateblo.jp

プレイヤーオブジェクトのレイヤーはPlayerにしておきます。

 

 

エネミーの作成

プレイヤーを追跡するエネミーを作成します。


Enemyオブジェクトを作成し、その子にLOS SensorRangeSensorCapsuleを作成します。

 

RangeSensorにはRangeSensorコンポーネントをアタッチ。
DetectsOnLayersをPlayerにします。


LOS SensorにはLOSSensorコンポーネントをアタッチ。
InputSensorに先ほど作ったRangeSensorを紐づけます。

 

 

Enemyには下記のEnemyAIコンポーネントNavMeshAgentをアタッチします。
EnemyAIのSightにはLOS Sensorを紐づけます。


EnemyAIはこちら
プレイヤーを視認したら追跡し、見失ったら即座に停止します。

using UnityEngine;
using UnityEngine.AI;
// SensorToolkitを使う場合はこれが必要
using Micosmo.SensorToolkit;

public class EnemyAI : MonoBehaviour
{
    [SerializeField] Sensor sight;
    NavMeshAgent agent;
    GameObject target;

    void Start ()
    {
        agent = GetComponent<NavMeshAgent>();
        sight.OnDetected.AddListener(OnDetect);
        sight.OnLostDetection.AddListener(OnLost);
    }

    /// <summary>
    /// 見つけたときに呼ばれる関数
    /// </summary>
    public void OnDetect(GameObject detection, Sensor sensor)
    {
        var detectObjs = sensor.GetDetections();
        // 見つけたら追跡開始
        // 今の所Playerは1体だけなのでこれでやる
        target = detectObjs[0];
        agent.isStopped = false;
    }

    /// <summary>
    /// 見失ったときに呼ばれる関数
    /// </summary>
    public void OnLost(GameObject detection, Sensor sensor)
    {
        target = null;
        agent.isStopped = true;
    }

    void Update ()
    {
        if(target)
        {
            agent.SetDestination(target.transform.position);
        }
    }

}

LOS Sensorにイベントを紐づけて追跡状態を切り替えます。
AddListenerせず、var detectObjs = sight.GetDetections(); で Sensorで検出しているオブジェクトを取得しいろいろやってもいいですね。(最初はそうやってた)

 

 

これで完成、視界が黄色いギズモで表示されていますね。

 

動かしてみると、視界に入っている時だけプレイヤーを追跡しているのがよくわかりますね。

見失ったら即座に停止しているので、数秒は追いかけるという風に書き換えてもいいかも。

 

 

CapsuleをSDユニティちゃんに置き換えたバージョン

 

 

まとめ

SensorToolkit 2はオブジェクトを検出するセンサー機能を提供するアセットです。
各センサーの概要、実際にオブジェクトを検出する方法を紹介しました。
NPCの視界を実装するのに便利!

 

 

 

他のアセットの紹介記事はこちら↓

raspberly.hateblo.jp

 

 

 

※本記事にはAssetStoreアフィリエイトリンクが含まれています。

他、間違っている箇所、わかりにくい所がありましたらコメントにお願いします。

*1:正しい解決方法はあるが複雑なので妥協案としてセーフモードが用意されている