当前位置: 首页 > news >正文

发射1w个子弹和Mono世界交互——影子Dots碰撞系统

影子 DOTS 碰撞系统

它的目标很明确:

  • 子弹仍然是海量 DOTS 实体
  • 场景里的可碰撞目标仍然可以来自 Mono 世界
  • 但碰撞检测本身尽量放回 DOTS 世界完成
  • 命中结果再通知回 Mono 世界

这套方案,本质上是为了解决一个很现实的问题:

海量 DOTS 子弹,不适合让 Mono 世界反过来逐个追踪。

尤其是像“1 万把飞剑剑雨”这种场景,如果每帧都在 Mono 世界对所有 DOTS 子弹做射线、SphereCast 或桥接查询,哪怕功能上能跑,性能和数据搬运成本都会很快暴露出来。

所以我们最后选的方向不是:

  • Mono 世界每帧追踪 DOTS 子弹

而是:

  • Mono 世界把“可被命中的碰撞体”同步一份到 DOTS 世界
  • DOTS 子弹直接和 DOTS 里的“影子碰撞体”发生命中
  • 命中后再把结果通知回对应的 Mono 物体

这就是“影子 DOTS 碰撞系统”的核心思想。


一、问题背景

最开始,这个项目里的子弹是 DOTS 生成的海量飞剑。
它们的移动、初始化、生命周期管理都在 ECS 里完成。

一开始的销毁逻辑很简单,是这种近似规则:

  • 飞剑落到某个 GroundY
  • 或者寿命结束
  • 或者超出最远距离

这种做法很便宜,但它不是“真正碰到场景物体才销毁”。

如果想让飞剑真正碰到场景里的 Collider 才消失,最直觉的想法通常有两种:

  1. 在 Mono 世界里,每帧拿 DOTS 剑的位置做射线检测
  2. 在 DOTS 世界里,直接接 Unity Physics

我们都试过。

Mono 桥接版一开始功能能跑,但很快暴露出问题:

  • 每帧要从 ECS 拿大量子弹数据
  • 再转成 Mono 世界查询输入
  • 再把命中结果写回 DOTS

这类“跨世界桥接”在子弹量大时成本很明显。

后来我们也试过 Unity Physics 的逐剑 CastRay 方案。
方向本身没错,但实现方式如果还是“每把剑逐个查”,在 1 万把剑的规模下,成本依然会很重。

所以最终我们落地的是第三种思路:

既然子弹本来就在 DOTS 世界,那就把碰撞目标也影子化到 DOTS 世界。


二、什么是影子碰撞体

所谓“影子碰撞体”,就是:

Mono 世界里原本有一个真实的 Collider,
我们在 DOTS 世界里再创建一个与它对应的碰撞体实体。

这个影子实体会同步原物体的关键信息,比如:

  • 世界位置
  • 旋转
  • 缩放
  • 碰撞盒尺寸
  • 对应 Mono 物体的实例标识

这样一来,DOTS 子弹在做碰撞检测时,面对的就不再是 Mono 世界的 Collider,
而是 DOTS 世界里的一组影子碰撞体。

命中之后,再通过影子碰撞体保存的映射关系,把事件通知回原始 Mono 物体。

你可以把它理解成:

  • Mono 世界负责“目标来源”
  • DOTS 世界负责“命中计算”
  • Mono 世界负责“业务响应”

三、系统结构

这套方案最终分成了三层。

1. Mono 侧代理层

我们新增了一个代理组件:

  • TestDotsShadowColliderProxy

它挂在 Mono 世界的可碰撞物体上,职责是:

  • 检查自己身上的 BoxCollider
  • 在 DOTS 世界创建一个对应的影子碰撞体实体
  • 每帧同步自己的变换和碰撞盒尺寸
  • 在销毁或禁用时注销对应的 DOTS 实体
  • 当 DOTS 世界通知命中时,回调到当前这个 Mono 物体

这一步的关键意义在于:

我们桥接的对象,不再是 1 万把子弹,而是少量可命中的环境物体。

这比“Mono 世界每帧追踪所有 DOTS 子弹”便宜得多。


2. DOTS 侧影子碰撞体

在 DOTS 世界里,我们给每个影子目标创建了真正的实体,并挂上:

  • 影子碰撞元数据
  • LocalTransform
  • PhysicsCollider
  • PhysicsWorldIndex

这样它就能进入 DOTS 的物理查询世界。

影子碰撞体上还会记录一个很关键的字段:

  • ColliderInstanceId

这个值用来把 DOTS 世界命中的影子实体,再映射回 Mono 世界真正的物体。

换句话说:

DOTS 世界只知道“命中了哪个影子实体”,
而代理层负责把它翻译成“命中了 Mono 世界里的哪个物体”。


3. DOTS 子弹命中系统

飞剑本身仍然是 DOTS 子弹实体。
每把剑都维护:

  • PreviousPosition
  • 当前 LocalTransform.Position
  • 速度
  • 年龄
  • 生命周期
  • 是否已经命中

这样每一帧,子弹都有一条明确的运动路径:

  • 从上一帧位置
  • 到当前帧位置

命中系统的任务,就是判断这段路径是否打到了 DOTS 世界里的影子碰撞体。

