# AI　モンスターの知能

# モンスターに視覚を与える

## ****isFindPlayer****<span style="white-space: pre-wrap;"> は誰が決めているのか？</span>

---

## 1. 前回までの確認

<span style="white-space: pre-wrap;">前回、モンスターの行動を </span>`<span class="editor-theme-code">if文</span>`<span style="white-space: pre-wrap;"> で切り替える考え方を学びました。</span>

例えば、次のような処理です。

```cpp
if (isAttackRange)
{
    Attack();
}
else if (isFindPlayer)
{
    ChasePlayer();
}
else
{
    Idle();
}
```

この処理では、モンスターは次のように行動します。

<table id="bkmrk-%E6%9D%A1%E4%BB%B6%E8%A1%8C%E5%8B%95%E6%94%BB%E6%92%83%E7%AF%84%E5%9B%B2%E3%81%AB%E3%81%84%E3%82%8B%E6%94%BB%E6%92%83%E3%81%99%E3%82%8B%E3%83%97%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC"><colgroup><col></col><col></col></colgroup><tbody><tr><th>条件

</th><th>行動

</th></tr><tr><td>攻撃範囲にいる

</td><td>攻撃する

</td></tr><tr><td>プレイヤーを見つけている

</td><td>追いかける

</td></tr><tr><td>どちらでもない

</td><td>待機する

</td></tr></tbody></table>

このように、条件によって行動を変えることで、モンスターが考えて動いているように見えます。

---

## 2. ここで問い

次の変数に注目してください。

```cpp
isFindPlayer
```

これは、

```text
プレイヤーを見つけているか？
```

を表すフラグです。

しかし、ここで考えるべきことがあります。

---

# `<span class="editor-theme-code">isFindPlayer</span>`<span style="white-space: pre-wrap;"> は誰が決めているのか？</span>

---

プログラムの中で、

```cpp
if (isFindPlayer)
```

と書くことはできます。

しかし、`<span class="editor-theme-code">isFindPlayer</span>`<span style="white-space: pre-wrap;"> が </span>`<span class="editor-theme-code">true</span>`<span style="white-space: pre-wrap;"> になる理由を作らなければ、モンスターはプレイヤーを見つけることができません。</span>

つまり、

```cpp
isFindPlayer = true;
```

になる条件を、こちらで作る必要があります。

---

## 3. ゲームAIには「知覚」が必要

モンスターが行動を決めるには、まずゲームの状況を知る必要があります。

例えば、

- プレイヤーが近くにいる
- プレイヤーが前方にいる
- プレイヤーとの間に壁がない
- プレイヤーが音を立てた
- プレイヤーが攻撃してきた

などです。

<span style="white-space: pre-wrap;">このように、AIがゲームの状況を知る仕組みを </span>****知覚****<span style="white-space: pre-wrap;"> と呼びます。</span>

---

## 4. 人間の「見る」とAIの「見る」は違う

人間は目で映像を見ています。

しかし、ゲームAIは本当に画面を見ているわけではありません。

ゲームAIは、次のような数値や条件を使って「見えているか」を判定します。

```text
距離は近いか？
前方にいるか？
壁に隠れていないか？
```

つまり、ゲームAIの視覚とは、

```text
画像を見ること
```

ではなく、

```text
条件を満たしたら「見えている」とする処理
```

です。

---

## 5. まず一番簡単な視覚を作る

最初は、距離だけで考えます。

例えば、

```text
プレイヤーが10m以内にいたら発見
```

というルールにします。

この場合、モンスターはプレイヤーとの距離を調べます。

```cpp
VECTOR toPlayer = VSub(playerPos, enemyPos);
float distance = VSize(toPlayer);

if (distance < VIEW_DISTANCE)
{
    isFindPlayer = true;
}
else
{
    isFindPlayer = false;
}
```

ここで、`<span class="editor-theme-code">VIEW_DISTANCE</span>`<span style="white-space: pre-wrap;"> は視界距離です。</span>

```cpp
const float VIEW_DISTANCE = 10.0f;
```

---

## 6. 距離だけの視覚の問題点

距離だけで判定すると、かなり単純なAIになります。

例えば、次のような問題があります。

---

### 問題1：後ろにいても見つかる

距離だけで判定すると、プレイヤーがモンスターの後ろにいても見つかってしまいます。

```text
プレイヤー
   ↓
モンスター → 前を向いている
```

本当なら、モンスターの後ろにいるプレイヤーは見えないはずです。

でも距離だけで判定していると、近ければ見つかります。

---

### 問題2：壁越しでも見つかる

距離だけで判定すると、壁の向こうにいるプレイヤーも見つかってしまいます。

```text
モンスター　｜壁｜　プレイヤー
```

本当なら、壁があるので見えないはずです。

でも距離だけで判定していると、近ければ見つかります。

---

## 7. 視覚に必要な条件

より自然な視覚を作るには、次の3つを考えます。

```text
1. 距離
2. 向き
3. 障害物
```

