> 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/mouse-movement.md).

# 鼠标移动

实时响应玩家的鼠标移动，以驱动拖拽手势、旋转物体或控制自定义相机。

你的场景可以在每一帧读取玩家原始的鼠标移动，并用它来驱动实时交互：拖拽和滑动手势、在玩家拖动对象时让对象旋转或滑动，或者像第一人称射击游戏那样控制自定义摄像机。

为此，请读取 `screenDelta` 属性从 `PrimaryPointerInfo` 组件上的 `engine.RootEntity`. 这个属性是一个 `Vector2` ，它会报告鼠标自上一帧以来移动了多少像素。正的 `x` 值表示鼠标向右移动，正的 `y` 值表示它向上移动，这与屏幕原点位于左下角相对应。在鼠标不移动的帧中，这两个值都为 0。

```ts
import { engine, PrimaryPointerInfo } from '@dcl/sdk/ecs'

engine.addSystem(() => {
	const delta = PrimaryPointerInfo.getOrNull(engine.RootEntity)?.screenDelta
	if (!delta) return
	console.log(`mouse moved: ${delta.x}, ${delta.y}`)
})
```

由于 `screenDelta` 只保存单帧的移动量，所以一定要在 [系统](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/systems.md)中读取它，这样帧与帧之间的任何移动都不会被遗漏。

{% hint style="warning" %}
**📔 注意**：请避免在 `engine.RootEntity` 初始场景加载时引用
{% endhint %}

{% hint style="warning" %}
**📔 注意**：本文档中描述的内容只与桌面端玩家相关。在 [移动应用](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/yi-dong-ke-hu-duan/overview.md)上，输入基于触控，并且没有自由移动的光标，因此 `screenDelta` 始终报告 0。有关触控设备上的输入工作方式，请参阅 [移动端输入](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/kai-fa/input-on-mobile.md) ，以及 [检测平台](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/kai-fa/detect-platform.md) ，为移动端玩家提供替代控制。
{% endhint %}

## 光标锁定时的鼠标移动

