> 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/an-niu-shi-jian/click-events.md).

# 点击事件

了解如何处理场景中的用户点击。

Decentraland 场景可以检测来自用于控制玩家头像的所有按钮的输入操作。这些包括指针点击、若干动作按钮，以及用于让头像移动的按键。按钮事件可以来自桌面客户端上的鼠标和键盘、来自屏幕上的控制按钮， [移动应用](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/yi-dong-ke-hu-duan/overview.md)，来自 VR 控制器，或来自其他输入设备——SDK 会将它们都以相同方式解释。

你可以检测针对实体的输入操作。这包括在玩家光标指向该实体的碰撞体时按下按钮。你还可以检测 *global* 输入事件，即在任何时候按下并激活输入，而不考虑指针瞄准的位置。

{% hint style="warning" %}
**📔 注意**：实体必须具有一个 [碰撞体](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/colliders.md) 才能响应输入操作。 `MeshRenderer` 模型也必须配置一个 `MeshCollider` 组件。来自某个 `GltfContainer` 可能有其自身嵌入的碰撞几何体，或者可以配置为使用其可见几何体；它们还可以再添加一个 `MeshCollider` 组件。
{% endhint %}

根据使用场景，有几种不同的处理输入操作的方法。

* [**注册回调**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/register-callback.md)：为单个实体添加交互的最简单方式。只需一条语句即可设置回调函数和悬停反馈。
* [**基于系统的**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/system-based-events.md)：非常适合处理具有相似行为的多个实体。使用一个系统遍历这些相似实体，并分别查询每个实体的输入操作，用同一套逻辑来处理它们。悬停反馈需要单独设置。处理全局输入操作也必须使用这种方法。
* [**高级**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/advanced-button-events.md)：读取每个实体上的原始响应数据，包括时间戳和输入事件历史。这对于定义自定义交互模式很有用。

## 在 Creator Hub 中使用 Scene Editor

处理实体点击事件最简单的方法是使用场景编辑器。使用无代码的 **点击时** 或 **在输入操作时** 在物体上设置触发器，以便在点击它时调用动作。或者使用 **全局点击时**, **全局主操作时** 或 **全局次操作时** 触发器来响应全局按钮事件。参见 [让任意项目变为智能项目](/creator/content-creator-zh/chang-jing-bian-ji-qi/jiao-hu-xing/make-any-item-smart.md).

## 简单示例

要检测实体上的点击，请使用 `pointerEventsSystem.onPointerDown`.

```ts
pointerEventsSystem.onPointerDown(
	{
		entity: myEntity,
		opts: { button: InputAction.IA_PRIMARY, hoverText: '点击' },
	},
	function () {
		console.log('已点击实体')
	}
)
```

参见 [**注册回调**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/register-callback.md) 了解更多信息。

## 悬停反馈

让玩家知道某个实体是可交互的很重要。否则，他们可能会完全错过你构建的体验。总是点击每个物体、希望其中一个有反应，这种体验并不好。

当你使用 [**注册回调**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/register-callback.md) 方法时，只要玩家将光标移到物体上方，就会显示两种反馈：

* 实体边缘会被高亮（仅在 Decentraland 2.0 桌面客户端中）。如果实体足够近，可以点击时，高亮为绿色；如果实体太远，则为红色。
* 光标附近会出现一个悬停提示，带有 UI 文本，说明如果点击会发生什么。

当使用 [**基于系统的**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/system-based-events.md) 方法时，你可以通过添加一个 `PointerEvents` 组件到可点击实体上来实现相同效果。

实体高亮和悬停提示都可以通过这些方法和组件中的属性禁用。