---

## 8. 条件1：距離

まずは距離です。

```text
プレイヤーが遠すぎるなら見えない
```

これは分かりやすい条件です。

```cpp
if (distance > VIEW_DISTANCE)
{
    isFindPlayer = false;
}
```

---

## 9. 条件2：向き

次に、プレイヤーがモンスターの前方にいるかを調べます。

モンスターには向きがあります。

```text
モンスター → 前方
```

プレイヤーが前方にいれば見える。

```text
モンスター → プレイヤー
```

しかし、プレイヤーが後ろにいるなら見えません。

```text
プレイヤー ← モンスター
```

---

## 10. 前方にいるかをどう調べるか

<span style="white-space: pre-wrap;">ここで使うのが </span>****内積****<span style="white-space: pre-wrap;"> です。</span>

必要なベクトルは2つです。

```text
モンスターの前方向
モンスターからプレイヤーへの方向
```

---

## 11. モンスターからプレイヤーへの方向

まず、モンスターからプレイヤーへのベクトルを求めます。

```cpp
VECTOR toPlayer = VSub(playerPos, enemyPos);
```

次に、長さを1にします。

```cpp
float distance = VSize(toPlayer);
VECTOR dirToPlayer = VScale(toPlayer, 1.0f / distance);
```

<span style="white-space: pre-wrap;">この </span>`<span class="editor-theme-code">dirToPlayer</span>`<span style="white-space: pre-wrap;"> が、モンスターから見たプレイヤーの方向です。</span>

---

## 12. モンスターの前方向

次に、モンスターの前方向を用意します。

例として、Y軸回転から前方向を作るなら、次のようになります。

```cpp
VECTOR forward =
{
    sinf(enemyRotY),
    0.0f,
    cosf(enemyRotY)
};
```

ただし、モデルの正面方向や座標系によって、`<span class="editor-theme-code">sinf</span>`<span style="white-space: pre-wrap;"> と </span>`<span class="editor-theme-code">cosf</span>`<span style="white-space: pre-wrap;"> の使い方は変わることがあります。</span>

---

## 13. 内積で角度を調べる

2つのベクトルの内積を取ります。

```cpp
float dot = VDot(forward, dirToPlayer);
```

`<span class="editor-theme-code">dot</span>`<span style="white-space: pre-wrap;"> の値は、だいたい次のように考えられます。</span>

<table id="bkmrk-dot%E3%81%AE%E5%80%A4%E6%84%8F%E5%91%B31.0-%E3%81%AB%E8%BF%91%E3%81%84%E6%AD%A3%E9%9D%A2%E3%81%AB%E3%81%84%E3%82%8B0"><colgroup><col></col><col></col></colgroup><tbody><tr><th>dotの値

</th><th>意味

</th></tr><tr><td>1.0 に近い

</td><td>正面にいる

</td></tr><tr><td>0.0 に近い

</td><td>横にいる

</td></tr><tr><td>-1.0 に近い

</td><td>後ろにいる

</td></tr></tbody></table>

---

## 14. 視野角の判定

例えば、次のようにします。

```cpp
const float VIEW_DOT = 0.5f;

if (dot > VIEW_DOT)
{
    isFindPlayer = true;
}
else
{
    isFindPlayer = false;
}
```

`<span class="editor-theme-code">dot > 0.5f</span>`<span style="white-space: pre-wrap;"> なら、前方のある範囲内にいると判断できます。</span>

---

## 15. VIEW\_DOT の目安

<table id="bkmrk-%E6%9D%A1%E4%BB%B6%E8%A6%8B%E3%81%88%E3%82%8B%E7%AF%84%E5%9B%B2%E3%81%AE%E7%9B%AE%E5%AE%89dot-%3E-0.5f"><colgroup><col></col><col></col></colgroup><tbody><tr><th>条件

</th><th>見える範囲の目安

</th></tr><tr><td>`<span class="editor-theme-code">dot > 0.5f</span>`

</td><td>前方約120度

</td></tr><tr><td>`<span class="editor-theme-code">dot > 0.707f</span>`

</td><td>前方約90度

</td></tr><tr><td>`<span class="editor-theme-code">dot > 0.866f</span>`

</td><td>前方約60度

</td></tr></tbody></table>

数値を大きくすると、視野が狭くなります。

数値を小さくすると、視野が広くなります。

---

## 16. 距離と向きを組み合わせる

距離と向きを組み合わせると、次のような判定になります。

```cpp
bool CheckCanSeePlayer()
{
    VECTOR toPlayer = VSub(playerPos, enemyPos);
    float distance = VSize(toPlayer);

    if (distance > VIEW_DISTANCE)
    {
        return false;
    }

    if (distance < 0.001f)
    {
        return true;
    }

    VECTOR dirToPlayer = VScale(toPlayer, 1.0f / distance);

    VECTOR forward =
    {
        sinf(enemyRotY),
        0.0f,
        cosf(enemyRotY)
    };

    float dot = VDot(forward, dirToPlayer);

    if (dot < VIEW_DOT)
    {
        return false;
    }

    return true;
}
```

