资讯动态

Unity DOTS Physics Raycast完全指南:从概念到并行批量射线检测

发布时间:2026/10/8 4:44:45 来源:尧图企业网站定制
很多开发者在从传统 MonoBehaviour 切换到 DOTS 之后第一个卡住的地方往往不是 ECS 本身的语法而是物理系统——以前一行Physics.Raycast就能完成的射线检测在 ECS 世界里居然找不到对应的 API网上资料又零散不成体系。本文围绕 DOTS Physics 中的 Raycast 做一次完整拆解从核心概念、环境准备、参数含义到可直接运行的射线检测系统、批量射线方案和常见踩坑排查尽量做到新手能看懂原理老手能直接抄代码落地。需要提前说明的是DOTS 的包版本和 API 变化速度比较快本文示例以 Entities 1.0 系列和 Unity Physics 1.0 系列的 API 风格为准。如果你的项目版本不同个别命名和系统组名可能需要微调但整体思路是通用的。1. DOTS Physics 与 Raycast 到底是什么1.1 从 DOTS 说起DOTS 是 Unity 面向数据的整套技术栈缩写核心包含三部分ECS、Job System、Burst Compiler。它的核心思想是把游戏对象拆成 Entity实体、Component组件数据、System处理逻辑让数据连续排列在内存中配合多线程和 Burst 编译获得高性能。在这个体系里“物体”不再是一个GameObject而是一个Entity。“物体的位置”不再是一个Transform组件而是LocalTransform这样的纯数据组件。“物理碰撞体”也是一个组件叫PhysicsCollider。很多初学者最大的困惑是我明明把PhysicsShape和PhysicsBody挂在了 GameObject 上为什么代码里访问不到这是因为 DOTS 的物理系统工作在 ECS World 里GameObject 只是“编辑器下的编辑壳”真正参与运算的是转换baking之后的 Entity 和 Component。理解这一点后面很多问题都会迎刃而解。1.2 Unity Physics 在 DOTS 生态中的角色Unity PhysicsPackage 名称com.unity.physics是 DOTS 生态里的官方物理系统实现。它负责给 Entity 添加刚体属性静态、动态、运动学给 Entity 添加碰撞体盒体、球体、胶囊、凸包、网格等维护一个可查询的物理世界结构包括CollisionWorld碰撞世界用于射线、距离、碰撞查询和DynamicsWorld动力学世界用于模拟求解提供碰撞事件、触发器事件、距离查询、射线检测等能力。传统 Unity 物理引擎的碰撞数据是引擎内部维护的黑盒你只能通过 API 查询而 Unity Physics 的碰撞世界是一个可以显式访问、可以放进 Job、可以并行处理的“数据集合”。这也是 DOTS 物理性能优势的来源之一。1.3 传统 Raycast 与 DOTS Raycast 的区别传统写法if (Physics.Raycast(origin, direction, out RaycastHit hit, maxDistance)) { Debug.Log(hit.collider.name); }DOTS 射线检测的思考方式完全不同对比项传统 Physics.RaycastDOTS Physics Raycast查询对象Unity 场景里的 GameObject ColliderECS 世界里的 PhysicsCollider 组件世界来源引擎内部自动维护PhysicsWorldSingleton中的CollisionWorld命中结果RaycastHit带collider/transformUnity.Physics.RaycastHit带Entity/ColliderKey过滤方式LayerMaskCollisionFilterBelongsTo / CollidesWith多线程一般在主线程调用可放入 Job / Burst 并行执行Is Trigger 处理trigger 默认不阻挡或可配置碰撞体需要区分 Collider 与 Trigger射线只对普通碰撞体生效简单说DOTS 的 Raycast 不是“调一个全局函数”而是“向一个物理世界数据结构发起查询”。你需要先拿到物理世界的引用再构造一个RaycastInput去查询最后从结果里读取命中的 Entity。2. 环境准备与版本说明2.1 Unity 版本与包依赖本文示例推荐使用较新的 LTS 版本 Unity例如 Unity 2022.3 或更高版本并且在 Package Manager 中安装以下两个包com.unity.entitiesEntities 1.0 系列com.unity.physicsUnity Physics 1.0 系列安装方式有两种打开 Package Manager 窗口左上角选择 “Unity Registry”搜索Entities和Physics安装直接编辑项目的Packages/manifest.json添加依赖后让 Unity 自动解析。参考片段如下{ dependencies: { com.unity.entities: 1.0.16, com.unity.physics: 1.0.16 } }这里需要注意版本号一定要根据你的 Unity 版本和包管理器里实际能解析到的版本去填不要盲目照抄。因为 DOTS 的 API 还在持续演进不同小版本的类名、系统组名、方法签名偶尔会有差异。一个稳妥的做法是先通过 Package Manager 安装再查看包版本对应的 changelog。2.2 示例项目整体结构为了后续代码演示清晰我建议按下面的目录结构组织脚本Assets/ ├── Scripts/ │ ├── RaycastDebugData.cs │ ├── SceneInitSystem.cs │ ├── SingleRaycastSystem.cs │ └── RaycastDebugSystem.cs └── Scenes/ └── DotsRaycastScene.unity在 Scene 中你需要创建一个空物体挂一个初始化的 SubScene或者直接使用纯代码创建实体在场景中放置若干受物理影响的物体进入 Play Mode 后观察射线命中结果与调试线。3. DOTS Physics Raycast 核心原理拆解3.1 碰撞体与物理体在 DOTS Physics 中一个能被射线命中的 Entity 必须至少满足两个条件有PhysicsCollider组件保存碰撞体的几何形状数据有LocalTransform或LocalToWorld组件决定碰撞体在世界中的位置和姿态。如果是纯代码创建实体通常还需要PhysicsBody组件来标记刚体类型。PhysicsBody.MotionType有三个值MotionType.Static静态刚体不参与模拟适合地面、墙壁MotionType.Dynamic动态刚体受重力、外力影响适合小球、箱子MotionType.Kinematic运动学刚体由代码驱动不受力但能推倒其他物体。一个常见的误区是只有动态刚体才能被射线命中。实际上只要有PhysicsCollider静态、动态、运动学刚体都可以被射线命中。射线检测并不关心刚体是否参与模拟它只关心碰撞世界里的碰撞体是否存在。PhysicsCollider.Value是一个BlobAssetReferenceCollider这是 DOTS 中一种把碰撞几何数据以二进制形式保存在非托管内存中的结构。运行时直接调用BoxCollider.Create()会产生一块 BlobAsset 内存使用完毕后需要手动Dispose()否则会内存泄漏。这也是为什么工程上更推荐用 Baker / SubScene 烘焙而不是在运行时反复手动创建 Collider。3.2 PhysicsWorld 与 PhysicsWorldSingleton在 DOTS Physics 中物理系统会定期把所有带PhysicsCollider的 Entity 构建成一个物理世界。这个世界的本体是PhysicsWorld它包含CollisionWorld用于射线检测、距离检测、碰撞查询DynamicsWorld用于动力学模拟、速度、质量等数据。但PhysicsWorld本身太大且可能被每帧重建不能直接作为唯一组件到处传递。于是 Unity Physics 提供了一个轻量级封装PhysicsWorldSingleton。它是一个特殊的单例组件用来在 System 里安全地访问当前的物理世界。获取方式非常固定PhysicsWorldSingleton physicsWorld SystemAPI.GetSingletonPhysicsWorldSingleton();拿到之后直接调用它的CastRay方法即可physicsWorld.CastRay(input, out Unity.Physics.RaycastHit hit);你也可以通过physicsWorld.PhysicsWorld.CollisionWorld访问底层碰撞世界比如查询当前世界里的刚体数量if (physicsWorld.PhysicsWorld.CollisionWorld.NumBodies 0) return;这个空判断很重要因为物理世界可能在第一帧还未构建完成或者场景里根本没有碰撞体。3.3 RaycastInput 参数解读RaycastInput是射线检测的输入结构核心字段有三个public struct RaycastInput { public float3 Start; // 射线起点世界坐标 public float3 End; // 射线终点世界坐标 public CollisionFilter Filter; // 碰撞过滤 }这里最容易踩坑的是End字段。它表示的是“终点世界坐标”不是“方向向量”也不是“方向乘以长度后的偏移量”省略写法。我们经常看到有人这样写// 错误示例End 被写成了方向向量 var input new RaycastInput { Start start, End direction, // 这里 direction 如果是 (1,0,0)射线区间会非常短 Filter CollisionFilter.Default };正确写法应该是float3 start new float3(0f, 1f, 0f); float3 end start new float3(10f, 0f, 0f);如果已知方向和长度可以这样计算float3 direction math.normalize(new float3(1f, 0f, 0f)); float maxDistance 10f; float3 start new float3(0f, 1f, 0f); float3 end start direction * maxDistance;3.4 CollisionFilter 的过滤逻辑CollisionFilter是 DOTS Physics 里替代 LayerMask 的过滤机制它有三个字段public struct CollisionFilter { public uint BelongsTo; // 自己属于哪个层 public uint CollidesWith; // 能和哪些层碰撞 public int GroupIndex; // 分组索引0 表示按层判断 }BelongsTo和CollidesWith是 32 位的 bitmask。通常用位移运算符定义uint layerCharacter 1 0; uint layerEnvironment 1 1;如果一个实体的Collider上 Filter 设置为new CollisionFilter { BelongsTo layerCharacter, CollidesWith layerEnvironment }表示这个实体属于角色层只和环境层碰撞。射线查询中的Filter.CollidesWith如果是 0那么射线什么都打不到。一个常见问题就是 Filter 不匹配导致射线永远 Miss。如果不关心分层直接用CollisionFilter.Default它相当于BelongsTo ~0u、CollidesWith ~0u即所有层都参与。注意RaycastInput.Filter与Collider.Filter是“两边同时满足”的关系射线要命中一个碰撞体必须射线的CollidesWith包含碰撞体的BelongsTo位同时碰撞体的CollidesWith也要包含射线的BelongsTo位。4. 完整实战DOTS 射线检测系统下面进入正题我们完整走一遍从搭建场景、创建实体到射线检测、结果调试的流程。4.1 用 SubScene 搭建测试关卡DOTS 场景的推荐做法是使用 SubScene。你可以先创建一个普通的 Scene然后右键 Hierarchy 选择创建 SubScene在 SubScene 里摆放游戏物体。给一个 GameObject 添加物理属性时添加Physics Body组件把Motion Type设置为Static地面、墙添加Physics Shape组件把Shape Type设置为Box并调整Size把物体拖入 SubSceneUnity 会在烘焙时自动把它转换成带PhysicsCollider和PhysicsBody的 Entity。这样搭建出来的关卡数据会在进入 Play Mode 后自动参与 DOTS 物理世界构建无需写任何代码。对于地面、障碍物这种静态物体强烈建议用 SubScene 而不是纯代码创建因为它会让 Unity Physics 自动处理 Collider BlobAsset 的生成与释放。4.2 纯代码创建障碍物实体有些场景无法使用 SubScene比如运行时动态生成地形或物体。这种情况下可以用ISystem在初始化阶段创建实体。下面是一段可以放在Assets/Scripts/SceneInitSystem.cs的代码用来创建一块地面和一个墙壁方块using Unity.Entities; using Unity.Mathematics; using Unity.Physics; [UpdateInGroup(typeof(InitializationSystemGroup))] public partial struct SceneInitSystem : ISystem { public void OnCreate(ref SystemState state) { var em state.EntityManager; // 创建地面 var groundEntity em.CreateEntity(); em.AddComponentData(groundEntity, new PhysicsBody { MotionType MotionType.Static }); em.AddComponentData(groundEntity, new PhysicsCollider { Value BoxCollider.Create(new BoxGeometry { Size new float3(20f, 1f, 20f), Center new float3(0f, -0.5f, 0f), Orientation quaternion.identity }) }); em.AddComponentData(groundEntity, new LocalTransform { Position float3.zero, Rotation quaternion.identity, Scale 1f }); // 创建静态墙壁方块 var wallEntity em.CreateEntity(); em.AddComponentData(wallEntity, new PhysicsBody { MotionType MotionType.Static }); em.AddComponentData(wallEntity, new PhysicsCollider { Value BoxCollider.Create(new BoxGeometry { Size new float3(1f, 1f, 1f), Center float3.zero, Orientation quaternion.identity }) }); em.AddComponentData(wallEntity, new LocalTransform { Position new float3(3f, 0.5f, 0f), Rotation quaternion.identity, Scale 1f }); // 这个系统只需要执行一次 state.Enabled false; } public void OnUpdate(ref SystemState state) { } }代码里有几个细节值得解释AddComponentData会以组件默认值创建组件但我们在括号里直接传入了带初始值的结构体所以创建出来就是配置好的状态BoxCollider.Create返回的是BlobAssetReferenceCollider运行时创建后如果不释放会有内存泄漏示例为了方便演示暂时“默认”为整个游戏生命周期存活创建 Entity 后必须给LocalTransform否则物理系统无法知道碰撞体在世界中的位置。4.3 单条射线检测系统实现接下来是本文的核心射线检测系统。为了让调试效果直观我故意不在这段代码上开启 Burst因为UnityEngine.Debug.DrawLine是托管 API无法在 Burst 编译的方法里调用。代码放在Assets/Scripts/SingleRaycastSystem.csusing Unity.Entities; using Unity.Mathematics; using Unity.Physics; using Unity.Physics.Systems; using UnityEngine; [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))] [UpdateAfter(typeof(PhysicsSystemGroup))] public partial struct SingleRaycastSystem : ISystem { public void OnCreate(ref SystemState state) { state.RequireForUpdatePhysicsWorldSingleton(); } public void OnUpdate(ref SystemState state) { PhysicsWorldSingleton physicsWorld SystemAPI.GetSingletonPhysicsWorldSingleton(); if (physicsWorld.PhysicsWorld.CollisionWorld.NumBodies 0) return; float3 start new float3(0f, 1f, 0f); float3 end new float3(8f, 1f, 0f); RaycastInput input new RaycastInput { Start start, End end, Filter CollisionFilter.Default }; if (physicsWorld.CastRay(input, out Unity.Physics.RaycastHit hit)) { Debug.DrawLine(start, hit.Position, Color.green, 0.1f); Debug.Log($命中实体{hit.Entity}命中点{hit.Position}); } else { Debug.DrawLine(start, end, Color.red, 0.1f); } } }这段代码的执行流程是OnCreate 中通过RequireForUpdatePhysicsWorldSingleton()保证物理世界单例存在时系统才更新每次更新先拿到PhysicsWorldSingleton判断碰撞世界里是否有刚体避免世界还没构建好就查询构造RaycastInput从(0,1,0)向(8,1,0)发射射线调用CastRay如果命中就画一条绿线并打印结果否则画红线。把这条射线放在y 1的高度是为了确保它正好能打在y范围是[0,1]的静态方块上同时不会打中地面。如果射线高度设成 0就会命中地面而不是墙壁这也是调试时很容易混淆的地方。4.4 Burst 友好写法结果回写单例组件上面的写法简单直观但直接在 System 里调用Debug.Log会破坏 Burst 编译不适合生产环境。正确的做法是把射线查询和可视化分离射线查询走 Burst结果写入一个普通组件可视化由一个非 Burst 系统读取组件后画线。先定义一个调试数据组件放在Assets/Scripts/RaycastDebugData.csusing Unity.Entities; using Unity.Mathematics; public struct RaycastDebugData : IComponentData { public bool IsHit; // 是否命中 public float3 Start; // 射线起点 public float3 End; // 射线终点 public float3 HitPosition; // 命中点 }然后在系统 OnCreate 中创建一个带这个组件的单例实体方便其他系统GetSingletonvar debugEntity state.EntityManager.CreateEntity(); state.EntityManager.AddComponentData(debugEntity, new RaycastDebugData { IsHit false, Start float3.zero, End float3.zero, HitPosition float3.zero });修改后的射线查询系统可以这样写using Unity.Burst; using Unity.Entities; using Unity.Mathematics; using Unity.Physics; using Unity.Physics.Systems; [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))] [UpdateAfter(typeof(PhysicsSystemGroup))] public partial struct BurstRaycastSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdatePhysicsWorldSingleton(); state.RequireForUpdateRaycastDebugData(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var physicsWorld SystemAPI.GetSingletonPhysicsWorldSingleton(); var physicsWorldData physicsWorld.PhysicsWorld; if (physicsWorldData.CollisionWorld.NumBodies 0) return; float3 start new float3(0f, 1f, 0f); float3 end new float3(8f, 1f, 0f); var input new RaycastInput { Start start, End end, Filter CollisionFilter.Default }; bool isHit physicsWorld.CastRay(input, out Unity.Physics.RaycastHit hit); SystemAPI.SetSingleton(new RaycastDebugData { IsHit isHit, Start start, End end, HitPosition isHit ? hit.Position : end }); } }可视化的 Debug 系统如下它放在Assets/Scripts/RaycastDebugSystem.csusing Unity.Entities; using Unity.Physics.Systems; using UnityEngine; [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))] [UpdateAfter(typeof(BurstRaycastSystem))] public partial struct RaycastDebugSystem : ISystem { public void OnUpdate(ref SystemState state) { if (!SystemAPI.HasSingletonRaycastDebugData()) return; var data SystemAPI.GetSingletonRaycastDebugData(); if (data.IsHit) Debug.DrawLine(data.Start, data.HitPosition, Color.green, 0.1f); else Debug.DrawLine(data.Start, data.End, Color.red, 0.1f); } }这种“查询系统只负责计算调试系统只负责表现”的架构在 DOTS 项目里非常推荐。它保证了核心物理查询仍然可以被 Burst 编译同时把可视化、日志这类托管操作隔离出去。4.5 批量射线与并行 Raycast很多实际需求不止一条射线例如子弹弹幕、雷达扫描、群群体索敌。DOTS 的射线查询天然适合并行化。一种常见的做法是把“每条射线的请求”做成组件然后通过IJobEntity批量处理。先定义请求组件和结果组件using Unity.Entities; using Unity.Mathematics; public struct RaycastRequest : IComponentData { public float3 Direction; public float MaxDistance; } public struct RaycastResultData : IComponentData { public bool Hit; public float3 HitPosition; public Entity HitEntity; }然后在实体上同时挂上LocalTransform、RaycastRequest、RaycastResultData用下面的 Job 处理using Unity.Burst; using Unity.Entities; using Unity.Mathematics; using Unity.Physics; [BurstCompile] public partial struct RaycastRequestJob : IJobEntity { [ReadOnly] public PhysicsWorldSingleton PhysicsWorld; public void Execute(in LocalTransform localTransform, in RaycastRequest request, ref RaycastResultData result) { float3 end localTransform.Position request.Direction * request.MaxDistance; var input new RaycastInput { Start localTransform.Position, End end, Filter CollisionFilter.Default }; if (PhysicsWorld.CastRay(input, out Unity.Physics.RaycastHit hit)) { result.Hit true; result.HitPosition hit.Position; result.HitEntity hit.Entity; } else { result.Hit false; result.HitEntity Entity.Null; } } }调度这个 Job 的 System 写法如下using Unity.Burst; using Unity.Entities; using Unity.Physics; [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))] [UpdateAfter(typeof(PhysicsSystemGroup))] public partial struct RaycastRequestSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdateRaycastRequest(); state.RequireForUpdatePhysicsWorldSingleton(); } [BurstCompile] public void OnUpdate(ref SystemState state) { state.Dependency new RaycastRequestJob { PhysicsWorld SystemAPI.GetSingletonPhysicsWorldSingleton() }.Schedule(state.Dependency); } }这里的关键点在于PhysicsWorldSingleton是只读传递给 Job 的因此多个 Entity 的射线查询可以在不同线程上并行执行互不干扰。比起在主线程里循环发几百条射线这种方式在大量查询场景下效率要高得多。如果不需要把请求建模成组件也可以直接用NativeArrayRaycastCommand一次性构造一批射线命令然后调用RaycastCommand.Schedule调度。不同 Unity Physics 版本里RaycastCommand.Schedule的重载略有不同使用前建议先查看当前包版本的 API 文档避免签名对不上。5. 常见问题与排查思路DOTS Physics Raycast 虽然性能好但踩坑也不少。下面把我在实战中经常遇到的问题整理成表方便快速定位。问题现象常见原因解决思路射线永远打不到任何东西实体没有PhysicsCollider或碰撞体位置不对检查目标实体是否带PhysicsCollider确认LocalTransform位置GetSingletonPhysicsWorldSingleton()报错没有安装 Unity Physics 包或物理系统未初始化安装com.unity.physics在 OnCreate 中加RequireForUpdate命中的实体是地面不是目标墙壁射线高度不合适先碰到了其他碰撞体调整射线起点/终点高度或用 Filter 过滤不需要的层射线 miss但物体明明有 ColliderRaycastInput.End被写成了方向向量确认End是世界坐标终点不是 direction命中位置和物体显示位置不一致碰撞体由LocalTransform定位与可视化 Mesh 位置不同步检查实体的LocalTransform和烘焙组件是否正确Burst 编译报错“Managed method call”在[BurstCompile]方法里调用了Debug.Log、Debug.DrawLine把这些托管调用拆到独立非 Burst 系统运行时创建 Collider 后内存持续增长BoxCollider.Create()/SphereCollider.Create()创建的 BlobAsset 未释放生产环境使用 SubScene/Baker或统一用 BlobAssetStore 管理生命周期除了表格里的问题还有一个容易忽略的现象如果射线打在了 Trigger 碰撞体上很多版本默认是不返回命中的。因为在 Unity Physics 的设计里Trigger 主要用于“进入/离开”事件而不是作为射线阻挡物。如果你希望射线忽略墙壁、只检测某个特殊标记请优先使用CollisionFilter而不是在结果里做二次判断。6. 最佳实践与工程建议在实际项目里使用 DOTS Physics Raycast建议从一开始就养成良好的工程习惯避免后面大规模重构。第一射线检测系统要明确更新顺序。物理世界是在PhysicsSystemGroup中构建的所以射线查询系统最好放在FixedStepSimulationSystemGroup中并用[UpdateAfter(typeof(PhysicsSystemGroup))]保证物理世界已经构建完成。如果你的项目里用到了多个物理查询系统要给它们之间定义清晰的先后依赖关系不要让两个系统同帧互相覆盖数据。第二射线查询和调试表现分离。所有物理查询逻辑尽量保持 Burst 编译把Debug.Log、Debug.DrawLine、Gizmos 等托管 API 隔离到专门的调试系统里。这样做的收益在于发布时可以直接剔除调试系统查询逻辑本身不需要修改。结果数据写入单例组件或 Buffer再由调试系统读取。第三CollisionFilter 使用显式定义不要全部套 Default。虽然CollisionFilter.Default开发时很方便但项目中期之后射线需要区分玩家、敌人、环境、Trigger、特效等不同层。建议项目里有一个静态类统一管理 Layer Bitpublic static class PhysicsLayers { public const uint Player 1 0; public const uint Enemy 1 1; public const uint Environment 1 2; public const uint Trigger 1 3; }查询时使用带名字的常量而不是魔法数字。这能避免 Filter 错配带来的诡异 Miss。第四谨慎处理运行时创建的 Collider BlobAsset。上文已经提到BoxCollider.Create这类 API 会在非托管内存中分配数据。如果运行时频繁创建实体比如生成大量障碍物、子弹命中后动态改变碰撞体一定要统一管理 BlobAsset 的释放否则 Unity Profiler 里可以看到非托管内存持续上涨。第五优先使用 SubScene / Baker 声明关卡数据。对于一个固定关卡摆在地图上的静态墙壁、地面完全可以通过 SubScene 烘焙自动生成不需要在运行时创建。这不仅让物理世界构建更快也让 Collider 数据生命周期由 Unity 自动管理从根源上规避内存泄漏问题。第六大规模射线查询优先走 Job。如果是几十条射线普通循环也能接受如果是几百条甚至更多一定要让查询并行执行。IJobEntity搭配PhysicsWorldSingleton或者RaycastCommand批量调度都比主线程循环性能好很多。7. 总结这篇文章围绕 DOTS Physics Raycast 做了一次从概念到落地的完整梳理。我们知道了 DOTS 物理的核心不是“调用一个全局函数”而是向由PhysicsCollider构建出的CollisionWorld发起查询学会了通过PhysicsWorldSingleton获取物理世界构造带Start、End、Filter的RaycastInput并从Unity.Physics.RaycastHit中提取命中的 Entity 和位置也通过完整示例掌握了单条射线、Burst 友好写法、以及基于IJobEntity的并行批量射线方案。下一步值得继续研究的方向包括接触事件与触发器事件、DistanceQuery距离查询、CCD 碰撞检测、以及物理查询与 Havok Physics 的对照使用。如果你是在一个刚切换 DOTS 的项目里做功能请优先记住三件事检查 Collider 是否存在、确认 Filter 是否匹配、不要把调试代码写进 Burst 方法里。把这三件事做好射线检测这块基本就不会再出幺蛾子。如果这篇文章对你有帮助可以收藏备用。后续再遇到 DOTS 物理相关的疑难杂症也欢迎在评论区一起交流。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