> 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/tou-xiang/avatar-animations.md).

# 头像动画

让玩家的头像播放默认或自定义动画，并检测某个表情动作何时结束

你可以作为场景代码的一部分，让玩家执行动画。这有助于增强沉浸感，也有助于让其他玩家彼此了解对方在做什么。化身动画会同时被玩家（第三人称视角）和周围的其他玩家看到。

作用于全身的玩家控制动画会被默认的移动动画覆盖，比如行走和跳跃。因此，场景播放的全身动画只会在玩家静止时播放。如果玩家走动或跳跃，任何全身动画都会被打断。上半身动画不会被移动打断。

{% hint style="warning" %}
**📔 注意**: 只有当玩家已经站在场景边界内时，才能对其播放动画；如果他们位于相邻场景中，则不行。智能穿戴物可以在任何地方播放动画。

当玩家正在执行动画时，不会受碰撞影响，其移动也不受场景物理规则限制。另请注意，如果动画使玩家偏离其初始位置（例如动画包含跳跃），玩家的 Transform 组件不会受到这种位移影响。
{% endhint %}

### 使用场景编辑器

让玩家执行动画的最简单方法是使用场景编辑器。使用无代码 **播放表情动作** 操作来播放默认动画，或者使用 **播放自定义表情动作** 操作来播放文件中的动画。参见 [让任意物品变得智能](/creator/content-creator-zh/chang-jing-bian-ji-qi/jiao-hu-xing/make-any-item-smart.md).

### 默认动画

使用 `triggerEmote()` 函数，用于运行玩家可以在 Decentraland 任意地方播放的某个默认动画。该函数接受一个带有以下属性的对象作为参数：

