Unity游戏开发 怪物巡逻AI

今天实现的内容是怪物AI,看了一些网上的AI,不是特别符合我的需求,于是就自己研究了一种AI,大致和魔兽类的RPG游戏效果差不多。

AI效果如下:

1. 将怪物分为如下几个状态:

待机状态(该状态内有3种行为:原地呼吸、原地观察、和游走,可通过权重配置待机状态下三种行为发生的比例);

警戒状态(怪物向玩家发出警告,并持续盯着玩家);

追击状态(怪物跑向玩家)

返回状态(跑回出生位置,该状态下不再理会玩家)

2. 可以为怪物配置各项状态的数值

游走半径,待机状态下,怪物游走时不会超出这个范围,根据出生点计算

警戒半径,当玩家与怪物之间距离小于警戒半径时进入警戒状态,根据怪物实时位置计算

自卫半径,当玩家与怪物之间距离小于自卫半径时进入追击状态

追击半径,怪物追击玩家时,自身不会超出这个范围,超出后跑回出生点,可以理解为最大活动范围

攻击距离,当玩家与怪物之间的距离小于攻击距离时触发战斗(我这边是进入之前做好的回合制战斗场景)

3. 各项数值设定的关系如下

比较合理的关系是:追击半径>警戒半径>自卫半径>攻击距离,游走半径只需要小于追击半径即可

1)自卫半径不建议大于警戒半径,否则就无法触发警戒状态,直接开始追击了

2)攻击距离不建议大于自卫半径,否则就无法触发追击状态,直接开始战斗了

3)游走半径不建议大于追击半径,否则怪物可能刚刚开始追击就返回出生点

我这边自己测试时,设置的属性如下:

