Unity AI逻辑笔记

用动画状态机当AI状态机

写ai逻辑基本上都需要状态机。因为懒得手搓状态机,所以选择直接用动画状态机当逻辑状态机用。

AI的基本行为

  1. 不停检测视野范围内是否有敌人;
  2. 没发现敌人时,静止、沿设定路线巡逻或随意走动;
  3. 发现敌人后,进入警戒状态,并进入攻击状态,转向敌人,攻击;
  4. 丢失目标后,保持警戒一段时间,然后进入安全状态;
  5. 丢失目标后可以选择是否追击,追击则移动到;
  6. 受攻击后会进入警戒状态;
  7. 警戒状态随意走动,指定一个附近目的地,朝向它,前进,同时不停检测是否已经足够接近,接近了就停止,再指定目的地,循环;
  8. 警戒状态,环顾四周;
  9. 攻击状态朝向目标,开启瞄准约束,开枪一段时间,停火一段时间,然后检查没子弹就换弹,如此循环;

AI程序的特点是在一个检测状态的大循环之下,各状态有攻击、环顾、走动等小循环。

架构设计

因为敌人的根节点已经有一个animator控制动画,只能增加一个子对象AI,给它加一个animator指向逻辑“动画”状态机。还有一个脚本,用来放一些检测函数和动画事件函数。

状态图设计

所有状态都不停执行检测敌人的方法。

动画剪辑设置

动画剪辑添加一个无关紧要的属性(如Scale)来卡时间。重要的是在特定的时间执行动画事件。如在Safe一段时间后开始巡逻:

public void StartPatrolling(){
        enemyController.SetBool(patrolling,true);
    }

 攻击状态开枪几秒,停歇几秒,然后检查是否该换弹:

状态机行为脚本

主要用于在特定状态才每帧执行的代码。

在状态机行为脚本里满足某些条件时执行animator.SetXXX()改变动画参数,动画参数改变又引起状态转换,执行新的状态机行为脚本,可以达到状态机“自驱动”的效果。

public class EnemyAlert : StateMachineBehaviour
{
    Character1 myCharacter;
    MyNPCAI myAI;
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex){
        myCharacter=animator.transform.parent.GetComponent<Character1>();
        myCharacter.PutAwayGun();
        myAI=animator.GetComponent<MyNPCAI>();
        myCharacter.PlayRandomClip(myAI.findAudio);
        myCharacter.UseRifle();
        myAI.StopLookingAround();
    }
    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex){
        if(myAI.DetectEnemy()){
            animator.SetBool(MyNPCAI.foundEnemyPara,true);
        }
    }
}

但是每个状态机行为脚本进入时都要获取一遍组件。这很恶心。 

慢慢地我发现用协程好像也能替代状态机行为的功能,只需要状态机+动画事件+协程就

状态里的小状态

  1. Alert交替进行环顾四周、随机移动。环顾四周是一个过程,可以指定目标方向或旋转时间,移动也是类似的过程;
  2. 攻击要先转向目标,然后交替进行射击和停火,也是个子状态机;
  3. 其他状态要想更细,都会变成子状态机;

要把状态分成小状态吗?状态会多一点,多出来的转换就更多了。或者可以用协程做小状态?怎么用协程做个小状态机?可以在行为IEnumerator里开启下一个行为的协程,会不会有什么问题?

AI检测敌人

一开始我想让AI能发现前方扇形区域的敌人,没有扇形碰撞体,用代码写了一个。后来为了简单,直接在人物前方用球形范围检测了。

除了范围检测,还要用Physics.LineCast()检测中间有没有障碍物。 

bool DetectEnemy()
        {
            Vector3 center = transform.position + transform.forward * detectRadius;
            Collider[] colliders = Physics.OverlapSphere(center, detectRadius, MyGameManager.Instance.aiDetectLayers);
            for (int i = 0; i < colliders.Length; i++)
            {
                CharacterBase character;
                if (colliders[i].TryGetComponent(out character) && !CheckBarrier(character) && character.HP > 0)
                {
                    if (character.characterSide != myCharacter.characterSide)
                    {
                        target = character;
                        targetLastPosition=character.transform.position;
                        return true;
                    }
                }
            }
            return false;
        }

AI瞄准敌人

给AI的枪绑定对象加了AimConstraint使枪对准敌人,省去了写复杂的瞄准算法。

为了使AI不每枪必中,给AimVector加了随机误差。

void AddAimError(){
        gunAim.aimVector=Vector3.forward+
        UnityEngine.Random.Range(-aimErrorRange,aimErrorRange)*Vector3.up+
        UnityEngine.Random.Range(-aimErrorRange,aimErrorRange)*Vector3.right;
    }

 这容易造成AI身体没指向目标时就打开瞄准约束,造成很不自然的样子。

AI攻击流程

进入攻击状态后转向目标,面向目标后交替开火和停火。攻击过程中要一直朝向敌人,没有朝向敌人不能开火。

public void TurnTo(Vector3 target)
        {
            Vector3 aimVector = target - myCharacter.transform.position;
            aimVector = new Vector3(aimVector.x, 0, aimVector.z);
            if (aimVector == Vector3.zero)
            {
                return;
            }
            Quaternion targetRotation = Quaternion.LookRotation(aimVector);
        myCharacter.transform.rotation=Quaternion.Lerp(myCharacter.transform.rotation,
            targetRotation,.2f);
        }

AI环顾四周

先得到一个随机方向A,然后一帧帧转过去,和方向A的偏差小于一个值后再得到随机方向,循环。

效果演示 

Unity简单敌人逻辑演示:巡逻、发现、攻击、追击_演示

总结

  1. 使用状态机写AI,一开始会想分成安全、警戒、攻击等状态,但是很快会发现这样粗略的状态划分不够,大状态下必然有次级状态规定人物的行为;
  2. 人物的行为不是执行一个函数就完成,都需要持续一段时间,有的持续一段时间结束,有的持续中还要检测结束条件;

NavMesh相关

给使用大型Terrain的场景生成NavMesh

收集对象不要选All Game Objects,否则容易卡死。选Current Object Hierarchy。好像如果层级里包含巨大的水面就会卡死,可以通过Layer把水面排除。

使用动画根运动的人物配合navMesh实现追击

navMesh.SetDestination()可以让ai寻路移动,但是ai使用动画根运动怎么按寻路移动?

navMesh.SetDestination()的移动本质上是把位置不断设置为navMeshAgent.nextPosition。就把NavMeshAgent.nextPosition作为移动的目的地。

AI.NavMeshAgent-nextPosition - Unity 脚本 API

但是如果让人物转向navMeshAgent.nextPosition,可能出现和当前位置重叠而提示

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值