Unity2D:敌人AI行动(Idle、向玩家走、A*寻路、射线检测)

/// <summary>
/// 逻辑分析
///     每隔20帧刷新玩家位置
///     若可以看见玩家,启动一段时间 ActiveTime=15s
///     启动时每隔30帧检测是否可以看见玩家
///     如果看得见
///         向玩家移动
///         刷新 ActiveTime
///     否则触发自动寻路
///     每隔20帧检测是否卡住
///         卡住时随机移动
///     非启动状态下
///         若看见玩家则启动
///         否则Idle
/// </summary>

由于敌人自己有一个Collider2D所以需要layer排除Enemy层。

    /// <summary>
    /// 检测是否可以感知 需要2维Collider
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <param name="maxDis"></param>
    /// <param name="layer"></param>
    /// <returns></returns>
    public static bool PerceiveObject(Vector2 a,GameObject b,float maxDis, int layer)
    {
        Ray2D ray = new Ray2D(a, ((Vector2)b.transform.position - a).normalized);
        //Debug.DrawRay(ray.origin, ray.direction * 100, Color.yellow);
        //RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, maxDis, layer);
        if (hit == false)
        {
            return false;
        }
        //Debug.Log(hit.collider.gameObject);
        return hit.collider.gameObject == b;
    }
/* 
 *  Author : Jk_Chen
 */

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;

/// <summary>
/// 逻辑分析
///     每隔20帧刷新玩家位置
///     若可以看见玩家,启动一段时间 ActiveTime=15s
///     启动时每隔30帧检测是否可以看见玩家
///     如果看得见
///         向玩家移动
///         刷新 ActiveTime
///     否则触发自动寻路
///     每隔20帧检测是否卡住
///         卡住时随机移动
///     非启动状态下
///         若看见玩家则启动
///         否则Idle
/// </summary>
public class EnemyAI : MonoBehaviour
{
    Helper H;
    const float _ActiveTime = 15;
    float ActiveTime;// 激活时间
    float MaxSpeed = 2; // 最大速度
    float SpeedLimit = 0.01f; // 更新动画器的速度阈值

    public bool useAI; // 是否AI行动
    Path path; // 路径
    int nextP = 0; // 当前要去的点下标

    Vector2 target; // 每隔一段时间刷新玩家的位置
    bool canSee; //是否可以看到玩家
    float radius = 10; // 感知范围

    Seeker seeker;
    Rigidbody2D _rigidbody;
    /// <summary>
    /// 检测射线需要取出Enemy层
    /// </summary>
    int layer;

    void Start()
    {
        layer = ~(1 << LayerMask.NameToLayer("Enemy"));
        useAI = false;
        H = Helper.instance;
        _rigidbody = GetComponent<Rigidbody2D>();
        seeker = GetComponent<Seeker>();
    }

    void FixedUpdate()
    {
        if (Time.frameCount % 20 == 0)
            target = ObjectLibrary.player.transform.position;
        if (Time.frameCount % 30 == 0)
            canSee = Helper.PerceiveObject(transform.position, ObjectLibrary.player, radius, layer);
        if (canSee)
        {
            ActiveTime = _ActiveTime;
            // 结束Idle
            resTime = 0;
        }
        if (resTime > 0)
        {
            MoveRandom();
            EndAI("随机走");
        }
        if (ActiveTime > 0)
        {
            if (Time.frameCount % 20 == 0)
                prePos = transform.position;
            else if(canSee)
            {
                MoveTarget();
                EndAI("往角色走");
            }
            else
            {
                if (!useAI) StartAI();
                MoveAI();
            }
            CheckStatic();
            ActiveTime -= Time.deltaTime;
            if (ActiveTime <= 0)
            {
                EndAI("激活时间结束");
            }
        }
        else
        {
            if (resTime <= 0)
            {
                randomV = Helper.instance.RandomDirection();
                resTime = timeLen;
            }
        }
        if (ActiveTime > 0)
        {
            transform.Find("fight").gameObject.SetActive(true);
        }
        else
        {
            transform.Find("fight").gameObject.SetActive(false);
        }
    }