接下来就是脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MonsterWander : MonoBehaviour {

    private GameObject playerUnit;          //获取玩家单位
    private Animator thisAnimator;          //自身动画组件
    private Vector3 initialPosition;            //初始位置

    public float wanderRadius;          //游走半径,移动状态下,如果超出游走半径会返回出生位置
    public float alertRadius;         //警戒半径,玩家进入后怪物会发出警告,并一直面朝玩家
    public float defendRadius;          //自卫半径,玩家进入后怪物会追击玩家,当距离<攻击距离则会发动攻击(或者触发战斗)
    public float chaseRadius;            //追击半径,当怪物超出追击半径后会放弃追击,返回追击起始位置

    public float attackRange;            //攻击距离
    public float walkSpeed;          //移动速度
    public float runSpeed;          //跑动速度
    public float turnSpeed;         //转身速度,建议0.1

    private enum MonsterState
    {
        STAND,      //原地呼吸
        CHECK,       //原地观察
        WALK,       //移动
        WARN,       //盯着玩家
        CHASE,      //追击玩家
        RETURN      //超出追击范围后返回
    }
    private MonsterState currentState = MonsterState.STAND;          //默认状态为原地呼吸

    public float[] actionWeight = { 3000, 3000, 4000 };         //设置待机时各种动作的权重,顺序依次为呼吸、观察、移动
    public float actRestTme;            //更换待机指令的间隔时间
    private float lastActTime;          //最近一次指令时间

    private float diatanceToPlayer;         //怪物与玩家的距离
    private float diatanceToInitial;         //怪物与初始位置的距离
    private Quaternion targetRotation;         //怪物的目标朝向

    private bool is_Warned = false;
    private bool is_Running = false;

    void Start () {
        playerUnit = GameObject.FindGameObjectWithTag("Player");
        thisAnimator = GetComponent<Animator>();

        //保存初始位置信息
        initialPosition = gameObject.GetComponent<Transform>().position;

        //检查并修正怪物设置
        //1. 自卫半径不大于警戒半径,否则就无法触发警戒状态,直接开始追击了
        defendRadius = Mathf.Min(alertRadius, defendRadius);
        //2. 攻击距离不大于自卫半径,否则就无法触发追击状态,直接开始战斗了
        attackRange = Mathf.Min(defendRadius, attackRange);
        //3. 游走半径不大于追击半径,否则怪物可能刚刚开始追击就返回出生点
        wanderRadius = Mathf.Min(chaseRadius, wanderRadius);

        //随机一个待机动作
        RandomAction();
    }

    /// <summary>
    /// 根据权重随机待机指令
    /// </summary>
    void RandomAction()
    {
        //更新行动时间
        lastActTime = Time.time;
        //根据权重随机
        float number = Random.Range(0, actionWeight[0] + actionWeight[1] + actionWeight[2]);
        if (number <= actionWeight[0])
        {
            currentState = MonsterState.STAND;
            thisAnimator.SetTrigger("Stand");
        }
        else if (actionWeight[0] < number && number <= actionWeight[0] + actionWeight[1])
        {
            currentState = MonsterState.CHECK;
            thisAnimator.SetTrigger("Check");
        }
        if (actionWeight[0] + actionWeight[1] < number && number <= actionWeight[0] + actionWeight[1] + actionWeight[2])
        {
            currentState = MonsterState.WALK;
            //随机一个朝向
            targetRotation = Quaternion.Euler(0, Random.Range(1, 5) * 90, 0);
            thisAnimator.SetTrigger("Walk");
        }
    }

    void Update () {
        switch (currentState)
        {
            //待机状态,等待actRestTme后重新随机指令
            case MonsterState.STAND:
                if (Time.time - lastActTime > actRestTme)
                { 
                    RandomAction();         //随机切换指令
                }
                //该状态下的检测指令
                EnemyDistanceCheck();
                break;

            //待机状态,由于观察动画时间较长,并希望动画完整播放,故等待时间是根据一个完整动画的播放长度,而不是指令间隔时间
            case MonsterState.CHECK:
                if (Time.time - lastActTime > thisAnimator.GetCurrentAnimatorStateInfo(0).length)
                {
                    RandomAction();         //随机切换指令
                }
                //该状态下的检测指令
                EnemyDistanceCheck();
                break;

            //游走,根据状态随机时生成的目标位置修改朝向,并向前移动
            case MonsterState.WALK:
                transform.Translate(Vector3.forward * Time.deltaTime * walkSpeed);
                transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed);
                
                if (Time.time - lastActTime > actRestTme)
                {
                    RandomAction();         //随机切换指令
                }
                //该状态下的检测指令
                WanderRadiusCheck();
                break;

            //警戒状态,播放一次警告动画和声音,并持续朝向玩家位置
            case MonsterState.WARN:
                if (!is_Warned)
                {
                    thisAnimator.SetTrigger("Warn");
                    gameObject.GetComponent<AudioSource>().Play();
                    is_Warned = true;
                }
                //持续朝向玩家位置
                targetRotation = Quaternion.LookRotation(playerUnit.transform.position - transform.position, Vector3.up);
                transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed);
                //该状态下的检测指令
                WarningCheck();
                break;

            //追击状态,朝着玩家跑去
            case MonsterState.CHASE:
                if (!is_Running)
                {
                    thisAnimator.SetTrigger("Run");
                    is_Running = true;
                }
                transform.Translate(Vector3.forward * Time.deltaTime * runSpeed);
                //朝向玩家位置
                targetRotation = Quaternion.LookRotation(playerUnit.transform.position - transform.position, Vector3.up);
                transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed);
                //该状态下的检测指令
                ChaseRadiusCheck();
                break;

            //返回状态,超出追击范围后返回出生位置
            case MonsterState.RETURN:
                //朝向初始位置移动
                targetRotation = Quaternion.LookRotation(initialPosition - transform.position, Vector3.up);
                transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed);
                transform.Translate(Vector3.forward * Time.deltaTime * runSpeed);
                //该状态下的检测指令
                ReturnCheck();
                break;
        }
	}

    /// <summary>
    /// 原地呼吸、观察状态的检测
    /// </summary>
    void EnemyDistanceCheck()
    {
        diatanceToPlayer = Vector3.Distance(playerUnit.transform.position, transform.position);
        if (diatanceToPlayer < attackRange)
        {
            SceneManager.LoadScene("Battle");
        }
        else if (diatanceToPlayer < defendRadius)
        {
            currentState = MonsterState.CHASE;
        }
        else if (diatanceToPlayer < alertRadius)
        {
            currentState = MonsterState.WARN;
        }
    }

    /// <summary>
    /// 警告状态下的检测,用于启动追击及取消警戒状态
    /// </summary>
    void WarningCheck()
    {
        diatanceToPlayer = Vector3.Distance(playerUnit.transform.position, transform.position);
        if (diatanceToPlayer < defendRadius)
        {
            is_Warned = false;
            currentState = MonsterState.CHASE;
        }

        if (diatanceToPlayer > alertRadius)
        {
            is_Warned = false;
            RandomAction();
        }
    }

    /// <summary>
    /// 游走状态检测,检测敌人距离及游走是否越界
    /// </summary>
    void WanderRadiusCheck()
    {
        diatanceToPlayer = Vector3.Distance(playerUnit.transform.position, transform.position);
        diatanceToInitial = Vector3.Distance(transform.position, initialPosition);

        if (diatanceToPlayer < attackRange)
        {
            SceneManager.LoadScene("Battle");
        }
        else if (diatanceToPlayer < defendRadius)
        {
            currentState = MonsterState.CHASE;
        }
        else if (diatanceToPlayer < alertRadius)
        {
            currentState = MonsterState.WARN;
        }

        if (diatanceToInitial > wanderRadius)
        {
            //朝向调整为初始方向
            targetRotation = Quaternion.LookRotation(initialPosition - transform.position, Vector3.up);
        }
    }

    /// <summary>
    /// 追击状态检测,检测敌人是否进入攻击范围以及是否离开警戒范围
    /// </summary>
    void ChaseRadiusCheck()
    {
        diatanceToPlayer = Vector3.Distance(playerUnit.transform.position, transform.position);
        diatanceToInitial = Vector3.Distance(transform.position, initialPosition);

        if (diatanceToPlayer < attackRange)
        {
            SceneManager.LoadScene("Battle");
        }
        //如果超出追击范围或者敌人的距离超出警戒距离就返回
        if (diatanceToInitial > chaseRadius || diatanceToPlayer > alertRadius)
        {
            currentState = MonsterState.RETURN;
        }
    }

    /// <summary>
    /// 超出追击半径,返回状态的检测,不再检测敌人距离
    /// </summary>
    void ReturnCheck()
    {
        diatanceToInitial = Vector3.Distance(transform.position, initialPosition);
        //如果已经接近初始位置,则随机一个待机状态
        if (diatanceToInitial < 0.5f)
        {
            is_Running = false;
            RandomAction();
        }
    }

}

