close

*這篇是以移動情況動態的迴避障礙的操控行為,不是用尋路演算法預先算出靜態的可走路徑。

如何使角色避開所有障礙到達地點呢?操控行為會用到射線的方式做迴避,先來敘述一下一般使用的操控迴避方法,會用一條射線往前打出,打到障礙物的話迴避

理念如下圖:

Steering-Obstacle Avoidance – Yashas Gujjar

射線向前打出,偵測到障礙物,取得障礙物中心,以原先前進的向量前方位置(ahead)減障礙物中心就能取得迴避向量

Understanding Steering Behaviors: Collision Avoidance

以迴避向量與原先行走向量相加,得到迴避過後的向量

圖片來源:https://gamedevelopment.tutsplus.com/tutorials/understanding-steering-behaviors-collision-avoidance--gamedev-7777

 

以上是一條射線做出的操避開障礙的操控行為,這個做法會有一個嚴重的缺點,如果障礙物是圓柱狀的障礙物沒有問題,但如果是T形或L形的障礙物很有可能會卡死在牆角

所以這裡會想要做出新的操控行為解決此問題

如果一個射線不夠那可以打出多條射線形成扇形來檢測障礙物,在用所有射線的合力取得迴避的行走向量,這樣也就能閃過T形或L形的障礙物了

 

程式碼部分: 

參數:

public class AvoidAI : MonoBehaviour
{
    public float m_MoveSpeed = 5f;
    public float m_TurnSpeed = 30f;
    public int m_NumberOfRays = 17;
    public float m_SensorInterval = 0.2f;
    public float m_SensorAngle = 90f;
    public float m_SensorRange = 10;
    public float m_StopRange = 5f;

    private Vector3 m_Target;
    private Vector3 m_TargetDir;
    private float m_PerRayAngle = 0;
    private float m_Timer = 0;

    private bool m_IsAvoid = false;
參數:

m_NumberOfRays:射線數量

m_SensorInterval:為了節省效能並且不需要每一幀都變更方向,所以有設定偵測間隔時間

m_SensorAngle:射線角度,也就是角色迴避視角

m_SensorRange:射線長度,也就是視線的最遠距離

m_StopRange:移動到目標點的停止距離

m_Target移動目標點

m_TargetDir移動向量

m_PerRayAngle每個射線的間隔角度

m_Timer:計時器

m_IsAvoid:是否有障礙需迴避

 

設定測試目標點

程式碼為了測試,在一開始目標點設為角色在起點位置往前方50公尺處,並預先計算射線間隔角度 

    private void Start()
    {
        //Test target
        m_Target = transform.position + transform.forward * 50;
        m_Timer = m_SensorInterval;
        var g = new GameObject("Target");
        g.transform.position = m_Target;
        m_PerRayAngle = 1f / (float)(m_NumberOfRays - 1) * m_SensorAngle * 2;
    }

往目標移動

在還沒到目標點前持續行走,用平方比對與目標點的距離是否小於停止距離

    private void Update()
    {
        if ((m_Target - transform.position).sqrMagnitude > m_StopRange * m_StopRange)
        {
            Movement();
        }
    }

移動與迴避

計時器來算每個偵測間隔時間,偵測時間一到打出射線做檢測,然後在所有射線裡,打到障礙物的射線為反向向量,沒打到的就是正向向量,藉由所有向量合力取得迴避向量

未命名繪圖 (1)

然後如果有打到障礙物,還要特別判斷是否迴避向量和目前行走的向量是一樣的,如果是一樣表示角色前進向量和障礙物是垂直的,會導致所有射線向量相加會是還是保持不變繼續卡牆,所以這裡判斷完如果是,就要就給他一個往右迴避的力,已便角色離開障礙物

未命名繪圖 (2)

    public void Movement()
    {
        m_Timer += Time.deltaTime;
        if (m_Timer >= m_SensorInterval)
        {
            m_TargetDir = Vector3.zero;
            m_IsAvoid = false;
            for (int i = 0; i < m_NumberOfRays; i++)
            {
                var rotationMod = Quaternion.AngleAxis(i * m_PerRayAngle - m_SensorAngle, Vector3.up);
                var direction = rotationMod * transform.forward;

                var ray = new Ray(transform.position, direction);
                //Debug.DrawRay(ray.origin, ray.direction * m_shipMoveSpeed, Color.blue);
                if (Physics.Raycast(ray, out RaycastHit hitInfo, m_SensorRange))
                {
                    m_IsAvoid = true;
                    m_TargetDir -= (1.0f / m_NumberOfRays) * direction;
                }
                else
                {
                    m_TargetDir += (1.0f / m_NumberOfRays) * direction;
                }
            }
            if (!m_IsAvoid) m_TargetDir = (m_Target - transform.position).normalized + transform.forward;
            else m_TargetDir = m_TargetDir.normalized == transform.forward ? m_TargetDir + Vector3.right : m_TargetDir;
            m_Timer = 0;
        }
        Debug.DrawRay(transform.position, m_TargetDir * m_SensorRange, Color.red);
        m_TargetDir.y = 0;
        Quaternion tarRotate = Quaternion.LookRotation(m_TargetDir);
        transform.rotation = Quaternion.RotateTowards(transform.rotation, tarRotate, m_TurnSpeed * Time.deltaTime);

        transform.position += transform.forward * m_MoveSpeed * Time.deltaTime;
    }

畫出測試射線

最後就畫出射線,以便測試觀察

    private void OnDrawGizmos()
    {
        m_PerRayAngle = 1f / (float)(m_NumberOfRays - 1) * m_SensorAngle * 2;
        for (int i = 0; i < m_NumberOfRays; i++)
        {
            var rotation = transform.rotation;
            var rotationMod = Quaternion.AngleAxis(i * m_PerRayAngle - m_SensorAngle, Vector3.up);
            var direction = rotation * rotationMod * Vector3.forward * m_SensorRange;
            Gizmos.DrawRay(transform.position, direction);
        }
    }
} 

最後展示:

ezgif.com-video-to-gif

arrow
arrow

    Kouhei 發表在 痞客邦 留言(0) 人氣()