`screenDelta` 与 `PrimaryPointerInfo` 的其他属性表现不同， [锁定](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/click-events.md#lock-or-unlock-the-cursor): `screenCoordinates` 会冻结在屏幕中心，而 `worldRayDirection` 始终报告屏幕中心的射线，但 `screenDelta` 会在每一帧持续报告原始鼠标移动。这使它成为光标锁定时读取鼠标移动的唯一方式，而这正是你实现自定义摄像机或瞄准控制所需要的。

## 拖动旋转对象

下面的示例让玩家在按住指针按钮时横向拖动鼠标来旋转一个立方体：

```ts
import {
	engine,
	InputAction,
	inputSystem,
	MeshCollider,
	MeshRenderer,
	PrimaryPointerInfo,
	Transform,
} from '@dcl/sdk/ecs'
import { Quaternion, Vector3 } from '@dcl/sdk/math'

const DRAG_SENSITIVITY = 0.5

export function main() {
	const cube = engine.addEntity()
	Transform.create(cube, { position: Vector3.create(8, 1, 8) })
	MeshRenderer.setBox(cube)
	MeshCollider.setBox(cube)

	engine.addSystem(() => {
		// 仅在按住指针按钮时旋转
		if (!inputSystem.isPressed(InputAction.IA_POINTER)) return

		const delta = PrimaryPointerInfo.getOrNull(engine.RootEntity)?.screenDelta
		if (!delta || delta.x === 0) return

		const transform = Transform.getMutable(cube)
		transform.rotation = Quaternion.multiply(
			transform.rotation,
			Quaternion.fromEulerDegrees(0, delta.x * DRAG_SENSITIVITY, 0)
		)
	})
}
```

同样的模式也适用于任何拖拽交互：使用 `delta.x` 作为偏移量沿着轨道滑动实体、拖动播放动画，或者通过检查较大的 delta 值来检测快速滑动。

## 鼠标视角摄像机控制

因为 `screenDelta` 在光标锁定时仍然可用，所以你可以用它来为一个 [虚拟摄像机](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/camera.md#using-virtual-cameras)构建鼠标视角控制。为此，将鼠标移动累积到偏航角和俯仰角中，然后在每一帧将它们应用到摄像机的旋转上。

在下面的示例中，玩家点击一个方块以切换到一个可用鼠标控制的虚拟摄像机，并按下次要按钮（*F* 或右键单击）返回默认摄像机。在虚拟摄像机处于活动状态时，场景还会锁定光标，并通过一个 [输入修饰器](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/player-avatar.md#freeze-the-player)冻结头像，这样玩家在控制摄像机时就不会盲目走动。

```ts
import {
	engine,
	Entity,
	InputAction,
	InputModifier,
	inputSystem,
	MainCamera,
	MeshCollider,
	MeshRenderer,
	PointerEventType,
	pointerEventsSystem,
	PointerLock,
	PrimaryPointerInfo,
	Transform,
	VirtualCamera,
} from '@dcl/sdk/ecs'
import { Quaternion, Vector3 } from '@dcl/sdk/math'

// 每像素鼠标移动对应的摄像机旋转角度
const SENSITIVITY = 0.15

let cameraEntity: Entity
let cameraActive = false
let yaw = 0
let pitch = 0

export function main() {
	// 摄像机跟随的实体
	cameraEntity = engine.addEntity()
	Transform.create(cameraEntity, { position: Vector3.create(8, 3, 8) })
	VirtualCamera.create(cameraEntity, {
		defaultTransition: { transitionMode: VirtualCamera.Transition.Time(0.5) },
	})

	// 点击这个方块进入鼠标视角模式
	const box = engine.addEntity()
	Transform.create(box, { position: Vector3.create(8, 1, 4) })
	MeshRenderer.setBox(box)
	MeshCollider.setBox(box)
	pointerEventsSystem.onPointerDown(
		{
			entity: box,
			opts: { button: InputAction.IA_POINTER, hoverText: '控制摄像机' },
		},
		() => activateCamera(true)
	)

	// 用鼠标控制摄像机
	engine.addSystem(() => {
		if (!cameraActive) return
		if (!PointerLock.getOrNull(engine.CameraEntity)?.isPointerLocked) return

		const delta = PrimaryPointerInfo.getOrNull(engine.RootEntity)?.screenDelta
		if (!delta) return

		yaw += delta.x * SENSITIVITY
		// 限制俯仰角，避免摄像机翻转
		pitch = Math.max(-85, Math.min(85, pitch - delta.y * SENSITIVITY))

		Transform.getMutable(cameraEntity).rotation = Quaternion.fromEulerDegrees(pitch, yaw, 0)
	})

	// 使用次要按钮退出鼠标视角模式
	engine.addSystem(() => {
		if (!cameraActive) return
		if (inputSystem.isTriggered(InputAction.IA_SECONDARY, PointerEventType.PET_DOWN)) {
			activateCamera(false)
		}
	})
}

function activateCamera(active: boolean) {
	cameraActive = active

	// 分配或释放虚拟摄像机
	MainCamera.createOrReplace(engine.CameraEntity, {
		virtualCameraEntity: active ? cameraEntity : undefined,
	})

	// 在控制摄像机时冻结头像
	InputModifier.createOrReplace(engine.PlayerEntity, {
		mode: InputModifier.Mode.Standard({ disableAll: active }),
	})

	// 锁定光标，这样鼠标就能立即控制摄像机
	PointerLock.createOrReplace(engine.CameraEntity, { isPointerLocked: active })
}
```

关于此示例，有几点需要注意：

* 系统会减去 `delta.y` 从俯仰角中减去它，因此向上移动鼠标会让摄像机向上倾斜。如果你更喜欢反向的垂直控制，可以把这个符号反过来。
* 俯仰角被限制在 -85 到 85 度之间，因此摄像机永远不会向后翻转。
* 该 `SENSITIVITY` 这个常量表示每像素鼠标移动对应的旋转角度，可按需调整。

{% hint style="info" %}
**💡 提示**：玩家可以随时通过按下 *Esc* 或右键单击来解锁光标，这会停止摄像机对鼠标的响应。务必为玩家提供一种明确的方式完全退出摄像机模式，就像上面示例中的次要按钮一样。你也可以在代码中重新锁定光标，参见 [锁定或解锁光标](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/an-niu-shi-jian/click-events.md#lock-or-unlock-the-cursor).
{% endhint %}

## 相关主题

要读取光标在屏幕上的绝对位置而不是它的移动量，请使用 `screenCoordinates` 该组件中的同名属性。参见 [检查玩家的光标位置](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/user-data.md#check-the-players-cursor-position).

要找出光标下方是哪一个实体，请将 `worldRayDirection` 属性与射线投射结合使用。参见 [光线投射](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/raycasting.md).

{% hint style="info" %}
**💡 提示**：有关指针状态的可运行示例，请参阅 [`0,5-primary-cursor-info`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,5-primary-cursor-info) 测试场景，它会在每一帧读取 `PrimaryPointerInfo` 并将其送入 `worldRayDirection` 一个射线投射； [`31,20-pointer-lock-control`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/31,20-pointer-lock-control)，它通过写入来请求和释放光标捕获 `PointerLock.isPointerLocked`； [`32,20-virtual-camera-mouse-look`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/32,20-virtual-camera-mouse-look)，它展示了 `screenDelta` 在 `screenCoordinates` 锁定期间仍在报告移动，而
{% 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/mouse-movement.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.
