Unity人工智能AI编程知识


我们在分散不发生的情况下才产生聚集,一样地获取周围个体,但是我们不是计算出聚集合力什么的,而是计算出周围个体的中心点,中心点计算方法:周围个体的位置之和/周围个体数量
然后,中心点-当前个体位置=当前个体位置 朝向 中心点 的向量,然后这个向量就是我们的聚集力
如果对聚集力的大小有点不满意的话,推荐:将聚集力的大小设置为当前速度的大小


经过以上说明,我们得到了三个力:分散合力、队列趋向力、聚集力,这三个力都可以分别设置一个权重,来调整它们的影响,加权重的意思就是给力乘以一个值
然后,我们将这三个力相加得到了一个 最终合力,我们是用这个最终合力来影响当前物体的
根据牛顿第二定律:F=ma  , 故  a= F/m  (F是最终合力,a是加速度(m/s^2) m是质量(kg) )
所以,在Update里面我们可以通过这个公式计算出 最终合力对物体产生的加速度a
当前速度+=a*Time.deltaTime 这样处理加速度对当前速度的影响,当前速度是一个三维向量
我们通过使用Quaternion.Slerp来逐渐旋转当前个体的朝向,面向当前速度的朝向
移动的话可以用Translate 直接朝向当前个体的前方forward来移动,速度大小为当前速度大小


FSM有限状态机:

个人理解:FSM就是一个控制状态切换的集中处理器,用一个FSMSystem管理着所有FSMState,每个FSMState(状态)都有它自身对应的转换事件(可有多个)(Transition(枚举))。
FSM根据传递进来的Transition信息,将当前状态转换为其它状态,这个转换成功条件必须是:当前状态中存在该转换事件Transition,并且转换事件对应的状态ID存在于FSM管理器中,这样才会发生转换。
每次转换时,都会触发(当前状态离开事件)和(转换后的当前状态进入事件),每个状态都有进入事件和离开事件。
注意:当前状态必须存在转换条件,并且该转换条件对应的状态必须存在于FSM管理器中!
例如:StartState、RunState、EndState 三个状态存在于FSM管理器
StartState中注册了Transition.GoToRunState转换条件,该转换条件对应的State是RunState,由于我们的状态是通过字典(键值对形式)保存在FSM状态机中的,所以我们还要有一个枚举StateID来标识每种状态,而转换条件对应的就是StateID了,那么上面的RunState的StateID属性为StateID.Run。这样StartState就存在了一个转换条件,这个转换条件也有了对应的StateID,我们把这个放入字典保存起来,key是Transition,value是StateID。
而在游戏一开始默认是StartState,那么我们就应该要有一个初始化状态机的当前状态的方法,将当前状态设置为StartState,ID为StateID.Start, 并且调用StartState的进入事件,我们状态必须要重写进入事件和离开事件,因为使得每种状态的不同就是它们的作用,不然FSM将毫无意义。
在我们给FSM状态机一个转换消息时,例如:Transition.GoToRunState,那么FSM会这样做:
1.从当前状态(StartState)中,寻找该转换条件GoToRunState对应的状态ID即StateID.Run
2.拿到StateID后,我们在FSM中保存的状态字典内 通过StateID寻找出对应的状态,即RunState
3.找到后,先调用 当前状态的离开事件(当前状态是StartState),然后将当前状态转为RunState,ID也要变,然后调用当前状态的进入事件(当前状态是RunState)。

补充:
Act事件,它是每个状态必须重写的事件,会在FSM状态机的Update中调用,FSM状态机只会调用当前状态的Act事件,Act事件相当于状态运行中的各种行为。
Reason事件,它是每个状态必须重写的事件,同样会在FSM状态机的Update调用,与Act的行为区别是它集中处理了各种转换事件的发生,就是说在什么情况下会触发什么转换条件,从而让FSM状态机自己去管理自己的状态转变。
但是,当FSM状态机不继承于Monobeviour时,Act事件和Reason事件也可能并非由状态机自身去调用,而是由创建状态机的那个脚本的Update去调用状态机的Update方法。注意,状态机不继承于MonoBehaviour的时候是这样用的,当状态机继承于Monobehaviour就不需要创建状态机自身了。
至于状态机继不继承Monobehaviour,可以自己看着办,个人认为继承于Monobehaviour不太好。