你也可以实现 [高级自定义提示](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/system-based-events.md#advanced-custom-hints)，例如你可以播放声音，让实体在被指向时变色、旋转或放大等。无论你做什么，都要确保这是一个清晰的提示。

## 障碍物

按钮事件会发射射线，只会与路径上第一个订阅了指针事件碰撞层的实体交互。只要该实体在距离限制内，这一点就成立。如果实体同时为摄像机和头像定义了距离限制，那么两者中任意一个足够接近就够了。

要让实体被指针事件的射线拦截，必须满足以下任一条件：

* 模型必须包含 [碰撞体网格](/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).

如果另一个实体的碰撞体挡在玩家想要交互的实体前面，玩家将无法点击后面的实体，除非该实体没有碰撞体，或者这个碰撞体被配置为不响应指针事件碰撞层。

```ts
// 可点击实体
const clickableEntity = engine.addEntity()
MeshRenderer.setBox(clickableEntity)
MeshCollider.setBox(clickableEntity)
Transform.create(clickableEntity, { position: Vector3.create(8, 1, 8) })

pointerEventsSystem.onPointerDown(
	{
		entity: clickableEntity,
		opts: {
			button: InputAction.IA_POINTER,
			hoverText: '点击',
		},
	},
	function () {
		console.log('已点击实体')
		const t = Transform.getMutable(clickableEntity)
		t.scale.y += 0.2
	}
)

// 不阻挡点击
const nonBlocker = engine.addEntity()
MeshRenderer.setBox(nonBlocker)
MeshCollider.setBox(nonBlocker, ColliderLayer.CL_PHYSICS)
Transform.create(nonBlocker, { position: Vector3.create(10, 1, 8) })

// 阻挡点击
const blocker = engine.addEntity()
MeshRenderer.setBox(blocker)
MeshCollider.setBox(blocker, ColliderLayer.CL_POINTER)
Transform.create(blocker, { position: Vector3.create(8, 1, 10) })
```

{% hint style="warning" %}
**📔 注意**：要让实体不仅拦截指针事件，还能返回数据，该实体还需要有一个 `PointerEvents` 组件。 `pointerEventsSystem` 辅助函数也会负责处理这个要求。
{% endhint %}

## 指针按钮

以下输入都可以通过任意一种检测输入事件的方法来处理。

* `InputAction.IA_POINTER`: **鼠标左键** 在电脑上， **交互按钮** 在移动设备上。
* `InputAction.IA_PRIMARY`: **E** 在电脑上的按键， **E 按钮** 在移动设备上。
* `InputAction.IA_SECONDARY`: **F** 在电脑上的按键， **F 按钮** 在移动设备上。
* `InputAction.IA_ACTION_3`: **1** 在电脑上的按键， **1 号按钮** 在移动设备上。 *在移动设备上不易触及——不建议用于移动端交互。*
* `InputAction.IA_ACTION_4`: **2** 在电脑上的按键， **2 号按钮** 在移动设备上。 *在移动设备上不易触及——不建议用于移动端交互。*
* `InputAction.IA_ACTION_5`: **3** 在电脑上的按键， **3 号按钮** 在移动设备上。 *在移动设备上不易触及——不建议用于移动端交互。*
* `InputAction.IA_ACTION_6`: **4** 在电脑上的按键， **4 号按钮** 在移动设备上。 *在移动设备上不易触及——不建议用于移动端交互。*
* `InputAction.IA_JUMP`: **空格** 在电脑上的按键， **跳跃按钮** 在移动设备上。
* `InputAction.IA_FORWARD`: **W** 在电脑上的按键。
* `InputAction.IA_LEFT`: **一个** 在电脑上的按键。
* `InputAction.IA_RIGHT`: **D** 在电脑上的按键。
* `InputAction.IA_BACKWARD`: **S** 在电脑上的按键。
* `InputAction.IA_WALK`: **控制** 在电脑上的按键。
* `InputAction.IA_MODIFIER`: **Shift** 在电脑上的按键。

每个 `InputAction` 与键盘上的字面输入相分离，因此可以根据设备映射到不同的输入。出于同样的原因，并非键盘上的所有按键都能被追踪为按钮事件，只有用于移动和交互的按键可以。这个有意的限制确保内容在桌面端、 [移动端客户端](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/yi-dong-ke-hu-duan/overview.md)移动端，以及未来的设备，如 VR 控制器和其他游戏手柄之间都兼容。

{% hint style="warning" %}
**📱 移动端**：在 [移动端客户端](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/yi-dong-ke-hu-duan/overview.md)，所有输入操作都可用，但 `IA_ACTION_3`–`IA_ACTION_6` （桌面端的 `1`/`2`/`3`/`4` （按钮）被收在二级菜单后面，在游戏过程中不容易触及。为移动端设计时，优先使用 `IA_POINTER` （交互按钮）， `IA_PRIMARY` （E 键），和 `IA_SECONDARY` （F 键）作为你的关键操作。参见 [移动端输入](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/kai-fa/input-on-mobile.md).
{% endhint %}

## 指针事件的类型

每种输入都可能产生以下类型的指针事件。以下各项都是 PointerEventType 枚举中的一个值 `PointerEventType` 枚举中的值。

* `PET_DOWN`：玩家在光标指向实体的碰撞体时按下某个特定按钮。
* `PET_UP`：玩家在光标指向实体的碰撞体时松开某个特定按钮。
* `PET_HOVER_ENTER`：玩家的光标开始指向实体的碰撞体。
* `PET_HOVER_LEAVE`：玩家的光标停止指向实体的碰撞体。
* `PET_PROXIMITY_ENTER`：玩家走入实体的接近范围，不管他们看向哪里。
* `PET_PROXIMITY_LEAVE`：玩家离开实体的接近范围。

参见 [**接近事件**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/proximity-events.md) 了解如何使用基于接近范围的交互。

## 输入操作的数据

所有输入操作都包含有关事件的数据，例如被激活的按钮，以及当时指针指向的位置。

可从任何输入事件中获取以下信息：

* `analog`: 可选数字，仅存在于来自模拟输入（如摇杆）的事件中，用于存储输入的模拟值。
* `按钮连接您的钱包`: 按下的是哪个按钮 ID。该数字对应于 `InputAction` 枚举，该枚举列出了所有可用按钮。
* `state`: 指针事件类型，来自枚举 `PointerEventType`. *0* 指的是 `PointerEventType.PET_UP`, *1* 为 `PointerEventType.PET_DOWN`, *2* 为 `PointerEventType.PET_HOVER_ENTER`, *3* 为 `PointerEventType.PET_HOVER_LEAVE`, *4* 为 `PointerEventType.PET_PROXIMITY_ENTER`, *5* 为 `PointerEventType.PET_PROXIMITY_LEAVE`
* `时间戳`: 一个 [Lamport 时间戳](https://en.wikipedia.org/wiki/Lamport_timestamp) 以识别每个按钮事件。

  > 注意：此时间戳并不是基于当前时间编号的。可以把它看作一个从 0 开始的计数器，每发生一个事件就加 1。
* `命中`: 一个对象，其中包含有关命中事件的以下数据：
  * `entityId`：被射线命中的实体的 ID 编号。
  * `meshName`: *字符串* ，表示 3D 模型中被命中的具体网格的内部名称。当 3D 模型由多个网格组成时，这很有用。
  * `globalOrigin`: *Vector3* ，表示射线起始的位置（相对于场景）
  * `方向`: *Vector3* 与射线的方向向量一起，采用全局坐标
  * `位置`: *Vector3* ，表示射线与命中的实体相交的位置（相对于场景）
  * `length`：射线从起点到与实体发生命中的位置之间的长度。
  * `normalHit`: *Vector3* 以及使用归一化方向向量，描述世界空间中命中法线的角度。

根据你处理输入操作所采用的方法，访问这些数据的方式也不同。

使用 [**注册回调**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/register-callback.md) 方法时，传递给回调函数的第一个参数包含整个数据结构。

```ts
pointerEventsSystem.onPointerDown({ entity: myEntity }, function (cmd) {
	console.log(cmd.hit?.entityId)
})
```

使用 [**基于系统的**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/system-based-events.md) 方法时，请使用 `inputSystem.getInputCommand()` 来获取这些数据。

```ts
engine.addSystem(() => {
	const cmd = inputSystem.getInputCommand(
		InputAction.IA_POINTER,
		PointerEventType.PET_DOWN,
		myEntity
	)
	if (cmd) {
		console.log(cmd.hit?.entityId)
	}
})
```

{% hint style="warning" %}
**📔 注意**：要让实体不仅拦截指针事件，还能返回数据，该实体还需要有一个 `PointerEvents` 组件。 `pointerEventsSystem` 辅助函数也会负责处理这个要求。
{% endhint %}

使用 [**高级**](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/advanced-button-events.md) 方法中， `PointerEventsResult` 包含一个列表，记录了针对该实体的所有指针事件的最近历史。

```ts
engine.addSystem(() => {
	for (const [entity] of engine.getEntitiesWith(PointerEventsResult)) {
		const pointerEvents = PointerEventsResult.get(entity)

		for (const event of pointerEvents) {
			console.log(event.hit?.entityId)
		}
	}
})
```

## 锁定或解锁光标

{% hint style="info" %}
**💡 注意**: `PointerLock` 是桌面客户端的一个概念（锁定 vs. 解锁鼠标光标）。它对 [移动应用](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/yi-dong-ke-hu-duan/overview.md)移动端没有影响，因为那里的输入基于触摸。
{% endhint %}

Decentraland 的桌面客户端玩家可以通过光标在两种不同的交互模式之间切换：

* 锁定光标：移动鼠标会转动摄像机，点击事件会发生在屏幕中央的准星处
* 解锁光标：光标可独立于摄像机自由移动，玩家可以点击屏幕上的任意位置，与 3D 空间或 UI 交互

这些交互模式适用于不同类型的游戏机制。你可能希望让玩家根据场景最适合的方式，在两者之间采用其中一种。你也可能想暂时确保玩家的光标处于解锁状态，以便他们与 UI 交互；这对不熟悉如何操作的新玩家尤其有帮助。

要更改玩家的光标状态，请使用 `PointerLock` 组件在 `engine.CameraEntity` 实体，从玩家摄像机位置向前追踪一条射线。

```ts
import {PointerLock} from '@dcl/sdk/ecs'
    
PointerLock.createOrReplace(engine.CameraEntity, {isPointerLocked: false})
```

{% hint style="warning" %}
**📔 注意**：引擎默认提供一个 `PointerLock` 组件在 `engine.CameraEntity` 。使用 `createOrReplace()` 或 `getMutable()` 而不是 `create()`，如果该组件已存在则会抛出错误。
{% endhint %}

你也可以通过读取 `PointerLock` 组件的状态来查询玩家的光标状态。

```ts
import {PointerLock} from '@dcl/sdk/ecs'

const isPointerLocked = PointerLock.getOrNull(engine.CameraEntity)?.isPointerLocked
```

使用 `.onChange` 函数来响应光标状态的变化。下面的示例强制光标始终处于解锁状态。

```ts
import {PointerLock} from '@dcl/sdk/ecs'

export function main() {

    PointerLock.createOrReplace(engine.CameraEntity, {isPointerLocked: false});

    PointerLock.onChange(engine.CameraEntity, (pointerLock) => {
		    if (!pointerLock) return
		    if(pointerLock.isPointerLocked){
			    PointerLock.getMutable(engine.CameraEntity).isPointerLocked = false
		   }
	})
}
```


---

# 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/an-niu-shi-jian/click-events.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.