* `predefinedEmote`：已有表情动作的字符串名称。
* `mask`： （可选）使用来自 `AvatarMask` 枚举的值，只在化身身体的一部分上播放动画。例如， `AvatarMask.AM_UPPER_BODY` 只会让化身上半身动起来。参见 [仅对上半身播放动画](#animate-only-the-upper-body).

```ts
import { triggerEmote } from '~system/RestrictedActions'

const emoter = engine.addEntity()
Transform.create(emoter, { position: Vector3.create(8, 0, 8) })
MeshRenderer.setBox(emoter)
MeshCollider.setBox(emoter)
pointerEventsSystem.onPointerDown(
	{
		entity: emoter,
		opts: { button: InputAction.IA_POINTER, hoverText: '跳舞' },
	},
	() => {
		triggerEmote({ predefinedEmote: 'robot' })
	}
)
```

下列表情动作会在你的场景中反馈玩家操作，以下所有值都可用于 `predefinedEmote` 字段：

* `buttonDown`
* `buttonFront`
* `getHit`
* `knockOut`
* `lever`
* `openChest`
* `openDoor`
* `punch`
* `push`
* `swingWeaponOneHand`
* `swingWeaponTwoHands`
* `throw`
* `sittingChair1`
* `sittingChair2`
* `sittingGround1`
* `sittingGround2`

四个 `坐姿` 表情动作会循环播放：化身会保持该姿势，直到玩家移动或你的场景调用 `stopEmote({})`。其他所有表情动作都会播放一遍，然后化身返回待机状态。

这些表情动作默认都可在所有玩家的表情轮盘中使用，也可用于任何场景。

* `wave`
* `fistpump`
* `robot`
* `raiseHand`
* `clap`
* `money`
* `kiss`
* `tik`
* `hammer`
* `tektonik`
* `dontsee`
* `handsair`
* `shrug`
* `disco`
* `dab`
* `headexplode`
* `crafting`
* `handsintheair`
* `Victory`
* `Waving`
* `fistpump_short`

{% hint style="warning" %}
**📔 注意**: 请严格使用这些名称。名称错误时，场景会静默失败：不会播放任何内容，也不会在场景控制台中显示任何内容。请注意 `handsintheair` 没有分隔符的名称，以及 `fistpump_short` 带有其后缀的名称。
{% endhint %}

{% hint style="info" %}
**💡 提示**: 如果玩家在播放动画时行走或跳跃，就会将其打断。如果你不希望这样发生，可以在化身动画持续期间使用 [输入修饰器](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/tou-xiang/locomotion.md#freeze-the-player) 将化身冻结。
{% endhint %}

### 自定义动画

使用 `triggerSceneEmote()` 让玩家执行自定义动画，作为场景资源的一部分存储为 .glb 文件。

{% hint style="info" %}
**💡 提示**：你不必自己制作这些动画。Scene Editor 中的 **头像动画** 资源包包含 45 个现成动画，包括坐姿、钓鱼、搬箱子和跳舞。将其中一个拖到 3D 视图中，编辑器会把其文件复制到你的场景中，然后把该文件路径传给 `triggerSceneEmote()`。文件会放在 `assets/asset-packs/<animation-folder>/`下，例如 `assets/asset-packs/whack/Whack_Avatar_emote.glb`。请检查准确路径 **本地资源**。参见 [添加物品](/creator/content-creator-zh/chang-jing-bian-ji-qi/ru-men/about-editor.md#avatar-animations).
{% endhint %}

{% hint style="warning" %}
**📔 注意**文件名 **必须** 以 `_emote.glb` 结尾，才能作为化身动画使用。
{% endhint %}

该函数接受一个带有以下属性的对象：

* `src`：指向表情动作文件的路径字符串，相对于你项目的根文件夹。它必须与文件在磁盘上的实际位置完全一致。路径错误会静默失败：不会播放任何内容，也不会有任何日志输出，所以动画不显示时应先检查这里。
* `loop`：如果为 true，动画会持续循环播放，直到玩家移动或动画被停止。默认值为 false。
* `mask`： （可选）使用来自 `AvatarMask` 枚举的值，只在化身身体的一部分上播放动画。例如， `AvatarMask.AM_UPPER_BODY` 只会让化身上半身动起来。参见 [仅对上半身播放动画](#animate-only-the-upper-body).

```ts
import { triggerSceneEmote } from '~system/RestrictedActions'
import { AvatarMask } from '@dcl/sdk/ecs'

const emoter = engine.addEntity()
Transform.create(emoter, { position: Vector3.create(8, 0, 8) })
MeshRenderer.setBox(emoter)
MeshCollider.setBox(emoter)
pointerEventsSystem.onPointerDown(
	{
		entity: emoter,
		opts: { button: InputAction.IA_POINTER, hoverText: '制作雪球' },
	},
	() => {
		triggerSceneEmote({ src: 'animations/Snowball_Throw_emote.glb', loop: false, mask: AvatarMask.AM_UPPER_BODY })
	}
)
```

{% hint style="info" %}
**💡 提示**: 如果玩家在播放动画时行走或跳跃，就会将其打断。如果你不希望这样发生，可以在化身动画持续期间使用 [输入修饰器](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/tou-xiang/locomotion.md#freeze-the-player) 将化身冻结。
{% endhint %}

### 仅对上半身播放动画

两者 `triggerEmote()` 并 `triggerSceneEmote()` 都接受一个可选的 `mask` 属性，用于将动画限制在化身身体的某一部分。

通常情况下，动画会在玩家一行走或一跳跃时立刻停止，因为默认的移动动画会接管整个身体。当你设置 `mask` 或将 `AvatarMask.AM_UPPER_BODY`时，动画只会驱动化身腰部以上，而腿部仍由默认移动动画控制。这意味着玩家可以在场景中继续行走或奔跑，同时上半身播放你的动画。可将其用于不应中断移动的动作，例如搬箱子、拿火把或杂耍。

```ts
import { triggerSceneEmote } from '~system/RestrictedActions'
import {
	engine,
	AvatarMask,
	InputAction,
	MeshCollider,
	MeshRenderer,
	pointerEventsSystem,
	Transform,
} from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const cheerButton = engine.addEntity()
Transform.create(cheerButton, { position: Vector3.create(8, 0, 8) })
MeshRenderer.setBox(cheerButton)
MeshCollider.setBox(cheerButton)
pointerEventsSystem.onPointerDown(
	{
		entity: cheerButton,
		opts: { button: InputAction.IA_POINTER, hoverText: '欢呼' },
	},
	() => {
		triggerSceneEmote({
			src: 'animations/Cheer_emote.glb',
			loop: true,
			mask: AvatarMask.AM_UPPER_BODY,
		})
	}
)
```

在这个例子中，玩家点击一个按钮，并开始举着双臂欢呼，同时动画会循环播放。由于只有上半身在播放动画，他们在欢呼时仍然可以在场景中继续跑动，例如跟上比赛或足球赛中的行动。

需要注意几点：

* `AvatarMask.AM_UPPER_BODY` 目前是 `AvatarMask` 枚举中的唯一值。要播放全身动画，只需不要设置 `mask` 属性定义。
* 这个 `loop` 属性，其行为与全身动画相同：在 `loop: false` 时，带遮罩的动画会播放一次，然后上半身恢复正常移动；在 `loop: true` 时，它会一直重复，直到停止。
* 要从代码中停止循环的带遮罩动画，请调用 `stopEmote({})`，它也从 `~system/RestrictedActions`.

### 检测表情动作何时结束

每个表情动作生命周期事件都会通过 `AvatarEmoteCommand` 组件报告到玩家实体上。每条新记录都带有一个 `状态` 字段，其值来自 `EmoteState` 枚举：

* `EmoteState.ES_STARTED`：表情动作开始播放。即使当 `状态` 字段缺失时，这也是报告的值。
* `EmoteState.ES_FINISHED`：非循环表情动作已自然播放完毕。
* `EmoteState.ES_INTERRUPTED`：表情动作被中断：玩家移动或跳跃、传送、开始了另一个表情动作、显式停止了该表情动作，或者玩家离开了场景。

这同样适用于场景触发的表情动作（`triggerEmote()` 并 `triggerSceneEmote()`）以及玩家通过表情轮盘自行播放的表情动作，也适用于场景中其他玩家播放的表情动作。

使用 `onChange` 函数位于 `AvatarEmoteCommand` 组件，以响应每条新记录：

```ts
import { AvatarEmoteCommand, EmoteState } from '@dcl/sdk/ecs'

export function main() {
	AvatarEmoteCommand.onChange(engine.PlayerEntity, (emote) => {
		if (!emote) return

		switch (emote.state ?? EmoteState.ES_STARTED) {
			case EmoteState.ES_STARTED:
				console.log('表情动作开始：', emote.emoteUrn)
				break
			case EmoteState.ES_FINISHED:
				console.log('表情动作自然结束：', emote.emoteUrn)
				break
			case EmoteState.ES_INTERRUPTED:
				console.log('表情动作被中断：', emote.emoteUrn)
				break
		}
	})
}
```

{% hint style="warning" %}
**📔 注意**: 此功能目前仅在桌面客户端中受支持。使用 `mask` 在本地玩家上播放的表情动作（部分身体表情动作）目前不会报告生命周期事件。
{% endhint %}

{% hint style="info" %}
**💡 提示**：要在非玩家角色的化身上播放动画，请参见 [NPC 头像](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/tou-xiang/npc-avatars.md#animations).
{% 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/tou-xiang/avatar-animations.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.
