見出し画像

【Unity初心者向け】Raycast の活用④ | 敵からプレイヤーが見えているか判定する

前回は、前方向と下方向へRaycastを飛ばして、壁や崖を見つけて方向転換する敵を作りました。

これまでのRaycastでは、

  • 下方向へ飛ばして地面を確認する

  • 前方向へ飛ばして壁を確認する

というように、決められた方向へRayを飛ばしていました。

今回は、敵からプレイヤーへ向かってRaycastを飛ばし、プレイヤーが見えているか調べます。

例えば、敵とプレイヤーの間に何もなければ、Raycast が Player まで届くので、プレイヤーを発見できます。

一方、途中に壁があると、Raycast は先に壁へ当たります。
この場合は「壁の向こうにPlayerがいるが、Enemyからは見えていない」と判断できます。

この仕組みを使うと、

  • プレイヤーを発見する

  • 壁の向こうにいれば気づかない

  • 発見したら追いかける

  • 見失ったら動きを止める

といった、敵AIの基本的な仕組みを作ることができます。
今回は、Raycastを敵の視線として使ってみましょう。



ステージの準備

今回は、仕組みを分かりやすくするために、ステージを上から見た形で作ってみます。

必要なGameObjectは、

  • Enemy

  • Player

  • Wall

の3種類、例えば次のように配置します。

EnemyとPlayerの間にWallを移動できるようにしておくと、今回の動きを確認しやすくなります。

Player と Wall には、Raycastが当たるようにCollider 2Dを設定しておきましょう。


Enemyのプログラム

Playerの場所を探す

まず、EnemyからPlayerの位置を取得できるようにします。

スクリプトファイル:EnemySight を作成して、Enemyへアタッチします。

最初は次のように記述します。

public class EnemySight : MonoBehaviour
{
    public Transform player;//Playerの位置を取得
}

今回はPlayerの位置を使用したいので、Transform型で用意しています

スクリプトをEnemyへ追加すると、Inspector にPlayerを設定する欄が表示されます。

Hierarchy にあるPlayerをドラッグ&ドロップして設定してください。

player.position

を使ってPlayerの現在位置を取得できるようになります。


Playerがいる方向を求める

ここからが今回の重要なポイントです。

これまで Raycast の方向には、

  • Vector2.right

  • Vector2.down

を使ってきました。

しかし今回は、Playerがどこにいるかによって Raycast を飛ばす方向が変わります。
そこで、Enemy から Player へ向かう方向を計算します。

次のように記述します。

void Update()
{
    // プレイヤーの方向を計算
    Vector2 direction = player.position - transform.position;
}

Player の位置から Enemy の位置を引くことで、Enemy から Player へ向かう方向を作ることができます。

例えば、EnemyがX座標:2、PlayerがX座標:7にいる場合は、
7 - 2 = 5
となります。
正の値になるので、Enemyから見てPlayerは右方向にいます。

Playe rが Enemy より左側へ移動すると結果は負の値になり、今度は左方向になります。

つまり、Playerがどこへ移動しても、

 player.position - transform.position

によって Enemy から Player へ向かう方向を求めることができます。


normalizedで方向の長さをそろえる

先ほど作った direction には、方向だけでなくEnemyとPlayerの距離も含まれています。
そこで .normalized を使います。

コードを次のように変更します。

void Update()
{
    // プレイヤーの方向を計算して正規化する
     Vector2 direction =
        (player.position - transform.position).normalized;
}

normalized を使うと、ベクトルの向きはそのままにして、長さを1にできます。
今回は、

  • direction → どちらへ飛ばすか

  • viewDistance → どこまで飛ばすか

というように、方向と距離を別々に管理したいのでnormalizedを使います。ここでは、「Playerへ向かう方向だけを取り出している」と考えておけばOKです。


Playerへ向かってRaycastを飛ばす

方向が作れたので、Raycastを飛ばしてみましょう。

まずRaycastの距離を設定するために、

public float viewDistance = 10f;

を追加します。
そしてUpdateの中でRaycastを使用します。

