扇形の当たり判定をつけて、リアルな視界を作る

今回はFPSでの敵キャラの設定などで使えそうな、扇形の当たり判定でリアルな人間の視界を作る実装について記事を書いていきます！

:::note info
使用PC： MacBook Pro (13-inch, M1, 2020)
OSバージョン: 11.5.2
Unityバージョン：2020.3.30f
:::

# 完成形
![画面収録-2022-05-07-18.28.36 (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/f3b5f287-6211-d8b8-d643-af3c3e30bc2a.gif)

動画は若干わかりにくいですが、視界の一定範囲にターゲットが入った場合にDebug.Logを出力するような機能になっています。視界範囲内に障害物があった場合は判定されません。

# 概要
![qiita111.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/67f9d950-9ed3-fca7-f3b1-5e49bb2d2cdf.png)

グレーのような扇形の部分を視界の範囲とし、ターゲットとなるGameObjectがそこに入ったら当たり判定を判別する仕組みを作ろうと思います！

構成内容は
>①視界の**範囲内**にいるかどうかの判定
②視界の**角度内**にいるかどうかの判定
③視界の**範囲内にターゲットが存在するか**どうかの判定

の3つの構成です！

## ちょこっと詳しく説明
### ①視界の**範囲内**にいるかどうかの判定
探す人の直線距離内、つまりどこまで遠くまで見れるかどうかを判別します。
この中に入らないとターゲットは見つけられないということになります。
⏬この画像では黄色の範囲内ですね！
![uiuiui.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/07303f78-1c56-0415-f5f8-615c5717812e.png)

### ②視界の**角度内**にいるかどうかの判定
探す人の視界の角度を扇形で指定することで、見える範囲に存在するかどうかの判定を行います。
Rotationが変われば、それに応じて判定される扇も回転します。
![ううう.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/43d25042-b0f6-f480-0619-898b4e87275e.png)

### ③視界の**範囲内にターゲットが存在するか**どうかの判定
途中で障害物などがあったときに、視界やその角度の範囲内にいたとしても直線上に他のオブジェクトで遮られていた場合は判定することができません。


# 下準備
## オブジェクトの配置
**Player, Ground, Wall, Target**の4つのオブジェクトを作成します。
今回はPlayerを`Cyllinder`、Goundを`Plane`、Wallを`Cube`、Targetを`Sphere`で作りました。

![スクリーンショット 2022-05-07 20.06.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/1bc98420-4ac8-95c1-5cb2-14bf1e141dc1.png)

わかりやすいようにMaterialで色もつけました。
>Materialは`Projectビュー`→`Create`→`Material`で作成します。

![スクリーンショット 2022-05-07 20.40.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/26d7fc20-ffe2-2d22-6e3c-4de21a6ae13c.png)

## コライダーの設定
①Player, Wall, Targetには`Rigidbody`をAdd Componentしてください。
![スクリーンショット 2022-05-08 16.16.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/a40dec7a-74b6-dfe5-209b-6c497941974c.png)

②Playerに`SphereCollider`をつけて、Radiusなどをいじっていい感じに大きさや位置を調整します。
この後このSphereColliderが自分の視界の範囲内にいるかどうかを判定するエリアになります。
![画面収録-2022-05-07-18.28.36_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/78bd1a01-da80-2561-7081-888d0e9f557f.gif)

③Sphere Colliderの`is Trigger`にチェックを入れます。
![スクリーンショット 2022-05-08 16.26.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/26a4fc9f-fb65-0c9c-5469-aca6760c2983.png)

## Targetにタグ付けを行う
①TargetのオブジェクトにInspectorビューから`Add Tag`を選択します。
![スクリーンショット 2022-05-08 16.28.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/1cd23eb7-55d4-8ca9-8f8c-85f79dd77628.png)

②＋ボタンから`target`と入力してsave。
![スクリーンショット 2022-05-08 16.28.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/11401242-d60f-9b02-326d-44e81a5b3a1b.png)

③もう一度TargetオブジェクトのInspectorビューに戻り、さっき加えたtargetタグを付与します。
![スクリーンショット 2022-05-08 16.29.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/591346fa-38d5-3c86-2d98-bea287304cbb.png)

## スクリプトを作成
①`Projectビュー`→`Create`→`C# Script`でスクリプトを作成します。
![スクリーンショット 2022-05-08 16.33.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/488f4f35-2fd1-4655-fc90-934959648f2f.png)

②名前を`Fun Search`とか好きな名前にします。
![スクリーンショット 2022-05-08 16.34.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/2e45a0e0-d1a2-4a8e-6dc1-df3ed44e7c97.png)

③Playerオブジェクトにスクリプトを紐付け。
![スクリーンショット 2022-05-08 16.36.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/fca9053e-d968-768d-797b-3d161c043e0d.png)


# コードを書く
👇コードの全文はこちら

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FunSearch : MonoBehaviour
{
    public float angle = 45f;

    private void OnTriggerStay(Collider other)
    {
        if (other.gameObject.tag == "target") //視界の範囲内の当たり判定
        {
            //視界の角度内に収まっているか
            Vector3 posDelta = other.transform.position - this.transform.position;
            float target_angle = Vector3.Angle(this.transform.forward, posDelta);

            if (target_angle < angle) //target_angleがangleに収まっているかどうか
            {
                if(Physics.Raycast(this.transform.position, posDelta, out RaycastHit hit)) //Rayを使用してtargetに当たっているか判別
                {
                    if (hit.collider==other)
                    {
                        Debug.Log("range of view");
                    }
                }
            }
        }
    }
}
```

## コードの解説
概要の部分の①、②、③の順番で条件のコードを記述していきます。

### 変数宣言
```C#
public float angle = 45f;
```
視界の範囲を45度として、変数として宣言しておきます。

視界の角度内に収まっているかどうかの判定については後述のコードの中で行います。

### 視界の範囲を設定
```C#
 private void OnTriggerStay(Collider other)
    {
        if (other.gameObject.tag == "target")
```
先ほどSphere Colliderのis Triggerにチェックを入れているので、Triggerモードで当たり判定を取れているものを教えてくれる関数を書きます。
今回はタグを利用するので、引数にあるother、つまり「当たった側の情報であるtagが`target`だった場合」という条件をif文で記述します。

### 角度の範囲内か確認
```C#
 Vector3 posDelta = other.transform.position - this.transform.position;
 float target_angle = Vector3.Angle(this.transform.forward, posDelta);

if (target_angle < angle)
            {
```
Playerの正面に対して、Targetの位置を取得してそれが角度が45度以内かどうか算出します。

![qiita111.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/da02e501-cbab-3b9e-cba5-111bb353acb8.png)

まずは位置のベクトルを引き算したものを表します。これを出すことでTargetに対するベクトルを出すことができます。
```C#
 Vector3 posDelta = other.transform.position - this.transform.position;
```
相手側のポジションと自分のポジションの差分を`posDelta`とします。
中学数学のベクトルの考え方と同じですね！
![Frame 4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/157b1283-1310-a45d-febf-9fb09e21ae66.png)![Frame 3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890419/e9a9254c-959a-1ff3-4a86-a98b257c34d4.png)

```C#
float target_angle = Vector3.Angle(this.transform.forward, posDelta);
```
ここでPlayerの正面とposDeltaのなす角度をここで測ります。
`Vector3.Angle()`は角度取得を簡単にできるメソッドです。自分の正面のベクトルをとる`this.transform.forward`に対して、第２引数に`posDelta`を入れることで**正面に対して何度の角度か**を得ることができます！

```C#
if (target_angle < angle)
```
if文の中に「算出した`target_angle`が変数宣言で指定している`angle`に収まっているかどうか」という条件文を書きます。
### 障害物を判定する
PlayerからTargetに対して`Ray`を飛ばし、その間に「別のものが当たり判定で取られないかどうか」確認する動作を行なっていきます。

>**Ray** とは、直訳すると「光線」という意味です。
シーン上の指定した地点から光線を飛ばし、光線上にあるコライダーの情報を取得するのに用いられます。


```C#
Physics.Raycast(Rayの開始位置, Rayの方向, 衝突したオブジェクトの情報, Rayの長さ);
```
といったように使います。いくつか要素がありますが、必要な要素は`Rayの開始位置`と`Rayの方向`のみです。

今回は以下のように記しました。
```C#
 if(Physics.Raycast(this.transform.position, posDelta, out RaycastHit hit))
```
第1引数には`this.transform.position`、第2引数には`posDelta`を入れます。
すでにVector3でベクトルを作っているので、今回はposDeltaを使用します。

また、`out RaycastHit hit`の**hit**は何があたったかという情報を持っています。

```C#
if (hit.collider==other)
     {
        Debug.Log("range of view");
     }    
```
RaycasthitのColliderを使って、あたった相手側である引数`other`に当たったかどうかのチェックを行います。

# 以上！！
実際にunityを動かしてみると、前方向一定の角度内に入っている場合のみにDebug.Logが表示されるはずです。

# 参考
>・https://www.youtube.com/watch?v=sM5eB4I1JwA
・http://physics.thick.jp/Physical_Mathematics/Section3/3-1.html
・https://nekojara.city/unity-vector-angle
・https://rightcode.co.jp/blog/become-engineer/unity-raycast-ray
・https://docs.unity3d.com/ja/current/ScriptReference/Physics.Raycast.html