*這篇是以移動情況動態的迴避障礙的操控行為,不是用尋路演算法預先算出靜態的可走路徑。
如何使角色避開所有障礙到達地點呢?操控行為會用到射線的方式做迴避,先來敘述一下一般使用的操控迴避方法,會用一條射線往前打出,打到障礙物的話迴避
理念如下圖:
射線向前打出,偵測到障礙物,取得障礙物中心,以原先前進的向量前方位置(ahead)減障礙物中心就能取得迴避向量
以迴避向量與原先行走向量相加,得到迴避過後的向量
以上是一條射線做出的操避開障礙的操控行為,這個做法會有一個嚴重的缺點,如果障礙物是圓柱狀的障礙物沒有問題,但如果是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(); } }
移動與迴避
計時器來算每個偵測間隔時間,偵測時間一到打出射線做檢測,然後在所有射線裡,打到障礙物的射線為反向向量,沒打到的就是正向向量,藉由所有向量合力取得迴避向量
然後如果有打到障礙物,還要特別判斷是否迴避向量和目前行走的向量是一樣的,如果是一樣表示角色前進向量和障礙物是垂直的,會導致所有射線向量相加會是還是保持不變繼續卡牆,所以這裡判斷完如果是,就要就給他一個往右迴避的力,已便角色離開障礙物
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); } } }
最後展示:
留言列表