unity Physics.RaycastNonAlloc

Physics.RaycastNonAlloc 是 Unity 中用于 3D 物理射线检测的高性能方法,它是 Physics.Raycast 的非分配版本。

方法签名

csharp 复制代码
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 数组的大小时,超出容量的击中目标将被忽略,这可能导致重要的碰撞检测被遗漏。

演示示例

csharp 复制代码
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. 子弹穿透系统问题

csharp 复制代码
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. 改进的解决方案

csharp 复制代码
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. 动态数组大小管理

csharp 复制代码
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. 合理估算数组大小

csharp 复制代码
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. 性能监控

csharp 复制代码
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 时,数组大小的选择至关重要:

  1. 过小的数组:会导致遗漏目标,可能影响游戏逻辑
  2. 过大的数组:浪费内存,但确保完整性
  3. 最佳实践:根据场景复杂度合理估算,加上安全余量
  4. 监控机制:在开发阶段监控实际使用情况,调整数组大小

记住:宁可数组稍大一些,也不要因为大小不足而遗漏重要的碰撞检测

相关推荐
RReality1 天前
【Unity Shader URP】Matcap 材质捕捉实战教程
java·ui·unity·游戏引擎·图形渲染·材质
魔士于安1 天前
unity urp材质球大全
游戏·unity·游戏引擎·材质·贴图·模型
南無忘码至尊1 天前
Unity学习90天 - 第 6 天 -学习物理 Material + 重力与阻力并实现弹跳球和冰面滑动效果
学习·unity·游戏引擎
mxwin1 天前
Unity 单通道立体渲染(Single Pass Instanced)对 Shader 顶点布局的特殊要求
unity·游戏引擎·shader
魔士于安1 天前
unity 低多边形 无人小村 木质建筑 晾衣架 盆子手推车,桌子椅子,罐子,水井
游戏·unity·游戏引擎·贴图·模型
RReality1 天前
【Unity Shader URP】简易卡通着色(Simple Toon)实战教程
ui·unity·游戏引擎·图形渲染·材质
魔士于安1 天前
unity 骷髅人 连招 武器 刀光 扭曲空气
游戏·unity·游戏引擎·贴图·模型
洛阳吕工1 天前
从 micro-ROS 到 px4_ros2:ROS2 无人机集成开发实战指南
游戏引擎·无人机·cocos2d
风酥糖1 天前
Godot游戏练习01-第29节-游戏导出
游戏·游戏引擎·godot
瑞瑞小安1 天前
Unity功能篇:文本框随文字内容动态调整
ui·unity