以上脚本经过多次测试,运行结果均较为理想。

  • 84
    点赞
  • 410
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
要实现Unity敌人自动巡逻,可以按照以下步骤操作: 1.创建一个敌人游戏对象,并添加一个NavMeshAgent组件。NavMeshAgent组件可以让敌人在导航网格上移动。 2.在场景中创建一个空对象作为巡逻点,将其命名为“Waypoints”。将巡逻点放置在地图上,以便敌人可以沿着它们移动。可以在Waypoints对象上创建子对象,以便更好地组织巡逻点。 3.编写一个脚本来控制敌人巡逻行为。该脚本应该附加到敌人游戏对象上。在脚本中,可以使用NavMeshAgent组件来控制敌人的移动。 4.在脚本中定义一个数组来存储巡逻点。可以使用FindGameObjectsWithTag()函数来查找Waypoints对象,并获取其子对象作为巡逻点。 5.在脚本中定义一个变量来存储当前巡逻点的索引。在敌人接近一个巡逻点时,可以将索引增加1,以便敌人移动到下一个巡逻点。如果敌人已到达最后一个巡逻点,则应将索引重置为0,以便敌人开始从头再次巡逻。 6.在Update()函数中,可以检查敌人是否接近当前巡逻点。如果敌人已接近当前巡逻点,则应将索引增加1,并将敌人移动到下一个巡逻点。 7.在脚本中添加一些额外的逻辑,以便敌人可以在巡逻过程中检测并攻击玩家或其他敌人。 这些步骤可以帮助您实现Unity敌人的自动巡逻。需要注意的是,在实现过程中可能会遇到一些问题,需要根据具体情况进行调整和修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值