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. 监控机制:在开发阶段监控实际使用情况,调整数组大小

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

相关推荐
郝学胜-神的一滴14 小时前
[简化版 GAMES 101] 计算机图形学 08:三角形光栅化上
c++·unity·游戏引擎·godot·图形渲染·opengl·unreal
nnsix14 小时前
Unity ILRuntime 笔记
unity·游戏引擎
nnsix16 小时前
Unity API 兼容的 .NET Standard 2.1 和 .NET Framework 区别
unity·游戏引擎·.net
mxwin17 小时前
Unity Shader 制作半透明物体 使用多Pass提前写入深度的方式 避免穿模
unity·游戏引擎
nnsix18 小时前
Unity HybridCLR 笔记
笔记·unity·游戏引擎
nnsix20 小时前
Unity Addressables 笔记
unity·游戏引擎
RReality20 小时前
【Unity Shader URP】视差贴图 实战教程
ui·平面·unity·游戏引擎·图形渲染·贴图
小清兔1 天前
Addressable的设置打包流程
笔记·游戏·unity·c#
3D霸霸2 天前
Sourcetree 拉取新工程
数据仓库·unity
程序员正茂2 天前
Unity3d中RawImage显示视频画面偏白的解决方法
unity·视频·rawimage