この関数は、

```text
プレイヤーが見えているなら true
見えていないなら false
```

を返します。

---

## 17. 条件3：障害物

まだ問題があります。

距離が近くて、前方にいても、壁の向こうにいるなら本当は見えません。

```text
モンスター ---- 壁 ---- プレイヤー
```

この場合は、見えないようにしたいです。

---

## 18. 壁で見えない処理

考え方は単純です。

```text
モンスターからプレイヤーまで線を引く
↓
途中に壁があるか調べる
↓
壁があれば見えない
↓
壁がなければ見える
```

このような判定を、レイ判定やライン判定と呼びます。

---

## 19. 疑似コード

```cpp
bool isHitWall = CheckLineHitWall(enemyPos, playerPos);

if (isHitWall)
{
    return false;
}
```

`<span class="editor-theme-code">CheckLineHitWall</span>`<span style="white-space: pre-wrap;"> は、敵とプレイヤーの間に壁があるか調べる処理だと考えてください。</span>

---

## 20. 最終的な視覚判定

距離、向き、壁を組み合わせると、次のような流れになります。

```cpp
bool CheckCanSeePlayer()
{
    VECTOR toPlayer = VSub(playerPos, enemyPos);
    float distance = VSize(toPlayer);

    // 遠すぎるなら見えない
    if (distance > VIEW_DISTANCE)
    {
        return false;
    }

    // ほぼ同じ位置なら見えている扱い
    if (distance < 0.001f)
    {
        return true;
    }

    // 方向を求める
    VECTOR dirToPlayer = VScale(toPlayer, 1.0f / distance);

    // モンスターの前方向
    VECTOR forward =
    {
        sinf(enemyRotY),
        0.0f,
        cosf(enemyRotY)
    };

    // 前方にいるか調べる
    float dot = VDot(forward, dirToPlayer);

    if (dot < VIEW_DOT)
    {
        return false;
    }

    // 壁があれば見えない
    if (CheckLineHitWall(enemyPos, playerPos))
    {
        return false;
    }

    return true;
}
```

---

## 21. 前回のAIに接続する

視覚判定ができたら、前回のAIに接続できます。

```cpp
isFindPlayer = CheckCanSeePlayer();

if (isAttackRange)
{
    Attack();
}
else if (isFindPlayer)
{
    ChasePlayer();
}
else
{
    Idle();
}
```

これで、

```text
見えたら追いかける
見えなければ待機する
```

というモンスターになります。

---

## 22. 何がAIらしくなるのか

距離だけで判定していたときは、

```text
近ければ必ず見つかる
```

という動きでした。

しかし、視野角や壁判定を入れると、次のようになります。

```text
後ろに回ると気づかれない
壁に隠れると見つからない
正面に出ると追いかけてくる
```

このようなルールがあると、プレイヤーは、

```text
敵に見つかった
敵から隠れた
敵の背後を取った
```

と感じやすくなります。

つまり、AIが少し賢く見えます。

---

## 23. 重要な考え方

ゲームAIは、本当に人間のように世界を理解しているわけではありません。

しかし、次のような情報を使うことで、考えているように見せることができます。

```text
距離
方向
角度
障害物
時間
状態
```

AIは、世界をそのまま理解しているのではなく、ゲームに必要な情報だけを取り出して判断しています。

---

# まとめ

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

```text
isFindPlayer は勝手に決まらない
```

```text
AIには知覚が必要
```

```text
ゲームAIの視覚は画像を見ることではない
```

```text
距離・向き・壁で「見えている」を作る
```

```text
見えているかどうかをフラグにして、行動分岐に使う
```

---

# 最後の確認問題

## 問1

次のフラグは何を表しているか説明しなさい。

```cpp
bool isFindPlayer;
```

---

## 問2

距離だけでプレイヤーを発見するAIには、どんな問題がありますか。  
2つ書きなさい。

---

## 問3

モンスターの前方にプレイヤーがいるかを調べるために使う計算は何ですか。

---

## 問4

壁の向こうにいるプレイヤーを見えないようにするには、どのような判定が必要ですか。

---

## 問5

次の処理の意味を説明しなさい。

```cpp
isFindPlayer = CheckCanSeePlayer();
```

---

# 発展課題

次のようなモンスターを考えなさい。

```text
普段は巡回している。
プレイヤーが視界に入ったら追いかける。
壁の向こうに逃げられたら見失う。
見失ったら、その場で3秒間探す。
それでも見つからなければ巡回に戻る。
```

このモンスターに必要なフラグを考えなさい。

例：

```cpp
bool isFindPlayer;
bool isLostPlayer;
bool isSearching;
bool isPatrol;
```

<span style="white-space: pre-wrap;">さらに、どのような順番で </span>`<span class="editor-theme-code">if文</span>`<span style="white-space: pre-wrap;"> を書けばよいか考えなさい。</span>