在最终实现里,这套查询放到了 DOTS 世界中执行,命中后会:

  • 把剑位置修正到命中点
  • 给子弹打上 Hit 标记
  • 创建一个命中事件实体
  • 后续由通知系统把这次命中回传给 Mono 侧

四、命中通知是怎么回到 Mono 世界的

为了把 DOTS 世界的命中结果交回 Mono 物体,我们加了一层事件回传:

  • TestDotsShadowHitEvent
  • TestDotsShadowHitNotifier

流程是这样的:

  1. DOTS 命中系统检测到飞剑打中了某个影子碰撞体
  2. 创建一条 TestDotsShadowHitEvent
  3. 事件里带上:
    • 命中目标的 ColliderInstanceId
    • 命中点
  4. TestDotsShadowHitNotifier 读取这些事件
  5. 再通过 TestDotsShadowColliderProxy.TryNotifyHit(...)
    找回对应的 Mono 物体
  6. Mono 侧执行 OnShadowHit(...)

当前我们先做的是最简单的验证版:

    • 打印命中物体名称
    • 打印命中点

```mermaid
flowchart LR
A["Mono World<br/>GameObject + BoxCollider"] --> B["TestDotsShadowColliderProxy<br/>注册 / 同步 / 注销"]
B --> C["DOTS Shadow Collider Entity<br/>LocalTransform<br/>PhysicsCollider<br/>TestDotsShadowBoxCollider"]

D["DOTS Sword Bullets<br/>LocalTransform<br/>PreviousPosition<br/>Velocity<br/>Hit"] --> E["TestDotsBulletShadowCollisionSystem<br/>DOTS 内命中检测"]
C --> E

E --> F["TestDotsShadowHitEvent<br/>ColliderInstanceId<br/>HitPoint"]
F --> G["TestDotsShadowHitNotifier"]

G --> H["TestDotsShadowColliderProxy.TryNotifyHit(instanceId, hitPoint)"]
H --> I["Mono Target Object<br/>OnShadowHit(hitPoint)"]

I --> J["业务响应<br/>Log / Damage / 特效 / 销毁"]

```

 

 

http://www.jsqmd.com/news/517520/

相关文章:

  • 低配置电脑福音:MPC-HC轻量化设计与资源优化策略研究
  • 手把手教你用虹科转换器搭建车载以太网测试环境(含MATEnet接口避坑)
  • 双MCU架构下的汽车ECU硬件电路设计关键点解析
  • 贵州维恒琦科技客服咨询AI流量赋能,重塑智能体验新标杆 - 速递信息
  • 2026年铝型材价格对比,南昌铝型材精品定制哪家性价比高 - 工业品网
  • 2026年高铁驱鸟器口碑排名,看看哪家更值得选 - 工业品牌热点
  • Anolis OS7.9_x86服务器部署避坑指南:PostgreSQL+PostGIS+PgRouting地理数据库配置详解
  • SQL也能玩转机器学习?教你用PostgreSQL内置ML引擎完成用户画像分析
  • 从USB摄像头到RTSP推流:基于RK3588 MPP硬编码的YOLOv8实时AI视觉方案
  • Seed_Ultrasonic_Range驱动库深度解析:HC-SR04超声波测距的嵌入式实现
  • 告别云端API调用!手把手教你用Ollama+AnythingLLM在Windows/Mac上搭建个人DeepSeek知识库
  • 2026年驻马店靠谱玻璃贴膜公司有哪些,怎么选择 - 工业设备
  • 深入解析Linux内核中的workqueue机制与queue_work实现
  • 终极Windows文件搜索指南:PowerToys Everything插件快速上手
  • I²C多电机控制库:单总线驱动数十台直流电机
  • 在openEuler系统构建高可用Python离线部署方案:从依赖打包到环境验证
  • Excel VBA防息屏神器:5分钟搞定自动鼠标点击脚本(附完整代码)
  • IntellIJ Idea内存不足?3种快速提升性能的配置方法(附实测数据)
  • 汽车车窗贴膜多少钱,安庆市场价格如何 - 工业推荐榜
  • Alibaba数学竞赛历年真题解析:从预选赛到决赛的完整攻略(附答案)
  • HDMI2.1接口保护指南:从浪涌损坏案例看RK3588板子的ESD设计要点
  • Dify v0.12.0+私有化高可用架构升级指南:etcd集群选型对比、PostgreSQL分库策略、Redis哨兵拓扑优化(实测TPS提升3.8倍)
  • Imatest西门子星图实战:如何用Star模块精准测试相机MTF(附参数详解)
  • UE5项目本地化实战:从Localization Dashboard到多语言切换的完整配置流程
  • 实效落地 + 华中优选:2026 武汉本地优质 GEO 优化公司 TOP5 甄选推荐指南 - 速递信息
  • RK3588交叉编译避坑指南:如何解决库路径不一致和环境变量干扰问题
  • 降AI率工具的效果怎么判断?看这几个硬指标就够了
  • 【ENVI】遥感图像处理实战:从数据下载到目视解译
  • 20260320 之所思 - 人生如梦
  • Prism+DryIoc避坑指南:从零构建WPF MVVM项目时我踩过的5个坑