感知系统:

        视觉:

        1、全方位无死角视觉,实现原理:利用Vector3.Distance()判断距离是否在视觉范围内。(不常用)

        2、半圆弧视觉:利用Vector3.Distance()判断距离是否在视觉范围内,并且用Vectoe3.Angle(机器人前方,从机器人指向玩家的向量)得到夹角(<90°),判断这个夹角是否在视觉角度/2内,若在视觉角度/2内,则认为是看到了,否则反之。半圆弧视觉和全方位无死角视觉的区别就在于多了一个Angle判断。这个视觉看不到看人 还会受到障碍物影响,在确认玩家在视觉范围内时,还要发射一条射线判断是否有障碍物挡着,若射线检测到的是玩家,则表明玩家在视野内,若碰到的是障碍物则不会看到玩家,也就不会触发看到玩家后的操作了。

        听觉:

        1、玩家跑动Bool变量,当玩家跑动时,为true,否则为false

        2、听觉最大距离:利用Unity寻路系统Nav,将2个点之间的最短距离计算出,然后判断这个最短距离是否 小于 听觉最大距离,若小于,则去判断玩家跑动Bool变量是否为true,若为true就表示听到了。这2点的意思是 机器人位置和玩家位置,因为我们的听觉是会受到障碍物干扰的,所以在计算2点距离的时候就不能使用简单的Vector3.Distance了,而是使用导航系统Nav的计算方法,或者根据你自己的寻路系统方法来计算2点的最短路径。

        后期说明,如何利用Nav导航系统计算最短路径问题TODO。

    

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class NavPath : MonoBehaviour {
    
    private NavMeshAgent agent;
    public Transform end;
    private void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        DrawPath();
    }
 
 
    private void DrawPath()
    {
        float smallestLength=0;
        List<Vector3> path = GetPath(end.position);
        Vector3[] pathArray = path.ToArray();
        for(int i=0;i<pathArray.Length-1;i++)
        {            
            GameObject go= GameObject.CreatePrimitive(PrimitiveType.Sphere);
            go.transform.position = pathArray[i];
            smallestLength += Vector3.Distance(pathArray[i], pathArray[i + 1]);
        }
        Debug.Log("路径长度:" + smallestLength);
    }
 
    private List<Vector3> GetPath(Vector3 endPoint)
    {
        //agent.SetDestination(endPoint);          
        NavMeshPath navMeshPath = new NavMeshPath();
        agent.CalculatePath(endPoint, navMeshPath);
        List<Vector3> path = new List<Vector3>();
        for(int i=0;i<navMeshPath.corners.Length;i++)
        {
            path.Add(navMeshPath.corners[i]);
            Debug.Log(i+1+"号  "+navMeshPath.corners[i]);
        }
        path.Insert(0, transform.position);
        path.Add(endPoint);
        return path;
    }
}

A*寻路:
           1、制作虚拟地图(美工管的),虚拟地图一般是用(一个类class或者结构体struct)去描述地图上一个点的信息,例如:

一个点需要有 坐标x,y ,是否为障碍物bool标志位,父节点,F,G,H,(基本都要这些)   

            2、讲解A*寻路的一个方程式:F=G+H

            简单地说明一下,F越小越好,代表着寻找出的路径会比较短,而不是最短。其中,G是确定的,H是预判的。

            该结点G的计算方法= 父节点G值 + 父节点到达该结点的距离(直接用Vector3.Distance计算出)

            H值的计算方法可以有多种:例如:第一种是直接 是 该结点与终点的X之差的绝对值与Y之差的绝对值 之和。

                                                                第二种是 直接Vector3.Distance计算出 该结点与终点的距离。

                                                                其他的方法也有 ,自己想到什么是什么,这个会影响到最终寻找出的路径。

            这里,因为H值的计算方法是人为设定的,可能靠谱可能不靠谱,故F值也不一定是最完美的,所以A*算法计算出的路径也不一定是最短的,而只能说是比较短的。

            3、A*算法需要的5个东西,上面已经说了1个,虚拟地图map[,] 我们通过把2D/3D的物体虚拟化为一张地图,这张地图的每个点都是一个信息体,即Point,里面记录着一些信息,初始化虚拟地图的时候 我们只会将x,y值和障碍物bool初始化。

            开启列表、闭合列表

            开启列表存放了还未处理的,但是已经确定父节点的结点。

            闭合列表存放了已经经过处理了的结点。

            当一个结点被处理完了,就会进入闭合结点。

            步骤:1、将起点放入开启列表。

                      2、若开启列表不为空,则从开启列表寻找一个结点,它的F值是最小的,开始处理该结点,我们称之为当前结点

                      3、判断终点是否已经在开启列表中了,若在开启列表中了,则表示找到寻路结束了,跳出循环。

                      3、获取 在 2 找出的拥有最小值F的结点 的 周围结点。(必须是获取能到达的结点,过滤掉越界的,障碍物的)

                      4、遍历3获取到的周围结点,

                     4.1 若发现遍历到的结点 已存在于开启列表中,也就是已经有(旧)父节点,计算出该结点新的G值,计算方法:当前结点的G值+当前结点到该结点的距离=新的G值, 用该新的G值对比 原本的G值,若新的G值更小,那么更新该结点的父节点为当前结点,G值为新的G值(更小),F=G+H(变小了)。 若新的G值比原本的要大,或者等于,就不作处理了。

                     4.2 若发现遍历到的结点 不存在于开启列表,我们就要计算该结点的信息,父节点=当前结点,G=当前结点的G值+当前结点到该结点的距离,H根据自己的公式计算,F=G+H,并且加入开启列表中。

                    5、回到2 继续执行

    下面是我啰嗦的话,不明白上面一些事情的话请看看。

    解释一下为什么终点存在于开列表中就代表寻路结束了,假设我们遍历到了终点之前的一个结点,那么这个结点肯定是在开启列表中F值最小的那个,对吧。还记得我们是怎么找到下一个要处理的结点的吗?是在开启列表中找出F值最小的那个结点!所以理所当然地 终点之前的一个结点是最好的,那么在该结点下 直接遍历周围结点,肯定会找到终点的,那么这样终点就会进入开启列表,终点的父节点和F、G、H值已经计算出来了,并且在开启列表中绝对是最小的,那么理所当然下一个处理的结点就是终点了,但是终点我们是不需要处理的,我们不需要继续处理了,所以循环结束,寻路结束。

                     这样,我们可以通过parent来获得一条路径,就是从终点不断地找parent,直至空为止。

                

上面,说了那么多,我说一句,寻路系统一般不用我们纯手写的,这样会不太方便,我们开发中一般会直接使用
--------------------- 

原文:https://blog.csdn.net/qq_39574690/article/details/80958838 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值