公開日:2023/09/27  更新日:2023/09/27

Unityの敵AIの作り方

 前回までの記事で敵が視界を持ち、Playerを見つけると徘徊行動→Playerへ近づく行動に変化するようにした。


 前回までの記事↓
TPSのキャラ移動
ジャンプ
ダッシュ実装
自作キャラを動き回らせる
剣を振る方法
剣コンボ
足が浮く対策
剣の軌跡の作り方
燃える剣の必殺技
ステータス管理
ダメージ判定・処理
HPゲージ
敵の攻撃
メッシュ変形での範囲攻撃
ビーム攻撃
NavMeshの使い方
敵の徘徊
視界の判定

 だが、敵がPlayerに突っ込んでいくだけでは面白くない。そこで、距離をとって遠距離攻撃を仕掛けたり左右から回り込むなど様々な行動をとる賢いAIを作り方を紹介していく。

Unityでの敵AIの作り方

ステータス記述
敵にとってほしい行動パターンを実現する
実際のスクリプト記述
目次にもどる

ステータス記述

 AIの作り方は実に様々だ。
 今回は敵であるスケルトンをとってほしい行動を考え、プレイヤーからの距離を元にして以下の4行動パターンを作成し、4パターンのうち、どの行動を多くとるかを優先度のパラメータを作って決めていく。

 ①近づいて近接攻撃

 ②距離をとって遠距離攻撃

 ③Playerの左右に回り込んで近接攻撃

 ④Playerの左右に回り込んで遠距離攻撃

 そのために必要なステータスをScriptableObjectを使って作っていく。
 なお、ScriptableObjectがわからない場合は以下の記事で紹介しているので確認してほしい。
ステータス管理

 遠距離攻撃不可距離
 遠距離攻撃を撃たない近距離。名前はLongAttackdameとした。

 移動速度
 プレイヤーを見つけた際の移動速度。名前はIdouSpeedとした。

 移動速度
 プレイヤーを見つけた際の移動速度。名前はIdouSpeedとした。

 回転速度
 プレイヤーに向く速度。名前はIdouKaitenとした。

 行動継続時間(最低)
 敵が同じ種類の行動をとる最低時間。名前はKoudoukeizokuminとした。

 行動継続時間(最高)
 敵が同じ種類の行動をとる最高時間。名前はKoudoukeizokumaxとした。

 近づいて近接攻撃優先度
 プレイヤーに最短距離で近づいて近接攻撃する行動パターンの優先度。名前はKinnkyorihindoとした。
 離れて遠距離攻撃優先度
 プレイヤーから距離をとって遠距離攻撃する行動パターンの優先度。名前はEnkyorihindoとした。

 左右回り込み近接攻撃優先度
 プレイヤーの左右どちらかの側面へ移動し近接攻撃する行動パターンの優先度。名前はMawarikomikinnsetuyuusenとした。
 左右回り込み遠距離攻撃優先度
 プレイヤーの左右どちらかの遠距離側面に移動し遠距離攻撃を仕掛ける行動パターンの優先度。名前はMawarikomikinsetuyuusenとした。

目次にもどる

敵にとってほしい行動パターンを実現する。

 以下の4つの行動パターンをどう実現するかについて紹介していく。

 ①近づいて近接攻撃

 ②距離をとって遠距離攻撃

 ③Playerの左右に回り込んで近接攻撃

 ④Playerの左右に回り込んで遠距離攻撃

 ①については以前の記事でやっているようにプレイヤーの位置をNavMeshで指定して目的地にしてやれば問題ない。

 「②距離をとって遠距離攻撃」は厄介だ。プレイヤーから離れた位置を目的地として指定すればいいのだが、この行動をとる直前にスケルトンがプレイヤーの近くにいた場合、以下のようにプレイヤーに背を向けて移動するので不自然だ(デフォルトではNavMeshで移動させると目的地へ向く)。
 そこでプレイヤーを発見した後は攻撃中以外はプレイヤーの方向を常に向き続けるようにする(攻撃直前まではプレイヤーの方向を向き続けるので、攻撃の照準をあわせる仕様にもなる)。スケルトンのNavMesh Agentのコンポーネントを見てほしい。
 「Angular Speed」の項目は目的地へ向く回転速度を示す。この値を0にしてやることで目的地へ向き変更をしなくなる。

 その上で、スクリプトでプレイヤーのほうを向き続けるようにしていく。だがこれも簡単そうで結構難しい。これを可能にするにはQuaternionを使う必要がある。
 Quaternionとは回転情報を扱う関数である(概念を全て理解するのは特に初心者には難しいが、これだけは覚えておこう)。
 ちなみにTransformコンポーネントのRotationにゲームオブジェクトの回転が記述されているが、この値はX軸、Y軸、Z軸のオイラー角(度数法、1周を360度としており人間にもわかりやすい)。

 だが、ここに入力したオイラー角をUnityはQuaternionとして計算して持っている。
 以下の記述をUpdateメソッドなどに入れることでスケルトンがプレイヤーの方向を向き続けることが可能。
