> 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`

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

* `挥手`
* `fistpump`
* `robot`
* `raiseHand`
* `鼓掌`
* `money`
* `亲吻`
* `tik`
* `hammer`
* `tektonik`
* `dontsee`
* `handsair`
* `shrug`
* `disco`
* `dab`
* `headexplode`

{% 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="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` 组件上报。每个新条目都带有一个 `state` 字段，其值来自 `EmoteState` 枚举中使用：

* `EmoteState.ES_STARTED`：表情动作开始播放。当 `state` 字段缺失时（由较旧客户端写入的条目），报告的值也是如此。
* `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.
