> 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-he-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()`：创建一个具有 **局部** 方向的射线检测。 `方向` field 期望一个 `Vector3` ，它描述了相对于实体及其旋转的向量（例如 `Vector3.Forward()` 最终会使用实体 Transform 的前向向量）
* `raycastSystem.registerGlobalDirectionRaycast()`：创建一个具有 **global** 方向的射线检测。 `方向` field 期望一个 `Vector3` ，它描述全局方向。
* `raycastSystem.registerGlobalTargetRaycast()`：创建一个具有由 **全局目标** position `目标` field 期望一个 `Vector3` 定义的方向的射线检测。
* `raycastSystem.registerTargetEntityRaycast()`：创建一个具有朝向 **目标实体** position `targetEntity` 定义方向的射线检测。field 期望一个实体引用，该实体的位置将被用作射线的目标。

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

* `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`.
* `连续`：如果为 true，将在每一帧持续运行一次射线检测查询。如果为 false，射线只会在当前帧使用。如果未设置，默认值为 false。
* 当使用局部或全局方向设置方向时， `方向` 字段默认值为 `Vector3.Forward()`.
* 当使用全局目标设置方向时， `目标` 字段默认值为 `Vector3.Zero()`.
* 当使用实体目标设置方向时， `targetEntity` 字段默认指向场景的根实体，位置在 `Vector3.Zero()`.

{% hint style="warning" %}
**📔 注意**： `连续` 属性应谨慎使用，因为每一帧都运行射线检测查询可能会对性能造成很大开销。在可能的情况下，请使用系统（或 `interval` 函数，位于 Utils 库中）以更规律但更稀疏的间隔运行射线检测查询，参见 [重复射线检测](#recurrent-raycasting).
{% endhint %}

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

```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 entity ', hit.entityId)
        }
      }
    } else {
      console.log('no entities hit')
    }
  }
)
```

{% 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`的函数时，默认行为是创建一条单独的射线，只查询一次碰撞。作为替代，你可以将 `连续` 字段设置为 *真* ，以在游戏循环的每个 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" %}
**📔 注意**： `连续` 属性应谨慎使用，因为每一帧都运行射线检测查询可能会对性能造成很大开销。
{% 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 模型相撞。
* `连续`：如果为 true，将在每一帧持续运行一次射线检测查询。如果为 false，射线只会在当前帧使用。默认情况下此值为 false。

{% hint style="warning" %}
**📔 注意**： `连续` 属性应谨慎使用，因为每一帧都运行射线检测查询可能会对性能造成很大开销。在可能的情况下，请使用系统（或 `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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