// プレイヤーの位置とこの敵の位置から角度を求める。
var qrot = Quaternion.LookRotation(PlayerPosition - ThisPosition);
// 現在の角度からqrotの角度まで滑らかに回転。Time.time * charadata.IdouKaiten / 100000は回転速度。
transform.rotation = Quaternion.Slerp(this.transform.rotation, qrot, Time.time * charadata.IdouKaiten / 100000);

 var qrot = Quaternion.LookRotation(PlayerPosition - ThisPosition);でPlayerPositionとThisPositionの間のQuaternionでの角度を求めることができる。
 後はQuaternion.Slerpを使うことで、プレイヤーの方向へ滑らかに向くことができる。今の角度、向きたい角度、向く速度を記述すればOK(Time.timeをかけることでフレーム数が多ければ1回の処理の移動量が減る調整ができゲーム機の性能が違っても同じ向く速度を実現できる。また、 / 100000としているのは調整値)。

 続いて「②距離をとって遠距離攻撃」を行う際の位置の指定方法だ。回り込む必要はなく、プレイヤーからLongAttackRange(以前記事で作ったステータス)の距離だけ離れている位置でしかもスケルトンから最も近い位置(つまりPlayerからスケルトンまでのベクトル上)を目標位置として指定する必要がある。
 これも厄介であり、再びQuaternionの出番となる。Quaternionは以下のように時計周りに角度が決まっている。

 Quaternion.Eulerを使えば以上のように角度と距離が分かれば位置を求めることができる。例えば以上の黄色の部分のように60度という角度と10という距離がわかっていれば(8.7,0,5)という位置を求めることができる。
 今回の場合Playerからスケルトンに伸びるベクトル上の長さが10(これがLongAttackRangeの値)の時の位置を求めて、NavMeshの目的地としたい。
 その場合以下の記述でOKだ。
