简单的巡逻AI

1、场景设置

场景中央为可供移动的主角(黑色箭头)和四处巡逻的AI(蓝色圆形),四周为黑色墙体。

墙体碰撞器为EdgeCollider2D

在这里插入图片描述

AI拥有一个子物体,子物体上挂一个Circle Collider2D,用于表示AI的听力范围

2、脚本编写

1、AI的脚本

首先,我们可以针对AI所拥有的状态,创建对应的枚举类型

public enum AvoidSceneState
{
    Idle,
    Track,
    AvoidWall
}

在这个简单巡逻场景中,AI需要三个状态:巡逻、追击、避墙

巡逻:

一种比较简单的巡逻实现方式,可以设置一个间隔时间,每经过一次间隔时间,生成一个随机数,随机数取值范围为[0,7]的整数,根据产生的随机数,来确定AI的移动方向(八方向)

private void Idleing(int num)
    {
        //if(info.collider.)
        lastState = AvoidSceneState.Idle;

        //八方向随机移动
        moveDir = new Vector2(Mathf.Cos((0 - num) * 45 * Mathf.Deg2Rad), Mathf.Sin((0 - num) * 45 * Mathf.Deg2Rad));
        this.transform.Translate(moveDir.normalized * Time.deltaTime * idleSpeed, Space.World);
    }

追击:

private void Tracking(GameObject player)
    {
        lastState = AvoidSceneState.Track;

        moveDir = (player.transform.position - this.transform.position);

        //有一定距离,则向主角移动
        if (moveDir.magnitude >= 0.5f)
        {
            
            this.transform.Translate(moveDir.normalized * Time.deltaTime * trackSpeed, Space.World);
        }
        
    }

避墙:

2d场景中,想要让AI在碰到墙前提前改变运动方向,可以使用叉乘。具体做法如下:

由AI当前位置加上碰撞器在运动方向上的矢量的一半(避免射线与AI碰撞器本身进行交互)作为起始位置,射出一条单位长度的射线矢量,每帧检测射线是否与墙壁有碰撞。当发生碰撞时,因为墙体采用的是线碰撞器,可以通过线碰撞器两个端点的减法,来计算墙体矢量。将射线与墙体矢量进行叉乘,计算出2d平面的z轴基矢量,再将射线与z轴基矢量进行叉乘,可计算出基于射线与墙体交点的一条与射线垂直的矢量,将矢量与运动方向矢量相加,获取新的运动方向。

在这里插入图片描述

如上图所示:蓝线为运动方向矢量(射线),黄线为墙体矢量,黑线为垂线矢量,红线为新的运动方向矢量。

private void RaysDetection(Vector2 moveDir)
    {
        //射线检测
        ray = new Ray2D(new Vector2(this.transform.position.x, this.transform.position.y) + moveDir.normalized * boxCollider2D.size.x * Mathf.Sqrt(2) / 2, moveDir.normalized);
        UnityEngine.Debug.DrawRay(ray.origin, ray.direction * 1, Color.blue);

        info = Physics2D.Raycast(ray.origin, ray.direction, 1f, mask);
        //UnityEngine.Debug.DrawRay(this.transform.position, moveDir, Color.red);
        if (info && info.collider.tag == "Wall")
        {
            //rotateTime = 0;
            canSrand = true;
            state = AvoidSceneState.AvoidWall;

            curMoveDir = moveDir;
            Vector3 dir = new Vector3(ray.direction.x, ray.direction.y, 0);
            Vector3 wall = new Vector3((info.collider.gameObject.GetComponent<EdgeCollider2D>().points[0] - info.collider.gameObject.GetComponent<EdgeCollider2D>().points[1]).x,
                (info.collider.gameObject.GetComponent<EdgeCollider2D>().points[0] - info.collider.gameObject.GetComponent<EdgeCollider2D>().points[1]).y, 0);
            //运动方向和墙叉乘出z轴
            Vector3 normal = Vector3.Cross(curMoveDir, wall);
            //Debug.Log(Vector3.Dot(curMoveDir, wall));
            if (Vector3.Dot(curMoveDir, wall) <= -0.01f)
            {
                //运动方向和z轴叉乘出运动方向关于交点的垂线矢量
                tangent = Vector3.Cross(dir, normal).normalized * 2;
            }
            if (Vector3.Dot(curMoveDir, wall) >= 0.01f)
            {
                tangent = Vector3.Cross(dir, normal).normalized * (-2);
            }
            if (Vector3.Dot(curMoveDir, wall) < 0.001f && Vector3.Dot(curMoveDir, wall) > -0.001f)
            {

                tangent = -2 * curMoveDir.normalized;
            }

            //Debug.Log(tangent + "tangent");
            UnityEngine.Debug.DrawRay(info.point, wall, Color.yellow);
            UnityEngine.Debug.DrawRay(info.point, tangent, Color.black);

            curMoveDir = new Vector2(tangent.x, tangent.y) + curMoveDir;

            UnityEngine.Debug.DrawRay(this.transform.position, curMoveDir, Color.red);
        }
    }
private void AvoidWalling()
    {
        lastState = AvoidSceneState.AvoidWall;

        moveDir = curMoveDir;
        
        this.transform.Translate(curMoveDir.normalized * Time.deltaTime * idleSpeed, Space.World);
    }

针对状态之间的切换

switch (state)
        {
            case AvoidSceneState.Idle:
                //恢复听力
                //this.transform.GetChild(0).GetComponent<CircleCollider2D>().enabled = true;
                text.text = "AI正常情况下会四处巡逻";

                idletimer += Time.deltaTime;
                if (idletimer > moveTime || canSrand)
                {
                    //八方向随机移动
                    randNum = Random.Range(0, 8);
                    idletimer = 0;
                    avoidtimer = 0;
                    canSrand = false;
                }

                Idleing(randNum);
                break;

            case AvoidSceneState.Track:
                text.text = "主角进入一定范围,AI会追击主角";
                idletimer = 0;
                avoidtimer = 0;
                Tracking(player);
                break;

            case AvoidSceneState.AvoidWall:
                text.text = "AI快要碰壁时,暂停追踪一段时间\n若主角再次进入范围,会被再次追击"; 
                text.text = text.text.Replace("\\n", "\n");
                //快要碰壁
                if (lastState == AvoidSceneState.Track)
                {
                    hearingTimer = 0;
                    this.transform.GetChild(0).GetComponent<CircleCollider2D>().enabled = false;
                }
                avoidtimer += Time.deltaTime;
                //碰壁改变方向后迅速回到Idle状态
                if (avoidtimer > 0.5)
                {

                    state = AvoidSceneState.Idle;
                    avoidtimer = 0;
                    idletimer = 0;
                }
                //Debug.Log("avoidWall");
                AvoidWalling();
                break;
        }

3、效果展示

在这里插入图片描述

项目文件

链接:https://pan.baidu.com/s/1vqZiW7QBd8Sa1TlcdNlcEw
提取码:ojuy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值