public float viewDistance = 5f; // 視界の距離

void Update()
{
    // 記述済みコード
    Vector2 direction =
        (player.position - transform.position).normalized;

    // プレイヤーの方向にRaycastを飛ばす
    RaycastHit2D hit = Physics2D.Raycast(
        transform.position,     // Raycastの発射位置
        direction,              // Raycastの方向(プレイヤーの方向)
        viewDistance            // Raycastの距離(視界の距離)
    );
}

これまで使用していた、

  • Vector2.right

  • Vector2.down

の代わりに、今回は自分で計算した direction を使っているわけです。


Raycastの可視化

Raycast は、そのままではGameビューやSceneビューに線として表示されません。
そこで今回も、

Debug.DrawRay()

を使って確認してみましょう。

Updateの中へ追加します。

// Raycastを可視化
Debug.DrawRay(
    transform.position,         // Raycastの発射位置
    direction * viewDistance,   // Raycastの方向と距離を指定
    Color.red                   // Raycastの色を赤に設定
);

全体は次のようになります。

ゲームを実行してSceneビューを確認してみましょう。
EnemyからPlayerの方向へ赤い線が伸びます。
Playerの位置を変えると、Rayの方向もPlayerに合わせて変化します。


Raycastが何に当たったか調べる

Raycast を Player へ向けられるようになりました。
次は、Raycast が何に当たったのかを確認します。

これまでにも使用した、hit.collider を使います。

if (hit.collider != null)  // Raycastが何かに当たった場合
{
    Debug.Log(hit.collider.name);
}

これを Update() に追加してゲームを実行してみましょう。

Enemy と Player の間に何もなければ、Raycast は Player に当たります。
この場合、hit.collider には Player の Collider が入ります。
Consoleにも「Player」が表示されます。

では、EnemyとPlayerの間にWallを置いてみましょう。
今度はRaycastが先にWallへ当たるので、hit.collider には Wall の Collider が入ります。
Consoleには「Wall」が表示されます。

Raycast が Player に当たったなら Player が見えている。
Raycast が Wall に当たったなら、その先にいる Player は見えていない。

という判定ができます。


PlayerかどうかをTagで確認

Raycastが何に当たったか確認できたので、今度は当たったGameObjectがPlayerなのかを調べます。

Player の Inspector をから Tag に Player を指定します。

さらにコードを次のように変更します。

if (hit.collider != null)
{
    if (hit.collider.CompareTag("Player")) // プレイヤーに当たった場合
    {
        Debug.Log("プレイヤー発見!");
    }
}

CompareTag() を使うと、GameObject に設定されている Tag を確認できます。
Player に直接 Raycast が当たれば、

プレイヤー発見!

と Console へ表示されます。

途中にWallを置くと、Raycastが当たるのはWallです。
そのため、CompareTag("Player") は false となり「プレイヤー発見!」は表示されません。

これだけでも、PlayerがEnemyから見えているかを判定できるようになりました。


LayerMaskで調べる対象を限定する

現在のRaycastは、途中にあるさまざまなColliderへ反応します。
第2回では、Raycastで調べる対象を限定するために LayerMask を使用しました。

今回も LayerMask を使ってみましょう。
例えば、次の2つの Layer を用意します。

  • Player

  • Wall

Playerには、Layer:Player、Wallには、Layer:Wall をそれぞれを設定します。

EnemySight には、

public LayerMask detectionLayer;    // プレイヤーを検知するレイヤー

を追加します。
Inspector に Detection Layer が表示されるので、Player, Wall の両方を選択します。

そして Raycast に検知対象のレイヤーを追加します。
次のように変更してください。

RaycastHit2D hit = Physics2D.Raycast(
    transform.position,
    direction,
    viewDistance,
    detectionLayer  // プレイヤーを検知するレイヤーを指定
);

これで今回のRaycastは、PlayerとWallのColliderだけを調べるようになります。


PlayerだけをLayerMaskにしてはいけない?

ここで注意があります。
Playerを探しているのだから、

Detection Layer

☑ Player
□ Wall