//LongAttackRangeの値に乱数を加算しAttackkyoriに代入。
            float Attackkyori = charadata.LongAttackRange;
            float Randamkyori = Random.Range(-1f, 0f);
            Attackkyori = Attackkyori + Randamkyori;


            //今回はY座標は考えずZ座標で距離をはかるので、PlayerPositionとThisPositionのY座標に0を代入。

            PlayerPosition = targetPlayer.transform.position;
            ThisPosition = this.transform.position;
            PlayerPosition.y = 0;
            ThisPosition.y = 0;


            //座標の差を求める。これがPlayerPositionからThisPositionへのベクトルとなる。
            Vector3 kyorimukikeisan = ThisPosition - PlayerPosition;

            //ワールド座標の正面ベクトルとkyorimukikeisanの間の角度を求める。
            var angle = Vector3.Angle(Vector3.forward, kyorimukikeisan);

            //ワールド座標の左方向のベクトルとkyorimukikeisanの間の角度を求める(計算用)。
            var angle2 = Vector3.Angle(Vector3.left, kyorimukikeisan);

            //angle2が90度以下なら360度からangleを引いた値をangleとする。

            if (90 > angle2)
            {
                angle = 360 - angle;
            }

            //ランダムに角度を変化させ、目標位置がランダムで変わるようにする。
            float Randamkakudo = Random.Range(-5f, 5f);
            angle = angle + Randamkakudo;

            //ランダムに角度を変えると360度を超える可能性がある。その場合は360度を引いて調整する。
            if (angle > 360)
            {
                angle = angle - 360;
            }

            //オイラー角のangleの角度とZ軸の距離であるAttackkyoriから目標位置を決める。
            NextPosition = Quaternion.Euler(0, angle, 0) * new Vector3(0, 0, Attackkyori);



            //決めた目標位置は原点(0,0,0)の位置が基準となってしまっている。そこにPlayerの位置座標を加算することで、Playerから敵までのベクトル上のAttackkyoriだけ離れた位置を目標位置とすることができる。
            NextPosition = NextPosition + PlayerPosition;

            //決めた目標位置をNavMeshで指定
            myAgent.SetDestination(NextPosition);
            koudou = 1;


 Quaternion.Eulerを使えば角度と距離が分かれば位置を求めることができる。
 角度を求めるための基準をVector3.forwardとした。kyorimukikeisanとの間をVector3.Angleで求める。ただQuaternionでは角度は以下のように時計周りとなっている。

 例えば以下のようにVector3.Angleで求めた角度がAであれば補正は必要ないが、Bの角度(Quaternionで180度以上となる角度)であれば補正が必要である。
 Quaternionの角度が180度以上かどうかはangle2ワールド座標の左方向のベクトルとkyorimukikeisanの間の角度を求め90度以下かどうか判断することで確認。Bの角度であれば360度-angleとすることでQuaternionで使える角度となる。
 後はNextPosition = Quaternion.Euler(0, angle, 0) * new Vector3(0, 0, Attackkyori);で目標位置を求める。ただ、Quaternionは原点からの位置を示す。よってPlayerの位置座標を加算することで目標位置を求められる。

 ③「Playerの左右に回り込んで近接攻撃」と④「Playerの左右に回り込んで遠距離攻撃」は②の応用。②と同様にして求めた角度angleに2分の1の確率で+90度、2分の1の確率で-90度を加算すればいい。③の場合は距離はShortAttackRangeで計算。

目次にもどる

実際のスクリプト記述

 参考までに、Enemystate1、Enemykoudou、Enemyidou1についてスクリプト全体の記述を示す(スケルトンにアタッチ)。

 Enemystate1(Enemykoudouのスクリプト呼び出しを一定間隔で行う)
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using UnityEngine;

public class Enemystate1 : MonoBehaviour
{
    Animator anim;

    [SerializeField] charadata charadata;
    [SerializeField] Transform targetPlayer;

    int State;

    float Katyokuzikan;
    int koudou;
    int katyokukoudou;
    Vector3 PlayerPosition;
    Vector3 ThisPosition;
    IEnemy Enemykoudou;

    void Start()
    {
        anim = GetComponent<Animator>();
        //IEnemyのインターフェースを宣言したコンポーネント(スクリプト)を手に入れ、Enemykoudouへ代入。
        Enemykoudou = GetComponent<IEnemy>();
    }





    void Update()
    {


        //Attackパラメータの値を代入し0より大きいなら攻撃中なのでreturn;して処理を終了する。
        bool FullAttack = anim.GetBool("FullAttacktime");
        if (FullAttack)
        {
            return;
        }

        //硬直中は攻撃できない。
        if (katyokukoudou == 2)
        {
            return;
        }
        if (katyokukoudou != 0)
        {
            Katyokuzikan = anim.GetFloat("Katyoku");
            StartCoroutine(Katyokutime());
        }
        if (katyokukoudou == 1)
        {
            katyokukoudou = 2;
            return;
        }




            //毎フレーム処理を行う必要はない。コルーチンでEnemytimeの時間だけ待機


        if (koudou == 2)
        {
            return;
        }
        if (koudou != 0)
        {
            StartCoroutine(Enemytime());
        }
        if (koudou == 1)
        {
            koudou = 2;
            return;
        }

        Debug.Log(katyokukoudou);




        //値が入っていない場合に備えnullチェック。
        if (Enemykoudou != null)
        {
            //スクリプトのEnemyAIkoudou()を呼び出し、返ってきた値をStateに代入する。
            State = Enemykoudou.EnemyAIkoudou();


            //Switch文でStateの値に応じて条件分岐。
            katyokukoudou = 1;
            koudou = 1;
            switch (State)
            {
                //Stateが0なら停止。
                case 0:

                    anim.SetInteger("Attack", 0);
                    break;

                //Stateが1なら攻撃。
                case 1:

                    anim.SetInteger("Attack", 1);
                    anim.SetBool("FullAttacktime", true);//攻撃開始。

                    break;


                //Stateが2なら遠距離攻撃。
                case 2:


                    anim.SetInteger("Attack", 11);
                    anim.SetBool("FullAttacktime", true);//攻撃開始。
                    break;

            }
        }

    }
    IEnumerator Enemytime()
    {
        yield return new WaitForSeconds(charadata.Enemytime);
        koudou = 0;
    }