    /// <summary>
    /// 向目标出发
    /// </summary>
    void MoveTarget()
    {
        Vector2 vector = (target - (Vector2)transform.position).normalized;
        Vector2 delta = Time.deltaTime * vector * MaxSpeed;
        RefreshAnimation(delta);
        Vector2 pos = (Vector2)transform.position + delta;
        _rigidbody.MovePosition(pos);
    }

    Vector2 prePos = new Vector2(-1e9f, -1e9f);
    float resTime;
    Vector2 randomV;
    float timeLen = 1f;

    /// <summary>
    /// 向下一个点移动
    /// </summary>
    void MoveAI()
    {
        if (path == null || !useAI)
        {
            return;
        }
        // 获取向量
        Vector2 vector = (path.vectorPath[nextP] - transform.position);
        if (vector.magnitude > SpeedLimit)
        {
            Vector2 delta = Time.deltaTime * vector.normalized * MaxSpeed;
            Vector2 pos = (Vector2)transform.position + delta;
            RefreshAnimation(delta);
            _rigidbody.MovePosition(pos);
            //由于使用的是移动而不是施加力,所以可能出现超过的情况
            //第一种处理办法是修改移动向量的长度使得当好落在目标点
            //      但是这种办法会导致移动过程速度时快时慢,镜头移动不流畅
            // 第二种办法是循环检测下一个目标点是否在当前移动后,已经越过了
            while (delta.magnitude > Vector2.Distance(transform.position, path.vectorPath[nextP]))
            {
                nextP++;
                if (nextP == path.vectorPath.Count)
                {
                    EndAI("已到达终点");
                    break;
                }
            }
        }
        else
        {
            nextP++;
            if (nextP == path.vectorPath.Count)
            {
                EndAI("已到达终点");
            }
        }
    }

    /// <summary>
    /// 向随机生成的方向移动
    /// </summary>
    void MoveRandom()
    {
        Vector2 delta = Time.deltaTime * randomV * MaxSpeed;
        RefreshAnimation(delta);
        Vector2 pos = (Vector2)transform.position + delta;
        _rigidbody.MovePosition(pos);

        resTime -= Time.deltaTime;
    }

    /// <summary>
    /// 检测是否停止
    /// </summary>
    void CheckStatic()
    {
        if (Time.frameCount % 20 == 19 && Vector2.Distance(prePos, transform.position) < 0.01f)
        {
            EndAI("检测静止");
            randomV = Helper.instance.RandomDirection();
            resTime = timeLen;
        }
    }

    /// <summary>
    /// 开启AI模式
    /// </summary>
    /// <param name="pos"></param>
    public void StartAI()
    {
        path = null;
        useAI = true;
        SeekPath();
    }

    /// <summary>
    /// 结束AI
    /// </summary>
    public void EndAI(string info)
    {
        useAI = false;
        path = null;
        //Debug.Log("END AI: " + info);
    }
    public void EndAI()
    {
        useAI = false;
        path = null;
    }

    /// <summary>
    /// 向目标寻路
    /// </summary>
    /// <param name="pos"></param>
    void SeekPath()
    {
        if (useAI && path != null && !seeker.IsDone()) return;
        seeker.StartPath(transform.position, target, OnPathComplete);
    }

    /// <summary>
    /// 路径查找完成时的初始化
    /// </summary>
    /// <param name="p"></param>
    void OnPathComplete(Path p)
    {
        if (!p.error)
        {
            Debug.Log("Success Seek");
            path = p;
            nextP = 0;
        }
        else
        {
            EndAI("");
        }
    }

    /// <summary>
    /// 刷新动画显示
    /// </summary>
    /// <param name="vector"></param>
    void RefreshAnimation(Vector2 vector)
    {
        if (Mathf.Abs(vector.x) < 0.01) return;
        if (vector.x < 0)
        {
            transform.localScale = new Vector3(-1, 1, 1);
        }
        else
        {
            transform.localScale = new Vector3(1, 1, 1);
        }
    }
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值