でよさそうに思えるかもしれません。
しかし、これでは今回の目的を正しく実現できません。

例えば、Enemy と Player の間に Wall があった場合を考えてみます。
Raycast の対象が Player だけだと、Wall の Collider は調べる対象から外れます。
すると Raycast は Wall を無視して、その先にいる Player を検出してしまいます。

結果として、壁の向こうにいるのにPlayerを発見してしまうことになります。

今回調べたいのは「Playerがいるか」だけではありません。
EnemyとPlayerの間に「視線を遮るものがあるか」も調べる必要があります。
そのため、

Detection Layer

☑ Player
☑ Wall

のように、Playerと視線を遮るWallの両方をRaycastの対象にします。


完成したスクリプトを確認

今回のEnemySightスクリプトは次のようになります。

public class EnemySight : MonoBehaviour
{
    public Transform player;//Playerの位置を取得
    public float viewDistance = 10f; // 視界の距離
    public LayerMask detectionLayer;    // プレイヤーを検知するレイヤーを指定

    void Update()
    {
        // プレイヤーの方向を計算して正規化する
        Vector2 direction =
            (player.position - transform.position).normalized;

        // プレイヤーの方向にRaycastを飛ばす
        RaycastHit2D hit = Physics2D.Raycast(
            transform.position,     // Raycastの発射位置
            direction,              // Raycastの方向(プレイヤーの方向)
            viewDistance,           // Raycastの距離(視界の距離)
            detectionLayer          // プレイヤーを検知するレイヤーを指定
        );

        // Raycastを可視化
        Debug.DrawRay(
            transform.position,         // Raycastの発射位置
            direction * viewDistance,   // Raycastの方向と距離を指定
            Color.red                   // Raycastの色を赤に設定
        );

        if (hit.collider != null)   // Raycastが何かに当たった場合
        {
            if (hit.collider.CompareTag("Player"))  // プレイヤーに当たった場合
            {
                Debug.Log("プレイヤー発見!");
            }
        }
    }
}

ゲームを実行して確認してみましょう。

EnemyとPlayerの間に何もなければ、Console には、

プレイヤー発見!

と表示されます。

間にWallを置くと、Raycast が Wall へ当たるので、Player は発見されません。
Wall を移動して視線が通るようにすると、再び Player を発見します。

これで、敵からPlayerが見えているか をRaycastで判定できました。


発見したあとの処理

今回は分かりやすくするため、

Debug.Log("プレイヤー発見!");

と表示するだけにしています。
しかし実際のゲームでは、この部分にさまざまな処理を入れることができます。
例えば、

  • 追いかける

  • 攻撃する

  • 警戒状態へ変更する

といった使い方ができます。
つまりRaycastは、敵がPlayerを発見するきっかけとして利用できます。


今回のまとめ

今回は、Raycastを使ってEnemyからPlayerが見えているか判定する方法を確認しました。

今回のポイントは次のとおりです。


次回は……

今回は、Raycastを敵の視線として使い、Playerが見えているかを判定しました。
Raycastで取得できる情報は、hit.colliderだけではありません。

次回は、hit.point や、hit.normal など、Raycastが当たった位置や面の向きにも注目してみます。

Raycastで取得できる情報をさらに詳しく確認しながら、シリーズの内容をまとめていきましょう。


こちらはUnity初心者向けの記事をテーマ別にまとめています。
▼ Unity初心者向け記事まとめはこちら


こちらのマガジン「ミニゲーム作成講座 全8回セット」でもシーンの切り替えなどを活用して、初心者でも知識ゼロから作れるミニゲーム作成講座を紹介しています。

さらにステップアップの「スマホで遊ぶジャンプゲーム 8回セット」のマガジンもぜひご活用ください。


ファイブボックスでは、スクラッチやUnityの個別指導のオンラインレッスンを行っています。
ご興味のある方は当サイト、オンラインレッスンから、無料体験授業へお問い合わせ下さい。

いいなと思ったら応援しよう!

ファイブボックス よろしければ応援お願いします! いただいたチップはクリエイターとしての活動費に使わせていただきます!