    IEnumerator Katyokutime()
    {
        yield return new WaitForSeconds(Katyokuzikan);
        anim = GetComponent<Animator>();
        anim.SetFloat("Katyoku" , 0);
        katyokukoudou = 0;
    }

}


 Enemykoudou(プレイヤーまでの距離から出す攻撃行動を制御)
using RPGCharacterAnims.Actions;

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;


//IEnemyのインターフェースを宣言しているので、public int EnemyAIkoudou()のメソッドを作る必要あり。
public class Enemykoudou : MonoBehaviour,IEnemy
{
    //シリアル化している。charadataの敵を指定。
    [SerializeField] charadata charadata;

    [SerializeField] playerdata playerdata;
    [SerializeField] GameObject EyeObject;

    Vector3 PlayereyePosition;
    Vector3 EnemyeyePosition;
    RaycastHit rayzyouhou;
    float shikaikyori;


    Vector3 distance; //Playerとの距離
    int State;
    int Kougeki;

    int koudou;
    float koudoutime;

    float bigdistance;


    Animator anim;



    // Start is called before the first frame update
    void Start()
    {
        anim = GetComponent<Animator>();
        shikaikyori = charadata.Shikaikyori;

    }



    public int EnemyAIkoudou()
    {
        //Playerを確認できていないならリターン。

        bool tekihaken = anim.GetBool("Playerkakunin");
        if (tekihaken == false)
        {
            State = 0;

            return State;
        }



        //キャラ距離計算の処理
        //敵の位置からPlayerの位置を引く
        distance = transform.position - playerdata.sousaplayer.transform.position;


        //Mathf.Absで絶対値を出すことでXとZの距離がわかる。
        float distanceX = Mathf.Abs(distance.x);
        float distanceZ = Mathf.Abs(distance.z);



        //X座標とZ座標の距離のどちらが大きいか調べる大きいほうの距離をbigdistanceに代入。

        if (distanceX > distanceZ)
        {
            bigdistance = distanceX;
        }
        else
        {
            bigdistance = distanceZ;
        }



        if (charadata.ShortAttackRange >= bigdistance)
        {
            int koudousentaku = Random.Range(1, 101);
            if (charadata.Kinnkyorihindo >= koudousentaku)
            {

                State = 1;

                return State;
            }

        }

        if (charadata.LongAttackRange >= bigdistance)
        {
            if (bigdistance > charadata.LongAttackdame)
            {

                int koudousentaku = Random.Range(1, 101);
                if (charadata.Enkyorihindo >= koudousentaku)
                {


                    //敵の目の位置からプレイヤーの目の位置のベクトルを求める。

                    PlayereyePosition = playerdata.sousaplayereye.transform.position;
                    EnemyeyePosition = EyeObject.transform.position;
                    Vector3 kyorikeisan = PlayereyePosition - EnemyeyePosition;



                    //敵の目からkyorikeisanの方向へshikaikyoriの長さのレイを放つ。
                    if (Physics.Raycast(EnemyeyePosition, kyorikeisan, out rayzyouhou, shikaikyori))

                    {

                        //取得したrayzyouhouのコライダーのゲームオブジェクトのタグがPlayerかどうか。
                        if (rayzyouhou.collider.gameObject.CompareTag("Player"))
                        {
                            //rayで感知できたので敵とプレイヤーとの間に障害物がない。ビーム攻撃を行う
                            State = 2;
                            return State;
                        }
                        else
                        {
                            //Playerのタグを取得できなかったということは障害物がある。攻撃せずリターン

                            State = 0;
                            return State;
                        }

                    }
                }

            }
        }
        

        //条件を満たさない場合はStateを0として返す。何もしない。
        State = 0;
        return State;

    }
}




 Enemyidou1(向きと移動関連のスクリプト)
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.EventSystems;
using System.Collections;
using UniGLTF;
using UnityEngine.UIElements;
using Unity.VisualScripting;
using UnityEngine.Rendering.VirtualTexturing;


