Physics.RaycastNonAlloc
是 Unity 中用于 3D 物理射線檢測的高性能方法,它是 Physics.Raycast
的非分配版本。
方法簽名
public static int RaycastNonAlloc(Ray ray, RaycastHit[] results, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)public static int RaycastNonAlloc(Vector3 origin, Vector3 direction, RaycastHit[] results, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
參數說明
- ray/origin+direction: 射線或起點+方向
- results: 預分配的 RaycastHit 數組
- maxDistance: 射線最大距離
- layerMask: 層級掩碼
- queryTriggerInteraction: 是否檢測觸發器
數組大小限制的重要性
問題說明
當可能擊中的目標數量超過 results
數組的大小時,超出容量的擊中目標將被忽略,這可能導致重要的碰撞檢測被遺漏。
演示示例
using UnityEngine;public class RaycastLimitationDemo : MonoBehaviour
{[SerializeField] private int arraySize = 3;[SerializeField] private float rayDistance = 100f;private RaycastHit[] smallArray;private RaycastHit[] largeArray;void Start(){smallArray = new RaycastHit[arraySize]; // 小數組largeArray = new RaycastHit[50]; // 大數組}void Update(){if (Input.GetKeyDown(KeyCode.Space)){CompareRaycastResults();}}void CompareRaycastResults(){Vector3 origin = transform.position;Vector3 direction = transform.forward;// 使用小數組檢測int smallHitCount = Physics.RaycastNonAlloc(origin, direction, smallArray, rayDistance);// 使用大數組檢測int largeHitCount = Physics.RaycastNonAlloc(origin, direction, largeArray, rayDistance);Debug.Log($"小數組(大小:{arraySize})檢測到: {smallHitCount} 個目標");Debug.Log($"大數組(大小:50)檢測到: {largeHitCount} 個目標");if (largeHitCount > smallHitCount){Debug.LogWarning($"?? 遺漏了 {largeHitCount - smallHitCount} 個目標!");// 顯示被遺漏的目標for (int i = smallHitCount; i < largeHitCount; i++){Debug.LogWarning($"遺漏目標: {largeArray[i].collider.name} (距離: {largeArray[i].distance:F2})");}}}
}
實際問題場景
1. 子彈穿透系統問題
public class BulletPenetration : MonoBehaviour
{[SerializeField] private int maxPenetrations = 3;private RaycastHit[] hits;void Start(){// ? 錯誤:數組太小,可能遺漏目標hits = new RaycastHit[maxPenetrations];}public void FireBullet(Vector3 origin, Vector3 direction, float range){int hitCount = Physics.RaycastNonAlloc(origin, direction, hits, range);Debug.Log($"檢測到 {hitCount} 個目標");// 問題:如果路徑上有5個目標,但數組只能容納3個// 最后2個目標不會被檢測到,即使它們在射線路徑上for (int i = 0; i < hitCount && i < maxPenetrations; i++){ProcessHit(hits[i]);}}void ProcessHit(RaycastHit hit){Debug.Log($"擊中: {hit.collider.name}");}
}
2. 改進的解決方案
public class ImprovedBulletPenetration : MonoBehaviour
{[SerializeField] private int maxPenetrations = 3;[SerializeField] private int maxDetectionTargets = 20; // 增大檢測容量private RaycastHit[] allHits;void Start(){// ? 正確:使用更大的數組確保不遺漏目標allHits = new RaycastHit[maxDetectionTargets];}public void FireBullet(Vector3 origin, Vector3 direction, float range){int totalHits = Physics.RaycastNonAlloc(origin, direction, allHits, range);Debug.Log($"路徑上共檢測到 {totalHits} 個目標");// 根據距離排序(RaycastNonAlloc 默認已按距離排序)int processedHits = 0;for (int i = 0; i < totalHits && processedHits < maxPenetrations; i++){if (CanPenetrate(allHits[i])){ProcessHit(allHits[i]);processedHits++;}else{// 遇到無法穿透的目標,停止處理ProcessHit(allHits[i]);break;}}// 顯示未處理的目標(因為穿透限制)if (totalHits > processedHits){Debug.Log($"因穿透限制,忽略了后續 {totalHits - processedHits} 個目標");}}bool CanPenetrate(RaycastHit hit){// 檢查材質或標簽決定是否可穿透return hit.collider.CompareTag("Penetrable");}void ProcessHit(RaycastHit hit){Debug.Log($"處理擊中: {hit.collider.name} (距離: {hit.distance:F2})");}
}
3. 動態數組大小管理
public class DynamicRaycastSystem : MonoBehaviour
{private RaycastHit[] raycastBuffer;private int currentBufferSize = 10;private const int MAX_BUFFER_SIZE = 100;void Start(){raycastBuffer = new RaycastHit[currentBufferSize];}public RaycastHit[] PerformRaycast(Vector3 origin, Vector3 direction, float distance){int attempts = 0;int hitCount;do{hitCount = Physics.RaycastNonAlloc(origin, direction, raycastBuffer, distance);// 如果數組已滿,說明可能還有更多目標if (hitCount == raycastBuffer.Length && currentBufferSize < MAX_BUFFER_SIZE){// 擴大數組currentBufferSize = Mathf.Min(currentBufferSize * 2, MAX_BUFFER_SIZE);raycastBuffer = new RaycastHit[currentBufferSize];Debug.LogWarning($"擴大射線檢測緩沖區至 {currentBufferSize}");attempts++;}else{break;}}while (attempts < 3); // 最多嘗試3次擴展// 返回實際擊中的結果RaycastHit[] results = new RaycastHit[hitCount];System.Array.Copy(raycastBuffer, results, hitCount);return results;}
}
最佳實踐建議
1. 合理估算數組大小
public class RaycastBestPractices : MonoBehaviour
{// 根據場景復雜度設置緩沖區大小private RaycastHit[] hits;void Start(){// 分析你的場景:// - 最密集區域可能有多少個碰撞器?// - 射線最長距離內可能遇到多少目標?// - 加上安全余量int estimatedMaxTargets = AnalyzeSceneComplexity();int safetyBuffer = estimatedMaxTargets / 2;int bufferSize = estimatedMaxTargets + safetyBuffer;hits = new RaycastHit[bufferSize];Debug.Log($"射線檢測緩沖區大小: {bufferSize}");}int AnalyzeSceneComplexity(){// 簡單的場景復雜度分析Collider[] allColliders = FindObjectsOfType<Collider>();// 可以根據場景大小、碰撞器密度等因素計算return Mathf.Max(20, allColliders.Length / 10);}
}
2. 性能監控
public class RaycastPerformanceMonitor : MonoBehaviour
{private RaycastHit[] hits = new RaycastHit[50];private int maxHitsRecorded = 0;void Update(){if (Input.GetMouseButton(0)){PerformMonitoredRaycast();}}void PerformMonitoredRaycast(){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);int hitCount = Physics.RaycastNonAlloc(ray, hits, 100f);// 記錄最大擊中數if (hitCount > maxHitsRecorded){maxHitsRecorded = hitCount;Debug.Log($"新的最大擊中數記錄: {maxHitsRecorded}");}// 檢查是否接近數組限制if (hitCount >= hits.Length * 0.8f){Debug.LogWarning($"射線檢測接近緩沖區限制!當前: {hitCount}/{hits.Length}");}}void OnGUI(){GUI.Label(new Rect(10, 10, 300, 20), $"最大擊中記錄: {maxHitsRecorded}");GUI.Label(new Rect(10, 30, 300, 20), $"緩沖區大小: {hits.Length}");}
}
總結
使用 Physics.RaycastNonAlloc
時,數組大小的選擇至關重要:
- 過小的數組:會導致遺漏目標,可能影響游戲邏輯
- 過大的數組:浪費內存,但確保完整性
- 最佳實踐:根據場景復雜度合理估算,加上安全余量
- 監控機制:在開發階段監控實際使用情況,調整數組大小
記住:寧可數組稍大一些,也不要因為大小不足而遺漏重要的碰撞檢測。