天天看點

關于Unity中AI随機巡邏障礙物預判與快速運動時實體穿透的思考

如果不想用Unity的導航系統,很多時候就要解決如何預判前進路徑中的障礙物問題,之前也看過一些非常經典的尋路算法例如AStar尋路,雖然也可實作功能,但總感覺有些小題大做。尋路算法大多數都是為了得出最優解,但如果隻是用在一個區域内随機運動的遠端怪身上的話,根本就不需要用這麼複雜的算法。

關于Unity中AI随機巡邏障礙物預判與快速運動時實體穿透的思考

就比如上面這個簡單的遠端怪,它根本就不想接近玩家,它的運動方式就是在一定的距離内朝任意方向走一段,到達了就朝玩家來一發,然後就這麼反複,直到它被打死。(哎,這悲傷的命運)

然後問題就出現了,因為它就這麼呆萌呆萌地一直朝標明的下一個随機位置運動,那麼很可能在途中遭遇不可逾越的障礙物,假如這怪物能跳的話那還好說,碰到了障礙物就飛奔而起,來個360度空中轉體3周半,驚豔全場。可惜的是它并又沒有Get這個技能,是以你就會看到一個頂着障礙物幹瞪眼的家夥與障礙物進行着永無天日的持久抗争,于心不忍的你開始給它增加一些預判障礙物的方法:

1.最簡單最粗暴,能不能直接用時間來呢,一段時間内還沒有任何的距離運動,那說明遇到障礙物了,直接就洗腦重新随機下一個目标位置,如果随機的下一個位置又有障礙物,那就繼續,直到可以繼續運動為止。其實這裡更優的做法應該是進行學習,每次随機到障礙物位置後就記下來,以後周圍半徑1的範圍内都不再成為下一個随機點。

public override TaskStatus OnUpdate()
{
    if (timer > .2f)
    {
        if ((transform.position - posAtlastTimer).sqrMagnitude < .1f)
        {
            //記錄目前位置...
            //重新随機目标位置...
        }
        timer = 0f;
        posAtlastTimer = transform.position;
    }
}           

複制

需要一個計時器timer和記錄目标上一timer位置的變量posAtlastTimer。

2.考慮利用OnCollisionEnter(Collision collision)方法來檢測,判斷碰到的碰撞體的标簽,如果是障礙物,就記錄目前位置并重新随機。這種方式可以避免因為反複随機到障礙物位置而産生的卡頓問題。

public override void OnCollisionEnter(Collision collision)
{
    if (collision.transform.tag == "Collider")
    {
        //記錄目前位置...
        //重新随機目标位置...
    }
}           

複制

3.在每次随機運動前就進行射線檢測,發出一條從目前點到目标點方向的射線(也可以按照物體的上下左右邊緣發出多條),射線的長度即為目前位置到目标位置的距離,如果射線碰到了障礙物,那麼就可以提前得知該路徑是無效的。

private Vector3 RayCheckCollider(Vector3 tarPos)
{
    Vector3 offset = tarPos - transform.position;
    Ray ray = new Ray(transform.position, offset);
    RaycastHit info;
    if (Physics.Raycast(ray, out info, offset.magnitude))
    {
        if (info.collider.tag == "Collider")
        {
            //記錄目前位置...
            //重新随機目标位置...
            tarPos = RayCheckCollider(tarPos);//遞歸檢測
        }
    }
    Debug.DrawLine(ray.origin, tarPos,Color.red);
    return tarPos;
}           

複制

以上三種方法并不沖突,可同時使用。一般第三種方法要優于前兩種,它可以提前避免遭遇障礙物,但往往這樣做也缺乏了一定的真實性,可以額外設定一個視野範圍進行優化,判斷AI是否能提前察覺障礙物,此時發出的射線長度也應該是該視野半徑值,一旦AI在行進目标位置的過程中發現障礙物,不等碰到障礙物之前就重新随機下一個目标位置。

AI通過學習記錄的無效目标位置集合可用于每次随機出下一目标位置的判定依據,該集合點可設定為所有AI進行随機判定的全局變量。

private void CheckDeadZone(Vector3 tarPos)
{
    foreach(var item in deadPoints)
    {
        if ((item - tarPos).sqrMagnitude < 1f)
        {
            //重新随機目标位置...
            return;
        }
    }
}           

複制

後來發現在遇到快速運動的物體的實體穿透問題時也可以用到射線檢測作為預判,在快要到達障礙物時脫離目前運動狀态或将速度迅速降低,可有效解決實體穿透的問題。