public class Enemyidou1 : MonoBehaviour
{
    // 目的地となるGameObjectをシリアル化。
    [SerializeField] Transform targetPlayer;
    [SerializeField] charadata charadata;
    NavMeshAgent myAgent;

    Vector3 PlayerPosition;
    Vector3 ThisPosition;
    Vector3 OldPosition;

    Vector3 NextPosition;

    Vector3 Idouryou;
    Vector3 moveDirection = Vector3.zero;
    Animator anim;

    int koudou;

    float idouzikan;

    Kakudoiti kakudoiti;

    int Koudoup;

    bool Koudousentakukeisan;



    void Start()
    {
        // Nav Mesh Agentのコンポーネントを取得。
        myAgent = GetComponent<NavMeshAgent>();
        anim = GetComponent<Animator>();
    }

    void Update()
    {




        // Playerを見つけているかどうかのパラメータを確認。見つけていないならリターン。
        bool tekihaken = anim.GetBool("Playerkakunin");
        if (tekihaken == false)
        {
            return;

        }

        myAgent.speed = charadata.IdouSpeed;
        myAgent.angularSpeed = 0;



        PlayerPosition = targetPlayer.transform.position;
        ThisPosition = this.transform.position;

        // 前の位置と今の位置の差を求める。
        Vector3 idouhandan = OldPosition - ThisPosition;


        // Playerと自分の間の距離を測定
        float kyori = Vector3.Distance(PlayerPosition, ThisPosition);

        //徘徊に戻る距離を超えたならPlayerを見失い徘徊行動へ戻る。
        if (kyori > charadata.HaikaiModorukyori)
        {
            anim.SetBool("Playerkakunin", false);
            return;
        }


        idouzikan = Random.Range(charadata.Koudoukeizokumin, charadata.Koudoukeizokumax);


        // Attackパラメータの値を取得。
        int Attack = anim.GetInteger("Attack");

        if (Attack == 0)

        {
            // Attackパラメータが0ならNavMeshをアクティブ。
            myAgent.isStopped = false;

            // プレイヤーの位置とこの敵の位置から角度を求める。
            var qrot = Quaternion.LookRotation(PlayerPosition - ThisPosition);
            // 現在の角度からqrotの角度まで滑らかに回転。Time.time * charadata.IdouKaiten / 100000は回転速度。
            transform.rotation = Quaternion.Slerp(this.transform.rotation, qrot, Time.time * charadata.IdouKaiten / 100000);


            Vector3 idouhandan2 = idouhandan.normalized;





            if (idouhandan2.magnitude > 0)
            {

                anim.SetInteger("Idouhoukou", 1);



            }
            else
            {
                anim.SetInteger("Idouhoukou", 0);

            }


        }
        else
        {
            // Attackパラメータが0でないならNavMeshを非アクティブにして移動を止める。
            myAgent.isStopped = true;
            anim.SetInteger("Idouhoukou", 0);
            return;
        }

        // 今の位置を記憶しておき、次の処理で前の位置として使う。
        OldPosition = this.transform.position;


        // koudou=1の時のみコルーチンしてリターン。koudou=2の間はリターンし続ける
        if (koudou == 2)
        {
            return;

        }
        if (koudou != 0)
        {
            StartCoroutine(Enemytime());
        }

        if (koudou == 1)
        {
            koudou = 2;
            return;
        }
        // これ以降がIdoukankakuの周期で繰り返される移動処理。

        // NavMeshをアクティブ化
        myAgent.isStopped = false;
        myAgent.speed = charadata.IdouSpeed;
        myAgent.angularSpeed = 0;




        // 乱数を1~100から決める。
        int koudousentaku = Random.Range(1, 101);



        Koudousentakukeisan = false;


        // あらかじめ各優先度を50、75、85、100のように記述。


        // あらかじめ各優先度が最大までいくと100になるように設定。例えばKinnkyoriyuusen = 50、Enkyoriyuusen = 75、Mawarikomikinnsetuyuusen = 85、Mawarikomikinsetuyuusen = 100のようにステータスで設定。


        if (charadata.Kinnkyoriyuusen >= koudousentaku)
        {
            //条件に当てはまった場合行動パターンと攻撃方法を指定し、KoudousentakukeisanをONにすることで他の行動パターンで上書きされない。
            anim.SetInteger("Koudou", 1);
            anim.SetInteger("Kougeki", 1);
            Koudousentakukeisan = true;


        }
        if (charadata.Enkyoriyuusen >= koudousentaku)
        {
            if (Koudousentakukeisan == false)
            {
                anim.SetInteger("Koudou", 2);
                anim.SetInteger("Kougeki", 2);
                Koudousentakukeisan = true;

            }


        }
        if (charadata.Mawarikomikinnsetuyuusen >= koudousentaku)
        {
            if (Koudousentakukeisan == false)
            {

                anim.SetInteger("Koudou", 3);
                anim.SetInteger("Kougeki", 1);
                Koudousentakukeisan = true;
            }


        }
        if (charadata.Mawarikomikinsetuyuusen >= koudousentaku)
        {
            if (Koudousentakukeisan == false)
            {
                anim.SetInteger("Koudou", 4);
                anim.SetInteger("Kougeki", 2);
                Koudousentakukeisan = true;
            }


        }






        Koudoup = anim.GetInteger("Koudou");




        if (Koudoup == 1)
        {

            // 今のPlayerの位置に加算する乱数を決める。
            float RandamX = Random.Range(-1, 1);
            float RandamZ = Random.Range(-1, 1);

            // Playerの位置に乱数を加算し次の位置を決める。
            NextPosition.x = PlayerPosition.x + RandamX;
            NextPosition.z = PlayerPosition.z + RandamZ;

            //次の位置に向かって移動する
            myAgent.SetDestination(NextPosition);
            //1回行動処理が終了したのでkoudouに1を代入。
            koudou = 1;


        }

        if (Koudoup == 2)
        {

            //LongAttackRangeの値に乱数を加算しAttackkyoriに代入。
            float Attackkyori = charadata.LongAttackRange;
            float Randamkyori = Random.Range(-1f, 0f);
            Attackkyori = Attackkyori + Randamkyori;


            //今回はY座標は考えずZ座標で距離をはかるので、PlayerPositionとThisPositionのY座標に0を代入。

            PlayerPosition = targetPlayer.transform.position;
            ThisPosition = this.transform.position;
            PlayerPosition.y = 0;
            ThisPosition.y = 0;


            //座標の差を求める。これがPlayerPositionからThisPositionへのベクトルとなる。
            Vector3 kyorimukikeisan = ThisPosition - PlayerPosition;

            //ワールド座標の正面ベクトルとkyorimukikeisanの間の角度を求める。
            var angle = Vector3.Angle(Vector3.forward, kyorimukikeisan);

            //ワールド座標の左方向のベクトルとkyorimukikeisanの間の角度を求める(計算用)。
            var angle2 = Vector3.Angle(Vector3.left, kyorimukikeisan);

            //angle2が90度以下なら360度からangleを引いた値をangleとする。

            if (90 > angle2)
            {
                angle = 360 - angle;
            }

            //ランダムに角度を変化させ、目標位置がランダムで変わるようにする。
            float Randamkakudo = Random.Range(-5f, 5f);
            angle = angle + Randamkakudo;

            //ランダムに角度を変えると360度を超える可能性がある。その場合は360度を引いて調整する。
            if (angle > 360)
            {
                angle = angle - 360;
            }

            //オイラー角のangleの角度とZ軸の距離であるAttackkyoriから目標位置を決める。
            NextPosition = Quaternion.Euler(0, angle, 0) * new Vector3(0, 0, Attackkyori);



            //決めた目標位置は原点(0,0,0)の位置が基準となってしまっている。そこにPlayerの位置座標を加算することで、Playerから敵までのベクトル上のAttackkyoriだけ離れた位置を目標位置とすることができる。
            NextPosition = NextPosition + PlayerPosition;

            //決めた目標位置をNavMeshで指定
            myAgent.SetDestination(NextPosition);
            koudou = 1;

        }



        if (Koudoup == 3)
        {
            //(Koudoup == 2の時と記述は基本的に同じ。角度をランダムに+90度または-90度しShortAttackRangの距離で目標位置を指定。

            float Attackkyori = charadata.ShortAttackRange;

            float Randamkyori = Random.Range(-1f, 1f);

            Attackkyori = Attackkyori + Randamkyori;


            PlayerPosition = targetPlayer.transform.position;
            ThisPosition = this.transform.position;
            PlayerPosition.y = 0;
            ThisPosition.y = 0;



            Vector3 kyorimukikeisan = ThisPosition - PlayerPosition;

            var angle = Vector3.Angle(Vector3.forward, kyorimukikeisan);


            var angle2 = Vector3.Angle(Vector3.left, kyorimukikeisan);


            if (90 > angle2)
            {
                angle = 360 - angle;
            }

            float Randamkakudo = Random.Range(-5f, 5f);
            angle = angle + Randamkakudo;


            int PlusMinus = Random.Range(1, 3);
            if (PlusMinus == 2)
            {
                angle += 90;
            }
            else
            {
                angle -= 90;
            }


            if (angle > 360)
            {
                angle = angle - 360;
            }



            NextPosition = Quaternion.Euler(0, angle, 0) * new Vector3(0, 0, Attackkyori);




            NextPosition = NextPosition + PlayerPosition;

            //次の位置に向かって移動する
            myAgent.SetDestination(NextPosition);
            koudou = 1;

        }




        if (Koudoup == 4)
        {
            //(Koudoup == 2の時と記述は基本的に同じ。角度をランダムに+90度または-90度しLongAttackRangeの距離で目標位置を指定。

            float Attackkyori = charadata.LongAttackRange;

            float Randamkyori = Random.Range(-1f, 0f);

            Attackkyori = Attackkyori + Randamkyori;


            PlayerPosition = targetPlayer.transform.position;
            ThisPosition = this.transform.position;
            PlayerPosition.y = 0;
            ThisPosition.y = 0;



            Vector3 kyorimukikeisan = ThisPosition - PlayerPosition;

            var angle = Vector3.Angle(Vector3.forward, kyorimukikeisan);


            var angle2 = Vector3.Angle(Vector3.left, kyorimukikeisan);


            if (90 > angle2)
            {
                angle = 360 - angle;
            }

            float Randamkakudo = Random.Range(-5f, 5f);
            angle = angle + Randamkakudo;


            int PlusMinus = Random.Range(1, 3);
            if (PlusMinus == 2)
            {
                angle += 90;
            }
            else
            {
                angle -= 90;
            }


            if (angle > 360)
            {
                angle = angle-360;
            }



            NextPosition = Quaternion.Euler(0, angle, 0) * new Vector3(0, 0, Attackkyori);




            NextPosition = NextPosition + PlayerPosition;

            //次の位置に向かって移動する
            myAgent.SetDestination(NextPosition);
            koudou = 1;

        }


        koudou = 1;


    }

    IEnumerator Enemytime()
    {
        //Idoukankakuの時間だけ待機。
        yield return new WaitForSeconds(idouzikan);
        //待機後にkoudouを0にすることで再び徘徊処理が行えるようになる。
        koudou = 0;
    }


}

 以上のように記述すれば4つの行動パターンを使い分けてくる敵AIを作ることができる。




 続きの記事は以下。
弾を撃つ
Unityのゲーム制作まとめへ戻る

page top