> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/raycasting.md).

# 射线投射

使用射线投射在空间中追踪一条线，并查询与场景中实体的碰撞。

射线投射是游戏开发中的基础工具。借助射线投射，你可以在空间中追踪一条虚拟直线，并查询是否有任何实体与该直线相交。这对于计算视线、子弹轨迹、寻路算法以及许多其他应用都很有用。

当玩家按下指针按钮，或主按钮或次按钮时，会从玩家的位置沿其所看的方向发射一条射线，见 [按钮事件](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/click-events.md) 了解更多详情。本文档介绍如何从任意位置和方向追踪一条不可见的射线，且不依赖玩家操作，你可以在许多其他场景中使用它。

请注意，射线投射只能命中带有碰撞器的对象。所以如果你想检测射线与 3D 模型的碰撞，请采取以下任一方式：

* 该模型必须包含 [碰撞网格](/creator/content-creator-zh/3d-jian-mo-yu-dong-hua/colliders.md).
* 该 `GLTFContainer` 必须配置为使用 [带碰撞遮罩的可见几何体](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/colliders.md#colliders-on-3d-models).
* 添加一个 [MeshCollider 组件](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/colliders.md).

此外，最好给 3D 模型分配自定义 [碰撞层](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/colliders.md#collision-layers) ，这样射线就只需计算与相关实体的碰撞，而不是与所有带有碰撞器的对象计算。

## 创建一条射线

所有射线都有一个起点和一个方向。起点基于某个实体的位置，并采用该实体 Transform 组件中的值。射线的方向可以通过 4 种不同方式定义：

* **局部**：相对于实体朝前方向的方向，也会受到任何父实体变换的影响。这对于检测遵循航向的车辆前方障碍物很有用。
* **global**：忽略实体的旋转，并像实体旋转为 0 一样朝向某个方向。例如，这对于始终向下非常有用。
* **全局目标**：在实体位置与场景中的目标全局位置之间追踪一条线。它会忽略实体的旋转。例如，这对于制作塔防游戏很有用，每座塔的炮塔都可以指向空间中的一个精确坐标。
* **目标实体**：在实体位置与第二个目标实体的位置之间追踪一条线。它会忽略任一实体的旋转。

以下代码使用局部方向创建一条射线检测：

```ts
const myEntity = engine.addEntity()
Transform.create(myEntity, {
  position: Vector3.create(4, 1, 4),
})

raycastSystem.registerLocalDirectionRaycast(
  {
    entity: myEntity,
    opts: { direction: Vector3.Forward() },
  },
  function (raycastResult) {
    // 回调函数
  }
)
```

使用以下函数，以不同方式提供方向来创建射线检测：

* `raycastSystem.registerLocalDirectionRaycast()`：创建一条带有 **局部** 方向的射线检测。 `方向` 字段期望一个 `Vector3` ，它描述了相对于实体及其旋转的向量（例如 `Vector3.Forward()` 最终会使用实体的变换前向向量）
* `raycastSystem.registerGlobalDirectionRaycast()`：创建一条带有 **global** 方向的射线检测。 `方向` 字段期望一个 `Vector3` ，用于描述全局方向。
* `raycastSystem.registerGlobalTargetRaycast()`：创建一条方向由 **全局目标** 位置 `目标` 字段期望一个 `Vector3` 定义的射线检测。
* `raycastSystem.registerTargetEntityRaycast()`：创建一条方向指向 **目标实体** 位置 `targetEntity` 字段期望一个实体引用，该实体的位置将被用作射线的目标。

在使用以上任一方法创建射线时，可使用以下可选字段：

* `maxDistance`: *数值* 用于设置这条射线的追踪长度。如果未设置，默认值为 16 米。
* `queryType`: *RaycastQueryType* 枚举值，用于定义射线是返回所有命中的实体还是只返回第一个。可用选项如下：
  * `RaycastQueryType.RQT_HIT_FIRST`: *（默认）* 只返回第一个命中的实体，从起点开始计算。
  * `RaycastQueryType.RQT_QUERY_ALL`：返回所有命中的实体，从起点一直到射线的最大距离。
* `originOffset`：不从实体的起点位置开始射线检测，而是添加一个偏移量，从相对位置开始查询。例如，你可以使用一个小偏移，防止射线与实体自身的碰撞器发生碰撞。如果未设置，默认值为 `Vector3.Zero()`.
* `collisionMask`：仅检测某些碰撞层中的碰撞。可将其与自定义碰撞层一起使用，或仅检测物理层或指针事件层。参见 [碰撞层](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/colliders.md#collision-layers)。如果未设置，默认使用的层是 `ColliderLayer.CL_PHYSICS`.
* `continuous`：如果为 true，则会在每一帧持续运行一次射线检测查询。如果为 false，则该射线只会在当前帧使用。如果未设置，默认值为 false。
* 当使用局部或全局方向设置方向时， `方向` 字段默认值为 `Vector3.Forward()`.
* 当使用全局目标设置方向时， `目标` 字段默认值为 `Vector3.Zero()`.
* 当使用实体目标设置方向时， `targetEntity` 字段默认值为场景的根实体，位于 `Vector3.Zero()`.

{% hint style="warning" %}
**📔 注意**：该 `continuous` 属性应谨慎使用，因为在每一帧都运行射线检测查询在性能上可能代价很高。在可能的情况下，请使用一个系统（或 `interval` 函数，位于 Utils 库中）以较稀疏的固定间隔运行射线检测查询，参见 [周期性射线检测](#recurrent-raycasting).
{% endhint %}

下面是使用这 4 种方法中的每一种来确定射线方向的示例：

```ts
// 局部方向射线检测
raycastSystem.registerLocalDirectionRaycast(
  {
    entity: myEntity,
    opts: {
      queryType: RaycastQueryType.RQT_QUERY_ALL,
      direction: Vector3.Forward(),
      maxDistance: 30,
    },
  },
  function (raycastResult) {
    console.log(raycastResult.hits)
  }
)
// 全局方向射线检测
raycastSystem.registerGlobalDirectionRaycast(
  {
    entity: myEntity,
    opts: {
      queryType: RaycastQueryType.RQT_QUERY_ALL,
      direction: Vector3.Forward(),
      maxDistance: 30,
    },
  },
  function (raycastResult) {
    console.log(raycastResult.hits)
  }
)
// 全局目标位置射线检测
raycastSystem.registerGlobalTargetRaycast(
  {
    entity: myEntity,
    opts: {
      queryType: RaycastQueryType.RQT_QUERY_ALL,
      target: Vector3.Zero(),
    },
  },
  (raycastResult) => {
    console.log(raycastResult.hits)
  }
)
// 目标实体射线检测
const targetEntity = engine.addEntity()
Transform.create(targetEntity, { position: Vector3.create(8, 1, 10) })

raycastSystem.registerTargetEntityRaycast(
  {
    entity: myEntity,
    opts: {
      queryType: RaycastQueryType.RQT_QUERY_ALL,
      targetEntity: targetEntity,
    },
  },
  (raycastResult) => {
    console.log(raycastResult.hits)
  }
)
```

{% hint style="warning" %}
**📔 注意**: `raycastSystem`, `RaycastQueryType` 和 `ColliderLayer` 必须通过

> `import { raycastSystem, RaycastQueryType, ColliderLayer } from "@dcl/sdk/ecs"`

参见 [导入](/creator/content-creator-zh/chang-jing-sdk7/ru-men/coding-scenes.md#imports) ，了解如何轻松处理这些内容。
{% endhint %}

## 射线检测结果

处理射线检测的回调函数会接收一个对象，其中包含关于射线本身以及任何被命中的实体的数据。

* `globalOrigin`：射线发射的起始位置，相对于场景。
* `方向`：射线所指向的全局方向，作为一个 `Vector3`.
* `hits`：一个数组，每个被命中的实体对应一个对象。如果没有命中的实体，这个数组为空。如果射线检测使用了 `RaycastQueryType.RQT_HIT_FIRST`，这个数组只会包含一个对象。

中的每个对象都包含： `hits` 数组

* `entityId`：被射线命中的实体的 ID 数字。
* `meshName`: *字符串* ，包含被命中的 3D 模型中具体网格的内部名称。当一个 3D 模型由多个网格组成时，这很有用。
* `位置`: *Vector3* 用于表示射线与命中实体相交的位置（相对于场景）
* `length`：射线从起点到命中实体发生位置的长度。
* `normalHit`: *Vector3* 用于表示世界空间中命中表面的法线。
* `globalOrigin`: *Vector3* 用于表示射线起点的位置（相对于场景）
* `方向`：射线所指向的全局方向，作为一个 `Vector3`.

以下示例会遍历被命中的实体：

```ts
const myEntity = engine.addEntity()
Transform.create(myEntity, {
  position: Vector3.create(4, 1, 4),
})

raycastSystem.registerLocalDirectionRaycast(
  {
    entity: myEntity,
    opts: {
      queryType: RaycastQueryType.RQT_QUERY_ALL,
      direction: Vector3.Forward(),
      maxDistance: 30,
    },
  },
  function (raycastResult) {
    if (raycastResult.hits.length > 0) {
      for (const hit of raycastResult.hits) {
        if (hit.entityId) {
          console.log('命中实体 ', hit.entityId)
        }
      }
    } else {
      console.log('没有命中任何实体')
    }
  }
)
```

{% hint style="warning" %}
**📔 注意**：你可以通过命中另一个场景中的实体获得射线检测结果。
{% endhint %}

## 处理命中的实体

当你获得一个命中实体的射线检测结果时，你可以使用 `entityId` 与该实体及其组件进行交互。实体不过是一个 [数字](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/entities-components.md#overview)，因此该 `entityId` 值本身可以被解释为一个 `Entity` 类型。

```ts
const hitEntity = hit.entityId as Entity
const transform = Transform.get(hitEntity)
console.log(transform.position)
```

## 碰撞层

最好只检查与相关实体的碰撞，以提升场景性能。 `collisionMask` 字段允许你只列出特定的碰撞层——物理层（场景墙壁和地板）、指针层（指针事件）、玩家层（头像），或者你可以自由分配的 8 个自定义层。参见 [碰撞层](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/colliders.md#collision-layers).

默认情况下， `collisionMask` 字段设置为 `ColliderLayer.CL_PHYSICS`。你可以将此值更改为列出其他层，或者使用 `|` 分隔符将多个层组合起来。

```ts
raycastSystem.registerLocalDirectionRaycast(
  {
    entity: myEntity,
    opts: {
      queryType: RaycastQueryType.RQT_QUERY_ALL,
      direction: Vector3.Forward(),
      maxDistance: 30,
      collisionMask:
        ColliderLayer.CL_CUSTOM1 |
        ColliderLayer.CL_CUSTOM3 |
        ColliderLayer.CL_POINTER,
    },
  },
  (raycastResult) => {
    log(raycastResult.hits)
  }
)
```

## 周期性射线检测

使用 `raycastSystem`的函数时，默认行为是创建一条只查询一次碰撞的单条射线。作为替代方案，你可以将 `continuous` 字段设置为 *true* ，以便在游戏循环的每个 tick 中都运行一次查询和回调函数。

下面的示例会从此时起持续运行射线检测查询

```ts
raycastSystem.registerLocalDirectionRaycast(
  {
    entity: myEntity,
    opts: {
      queryType: RaycastQueryType.RQT_QUERY_ALL,
      direction: Vector3.Forward(),
      maxDistance: 30,
      continuous: true,
    },
  },
  function (raycastResult) {
    log(raycastResult.hits)
  }
)
```

{% hint style="warning" %}
**📔 注意**：该 `continuous` 属性应谨慎使用，因为在每一帧都运行射线检测查询在性能上可能代价很高。
{% endhint %}

当不再需要时，请移除任何周期性射线检测。为此，你必须使用 `raycastSystem.removeRaycasterEntity`.

```ts
raycastSystem.removeRaycasterEntity(myEntity)
```

在可能的情况下，请使用一个系统（或 `interval` 函数，位于 Utils 库中）以较稀疏的固定间隔运行射线检测查询，例如每秒一次，或者每五分之一秒一次。

```typescript
// 自定义组件
const CubeOscilator = engine.defineComponent('CubeOscilator', {
  t: Schemas.Float,
})

const TimerComponent = engine.defineComponent('TimerComponent', {
  t: Schemas.Float,
})

const RAY_INTERVAL = 0.1

// 检查射线
engine.addSystem((dt) => {
  for (const [entity] of engine.getEntitiesWith(TimerComponent)) {
    const timer = TimerComponent.getMutable(entity)
    timer.t += dt

    if (timer.t > RAY_INTERVAL) {
      timer.t = 0
      raycastSystem.registerGlobalDirectionRaycast(
        {
          entity: myEntity,
          opts: {
            queryType: RaycastQueryType.RQT_HIT_FIRST,
            direction: Vector3.Forward(),
            maxDistance: 16,
          },
        },
        function (raycastResult) {
          log(raycastResult.hits)
        }
      )
    }
  }
})

TimerComponent.create(engine.addEntity())

// 振荡立方体系统
engine.addSystem((dt) => {
  for (const [entity, cube] of engine.getEntitiesWith(
    CubeOscilator,
    Transform
  )) {
    CubeOscilator.getMutable(entity).t += dt
    Transform.getMutable(entity).position.y = 2 + Math.cos(cube.t)
  }
})

// 创建立方体
const cubeEntity = engine.addEntity()
Transform.create(cubeEntity, { position: { x: 8, y: 1, z: 8 } })
CubeOscilator.create(cubeEntity)
MeshRenderer.setBox(cubeEntity)
MeshCollider.setBox(cubeEntity)
```

上面的示例每 0.1 秒运行一次周期性射线检测。它使用一个计时器组件和一个系统的 `dt` 属性来均匀计时。它还包含一个上下振荡的立方体，由另一个系统控制，使其进出射线路径。

{% hint style="info" %}
**💡 提示**：使用 `interval` 函数，位于 [SDK Utils 库中](https://github.com/decentraland/sdk7-utils) ，可用更简单的方式以固定间隔运行一个函数。
{% endhint %}

## 通过系统执行射线检测

执行周期性射线检测的另一种方法，是在系统的循环函数内部运行它们。这使你能够对它们何时以及如何工作有更多控制。你可以不注册回调函数，而是通过 `raycastSystem.registerRaycast` 执行射线检测查询，然后在系统函数中检查该操作返回的数据。

请注意，由于射线检测是在系统中执行的，结果要到下一个 tick 才可用，因此需要运行两次系统：一次用于为下一帧注册射线检测，下一帧再处理其结果。

```ts
engine.addSystem((deltaTime) => {
		const result = raycastSystem.registerRaycast(
			myEntity,
			raycastSystem.localDirectionOptions({
				collisionMask: ColliderLayer.CL_CUSTOM1 | ColliderLayer.CL_CUSTOM3 | ColliderLayer.CL_POINTER,
				originOffset: Vector3.create(0, 0.4, 0),
				maxDistance: 16,
				queryType: RaycastQueryType.RQT_HIT_FIRST,
				direction: Vector3.Forward(),
				continuous: true // 不要过度使用 'continuous' 属性，因为射线检测在性能上代价很高
			})
		)
		if (result) {
			// 执行某些操作
		}
	})
```

## 与玩家碰撞

你可以通过在掩码中包含任意一个头像碰撞层，直接使用射线检测来检测头像：

* `ColliderLayer.CL_PLAYER`：匹配任意头像——本地玩家以及场景中渲染的任何其他玩家。
* `ColliderLayer.CL_MAIN_PLAYER`：仅匹配本地（主）玩家。

这两个层可以组合使用，也可以单独使用。射线检测的默认 `collisionMask` 为 `CL_PHYSICS`，这不会命中头像——你必须显式启用。

```ts
// 仅命中本地玩家（忽略其他头像）
raycastSystem.registerLocalDirectionRaycast(
  {
    entity: myEntity,
    opts: {
      direction: Vector3.Forward(),
      collisionMask: ColliderLayer.CL_MAIN_PLAYER,
    },
  },
  (raycastResult) => {
    if (raycastResult.hits.length > 0) {
      console.log('命中本地玩家')
    }
  }
)

// 命中任意头像（本地 + 远程）
raycastSystem.registerLocalDirectionRaycast(
  {
    entity: myEntity,
    opts: {
      direction: Vector3.Forward(),
      collisionMask: ColliderLayer.CL_PLAYER,
    },
  },
  (raycastResult) => {
    // 对远程头像命中来说，raycastResult.hits[i].entityId 为 0（没有场景本地实体 ID）
    console.log(raycastResult.hits)
  }
)
```

当射线检测命中本地玩家时， `hit.entityId` 为 `engine.PlayerEntity`。命中远程头像时，不会携带场景本地实体 ID（该字段为 `0`），因为远程玩家并不是你场景世界中的实体——但仍会报告其 `位置`, `length`, `normalHit`以及其他几何数据。

{% hint style="info" %}
**💡 提示**：若只检测 **其他** 玩家（排除本地玩家），请使用 `CL_PLAYER` 并在 `hit.entityId !== engine.PlayerEntity` 中进行过滤。
{% endhint %}

## 从玩家发射射线

若要从玩家位置沿着摄像机朝向追踪一条射线，你可以使用摄像机或头像来追踪一条射线 [保留实体](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/entities-components.md#reserved-entities).

{% hint style="info" %}
**💡 提示**：在大多数情况下，你可能更适合使用 [指针事件](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/click-events.md) 而不是射线检测。
{% endhint %}

以下示例使用 `engine.CameraEntity` 实体，从玩家摄像机位置向前追踪一条射线。

```ts
raycastSystem.registerGlobalDirectionRaycast(
  {
    entity: engine.CameraEntity,
    opts: {
      queryType: RaycastQueryType.RQT_HIT_FIRST,
      direction: Vector3.rotate(
        Vector3.Forward(),
        Transform.get(engine.CameraEntity).rotation
      ),
    },
  },
  function (raycastResult) {
    console.log(raycastResult)
  }
)
```

{% hint style="warning" %}
**📔 注意**：请记住，在第三人称中，光标在未来可能不会像第一人称中那样表现相同。建议仅在玩家处于第一人称时使用此方法。
{% endhint %}

## 从光标位置发射射线

你也可以从玩家的光标位置向 3D 世界中追踪一条射线。这可用于拖动物体、射击游戏等。

在这个示例中，我们检测玩家何时按下 E 键，然后从光标位置向 3D 世界中追踪一条射线。接着我们检查射线是否命中任何实体，如果命中了，就对其执行某些操作。

```ts
import { engine, Entity, InputAction, inputSystem, PointerEventType, RaycastQueryType, raycastSystem, TextShape, Transform } from '@dcl/sdk/ecs'
import { PrimaryPointerInfo } from '@dcl/sdk/ecs'

let cooldown = 1
let rayFrequency = 0.1
let mousePressed = false

export function main() {
   engine.addSystem(rayCastSystem)
}

const rayCastSystem = (t: number) => {

    if (inputSystem.isTriggered(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN)) {
      mousePressed = true
    }

    if (inputSystem.isTriggered(InputAction.IA_PRIMARY, PointerEventType.PET_UP)) {
      mousePressed = false
    }

    if (!mousePressed) {
      cooldown = 0
      raycastSystem.removeRaycasterEntity(engine.CameraEntity)
      return
    }

    cooldown += t
    if (cooldown < rayFrequency) return
    cooldown = 0

    const pointerInfo = PrimaryPointerInfo.getOrCreateMutable(engine.RootEntity)
    let dir = pointerInfo.worldRayDirection

    raycastSystem.registerGlobalDirectionRaycast(
      {
        entity: engine.CameraEntity,
        opts: {
          queryType: RaycastQueryType.RQT_HIT_FIRST,
          direction: dir,
        },
      },
      function (raycastResult) {
        let result = raycastResult.hits[0]

        // 在命中位置执行某些操作
        if (result && result.position) {
          console.log("x:", result.position.x, ", y:", result.position.y, ", z:", result.position.z)
        }

        // 对命中的实体执行某些操作
        const entity = result.entityId as Entity
        if (entity) {
          console.log("entity: ", entity)
        }
      }
    )
}

```

{% hint style="info" %}
**💡 提示**：在此示例中，我们使用主按钮（E）来触发射线检测。我们不使用指针按钮（左键单击），因为单击并拖动时默认也会改变摄像机角度。如果你想在拖动时阻止摄像机旋转，可以使用一个 [虚拟摄像机](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/camera.md) 来将摄像机角度设置为固定。
{% endhint %}

## 高级语法

### 创建射线检测组件

Raycast 组件描述了用于查询相交实体的不可见射线。射线从实体的位置开始追踪，该位置由 Transform 组件定义，并受到任何父实体的影响。方向可以通过多种方式定义，

射线使用以下数据定义：

* `方向`：一个对象，包含一个 `$case` 字段用于选择方向类型，以及一个取决于该类型的附加字段，用于确定这个方向。以下是可接受的 `$case`:
  * `“localDirection”`：相对于实体朝前方向的方向，也会受到任何父实体变换的影响。这对于检测遵循航向的车辆前方障碍物很有用。旋转由 `localDirection` 字段定义，为一个 `Vector3` ，用于描述旋转。
  * `“globalDirection”`：忽略实体的旋转，并像实体旋转为 0 一样朝向某个方向。这对于例如始终向下很有用。旋转由 `globalDirection` 字段定义，为一个 `Vector3` ，用于描述旋转。
  * `“globalTarget”`：在实体位置与场景中的目标全局位置之间追踪一条线。它会忽略实体的旋转。对于创建塔防游戏很有用，每座塔的炮塔都可以指向空间中的一个精确坐标。目标由 `globalTarget` 字段定义，为一个 `Vector3` 定义，它描述全局位置。
  * `“targetEntity”`：在实体位置与第二个目标实体的位置之间追踪一条线。它会忽略任一实体的旋转。目标由 `targetEntity` 字段定义，该字段持有实体的引用。
* `maxDistance`: *数值* 用于设置这条射线的追踪长度。
* `queryType`: *RaycastQueryType* 枚举值，用于定义射线是返回所有命中的实体还是只返回第一个。可用选项如下：
  * `RaycastQueryType.RQT_HIT_FIRST`：只返回第一个命中的实体，从起点开始计算。
  * `RaycastQueryType.RQT_QUERY_ALL`：返回所有命中的实体，从起点一直到射线的最大距离。
* `collisionMask`：仅检测某些碰撞层中的碰撞。可将其与自定义碰撞层一起使用，或仅检测物理层或指针事件层。参见 [碰撞层](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/colliders.md#collision-layers)。默认值为 `ColliderLayer.CL_POINTER | ColliderLayer.CL_PHYSICS`.
* `originOffset`：不从实体的起点位置开始射线检测，而是添加一个偏移量，从相对位置开始查询。例如，你可以使用一个小偏移，防止射线与实体自身的 3D 模型发生碰撞。
* `continuous`：如果为 true，则会在每一帧持续运行一次射线检测查询。如果为 false，则该射线只会在当前帧使用。默认情况下该值为 false。

{% hint style="warning" %}
**📔 注意**：该 `continuous` 属性应谨慎使用，因为在每一帧都运行射线检测查询在性能上可能代价很高。在可能的情况下，请使用一个系统（或 `interval` 函数，位于 Utils 库中）以较稀疏的固定间隔运行射线检测查询，参见 [周期性射线检测](#recurrent-raycasting).
{% endhint %}

下面的示例使用全局旋转来确定方向，并且只返回射线发出所在帧中被命中的第一个实体。

```typescript
const entity1 = engine.addEntity()

Transform.create(entity1, {
  position: Vector3.create(8, 1, 0)
})

Raycast.createOrReplace(entity1, {
  direction: {
    $case: "globalDirection",
    globalDirection: Vector3.create(0, 0, 1)
  },
  maxDistance: 16,
  queryType: RaycastQueryType.RQT_HIT_FIRST
})
```

下面的示例会沿实体的前向方向发射一条射线，只返回第一个命中的物体。它会持续执行。它还包含 0.5 的轻微偏移，以防止射线击中实体自身的碰撞体。

```typescript
const entity1 = engine.addEntity()

Transform.create(entity1, {
  position: Vector3.create(8, 1, 0)
})

Raycast.createOrReplace(entity1, {
  direction: {
    $case: "localDirection",
    localDirection: Vector3.Forward()
  },
  maxDistance: 16,
  queryType: RaycastQueryType.RQT_HIT_FIRST,
  originOffset: Vector3.create(0.5, 0, 0),
  continuous: true
})
```

这个示例在两个实体之间追踪一条射线。它会返回中间所有被击中的实体。

```ts
const entity1 = engine.addEntity()

Transform.create(entity1, {
  position: Vector3.create(8, 1, 0)
})

const entity2 = engine.addEntity()

Transform.create(entity2, {
  position: Vector3.create(0, 1, 8)
})

Raycast.createOrReplace(entity1, {
  direction: {
    $case: "targetEntity",
    targetEntity: entity2
  },
  maxDistance: 16,
  queryType: RaycastQueryType.RQT_QUERY_ALL
})
```

### Raycast 结果组件

{% hint style="warning" %}
**📔 注意**：处理射线检测结果最简单的方法是使用 `raycastSystem`，并在创建射线的同一语句中注册一个回调函数。`RaycastResult` 组件在该接口内部使用，但也被公开出来，以便实现更高级的自定义逻辑。
{% endhint %}

创建 Raycast 组件后，添加了该组件的实体将拥有一个 `RaycastResult` 组件。该组件包含有关射线任何命中的信息。请设置一个系统来检查这些数据。

该 `RaycastResult` 组件包含以下数据：

* `globalOrigin`：射线发射的起始位置，相对于场景。
* `方向`：射线所指向的全局方向，作为一个 `Vector3`.
* `hits`：一个数组，每个被命中的实体对应一个对象。如果没有命中的实体，这个数组为空。如果射线检测使用了 `RaycastQueryType.RQT_HIT_FIRST`，这个数组只会包含一个对象。

中的每个对象都包含： `hits` 数组

* `entityId`：被射线命中的实体的 ID 数字。
* `meshName`: *字符串* ，包含被命中的 3D 模型中具体网格的内部名称。当一个 3D 模型由多个网格组成时，这很有用。
* `位置`: *Vector3* 用于表示射线与命中实体相交的位置（相对于场景）
* `length`：射线从起点到命中实体发生位置的长度。
* `normalHit`: *Vector3* 用于表示世界空间中命中表面的法线。
* `globalOrigin`: *Vector3* 用于表示射线起点的位置（相对于场景）
* `方向`：射线所指向的全局方向，作为一个 `Vector3`.

下面的示例展示了如何使用一个系统从单个实体访问结果：

```typescript

const rayEntity = engine.addEntity()

Transform.create(rayEntity, {
  position: Vector3.create(8, 1, 0)
})

// 返回所有实体
Raycast.createOrReplace(rayEntity, {
  direction: {
    $case: "globalDirection",
    globalDirection: Vector3.create(0, 0, 1)
  },
  maxDistance: 16,
  queryType: RaycastQueryType.RQT_QUERY_ALL
})

engine.addSystem(() => {
  const rayResult = RaycastResult.get(rayEntity)
  console.log(rayResult.hits)
})
```

下一个示例展示了如何访问 `RaycastResult` 使用一个从场景中所有实体获取 [组件查询](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/querying-components.md).

```typescript
engine.addSystem(() => {
  for (const [_, result] of engine.getEntitiesWith(RaycastResult)) {
    console.log(result.hits)
  }
})
```

{% hint style="warning" %}
**📔 注意**：射线检测的结果不会在你创建射线检测的同一游戏循环 tick 中到达。结果可能需要一个或多个 tick 才会到达。
{% endhint %}

在一个场景中，如果你出于不同目的使用多种射线（例如路径查找、视线检查、投射物追踪等），你可能希望使用不同的 [碰撞层](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/colliders.md#collision-layers)，以避免计算无关的碰撞。

{% hint style="info" %}
**💡 提示**：有关射线检测的可运行示例，请参见 [`77,-1-raycast-unit-tests`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/77,-1-raycast-unit-tests) 测试场景，它通过 `@dcl/sdk/testing` 跨以下内容的单元测试 `localDirection`, `globalDirection`, `globalTarget` 和 `targetEntity` 方向形式，包括从已变换父级发出的射线。要了解射线可命中的碰撞体层如何表现，请参见 [`5,5-collider-layers`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/5,5-collider-layers).
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/raycasting.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
