> 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/3d-nei-rong-ji-chu/3d-model-animations.md).

# 3D 模型动画

如何为场景中的 3D 模型制作动画

3D 模型中的 *.glTF* 和 *.glb* 格式可以包含任意数量的动画。动画通过指定一系列 *关键帧* 这些关键帧按时间排列，网格随后从一个姿势过渡到另一个姿势，以模拟连续运动。

大多数 3D 模型动画是 [*骨骼动画*](https://en.wikipedia.org/wiki/Skeletal_animation)。这些动画将模型的复杂几何结构简化为“火柴人”骨架，把网格中的每个顶点都连接到最近的 *骨骼* 在 *骨架*。建模者将骨架调整到不同姿势，网格会随之拉伸和弯曲以跟随这些动作。

作为替代， *顶点动画* 无需骨架即可为模型添加动画。这些动画直接指定模型中每个顶点的位置。Decentraland 也支持这些动画。

参见 [动画](https://github.com/decentraland/docs/blob/main/creator/3d-modeling/animations.md) ，了解如何为 3D 模型创建动画的详细信息。阅读 [Shape 组件](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/shape-components.md) ，了解如何将 3D 模型导入场景的说明。

{% hint style="info" %}
**💡 提示**：动画通常更适合让某个对象在原地移动，而不是改变实体的位置。例如，你可以设置一个动画让角色的脚在原地摆动，但如果要改变实体的位置，最好使用 Transform 组件。参见 [放置实体](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/move-entities.md) ，了解更多详情。
{% endhint %}

## 检查 3D 模型是否包含动画

并非所有 *glTF* 文件都包含动画。要查看是否有可用动画，你可以执行以下操作：

* 如果使用 [VS Code](https://code.visualstudio.com/)（推荐），请安装 *GLTF Tools* 扩展，并在其中查看 glTF 文件的内容。
* 打开 [Babylon Sandbox](https://sandbox.babylonjs.com/) 网站，并将 glTF 文件（以及任何 *.jpg* 或 *.bin* 依赖项）拖到浏览器中。
* 打开 *.glTF* 文件，用文本编辑器打开并向下滚动，直到找到 *"animations":*.

{% hint style="info" %}
**💡 提示**: 在 *骨骼* 动画中，动画名称通常由骨架名称、下划线和动画名称组成。例如 `myArmature_animation1`.
{% endhint %}

## 自动播放

如果 3D 模型包含任何动画，默认行为是始终循环播放其中第一个动画。

要避免这种行为，请添加一个 `Animator` 组件到包含该模型的实体中，然后显式地处理动画播放。如果 `Animator` 组件存在于该实体中，则所有动画默认处于 `playing: false` 状态，需要手动播放。

{% hint style="info" %}
**💡 提示**: 在 [场景编辑器](/creator/content-creator-zh/chang-jing-bian-ji-qi/ru-men/about-editor.md)中，你可以可视化地添加一个 **Animator** 组件。参见 [添加组件](/creator/content-creator-zh/chang-jing-bian-ji-qi/gou-jian/components.md#add-components)。你也可以通过 **动作**，参见 [让任意物体变得智能](/creator/content-creator-zh/chang-jing-bian-ji-qi/jiao-hu-xing/make-any-item-smart.md).
{% endhint %}

## 显式处理动画

一个 `Animator` 组件用于访问实体的所有动画，并可显式告知实体播放或停止某个动画。 `Animator` 组件包含一个 `states`数组，此列表必须为 3D 模型可执行的每个动画包含一个对象。单个 `Animator` 可以包含任意数量的状态。

```ts
// 创建实体
const shark = engine.addEntity()

// 向其中添加一个 3D 模型
GltfContainer.create(shark, {
	src: 'models/shark.glb',
})

Animator.create(shark, {
	states: [
		{
			clip: 'swim',
			playing: true,
			loop: true,
		},
	],
})
```

每个 `状态` 对象都会跟踪某个动画当前是否正在播放。

{% hint style="warning" %}
**📔 注意**: `Animator` 组件必须通过以下方式导入：

> `import { Animator } from "@dcl/sdk/ecs"`

参见 [导入](/creator/content-creator-zh/chang-jing-sdk7/ru-men/coding-scenes.md#imports) ，了解如何轻松处理这些内容。
{% endhint %}

## 获取动画

通过以下方式从 `Animator` 中按名称获取一个片段，使用 `.Animator.getClip()` 函数。此函数返回动画状态对象的可变版本。

```ts
const swimAnim = Animator.getClip(sharkEntity, 'swim')
```

`Animator.getClip` 需要以下参数：

* `实体`：你要查询的 `Animator` 组件所属的实体。
* `clipName`：要获取的片段名称字符串。

`Animator.getClip` 会获取动画状态的可变版本，因此你可以自由修改该函数返回的值。

```ts
const swimAnim = Animator.getClip(sharkEntity, 'swim')
swimAnim.loop = false
```

{% hint style="warning" %}
**📔 注意**：如果你尝试使用 `Animator.getClip()` 去获取一个未列在 `Animator` 组件中的片段，它会抛出错误。请使用 `Animator.getClipOrNull()` 如果你希望在这种情况下获得 `null` 响应，而不是错误。
{% endhint %}

## 播放动画

动画状态中的 `.playing` 字段决定当前动画是否正在播放。请注意，单个 3D 模型可以同时播放多个动画。

在 `Animator.playSingleAnimation()` 函数上使用 `AnimationState` 对象。

```ts
Animator.playSingleAnimation(sharkEntity, 'swim')
```

如果该实体正在播放其他任何动画， `Animator.playSingleAnimation` 会停止它们。

`Animator.playSingleAnimation` 需要以下参数：

* `实体`：你要查询的 `Animator` 你想要影响的组件。
* `clipName`：要播放的片段名称字符串。
* `resetCursor`: *（可选）* 如果 *true*，它会从头开始播放该动画，即使该动画之前已暂停。如果 *false*，它会从暂停处继续播放动画。默认： *true*.

```ts
Animator.playSingleAnimation(sharkEntity, 'swim', false)
```

下表总结了 `Animator.playSingleAnimation()` 在不同 `resetCursor` 属性值下的行为：

|              | `resetCursor` = *false* | `resetCursor` = *true* （默认） |
| ------------ | ----------------------- | --------------------------- |
| **当前正在播放**   | 没有影响。                   | 从头播放。                       |
| **已暂停**      | 从上次播放的帧继续。              | 从头播放。                       |
| **已结束（非循环）** | 从头播放。                   | 从头播放。                       |

## 循环动画

默认情况下，动画会以循环方式播放，并永久重复。

通过设置 `loop` 属性来更改此设置。 `状态` 对象。

```ts
Animator.create(shark, {
	states: [
		{
			clip: 'bite',
			playing: true,
			loop: false,
		},
	],
})
```

如果 `loop` 被设置为 *false*时，动画只会播放一次然后停止，并停留在最后一帧的姿势上。

## 停止动画

要停止实体当前播放的所有动画，请使用 `Animator.stopAllAnimations()`.

```ts
Animator.stopAllAnimations(shark)
```

`Animator.stopAllAnimations` 需要以下参数：

* `实体`：你要查询的 `Animator` 你想要影响的组件。
* `resetCursor`: *（可选）* 如果 *true*，它会回到动画第一帧的姿势。如果 *false*，则停留在当前姿势的暂停状态。默认： *true*.

{% hint style="warning" %}
**📔 注意**：当使用 `Animator.playSingleAnimation`播放动画时，此函数会在后台处理停止所有其他动画的操作。在这种情况下，你无需显式停止其他动画。
{% endhint %}

当一个非循环动画播放结束后，默认情况下 3D 模型会保持其最后的姿势。 `shouldReset` 属性控制当一个已停止的动画再次播放时会发生什么：如果 *true*，动画会恢复到初始状态（第一帧；如果以负的 `speed`播放，则为最后一帧），每当它从停止变为播放时都是如此。如果 *false* （默认），它会从上次的位置继续播放。

```ts
Animator.create(shark, {
	states: [
		{
			clip: 'bite',
			playing: true,
			shouldReset: true,
			loop: true,
		},
	],
})
```

你也可以随时使用 `Animator.stopAllAnimations()` 将姿势显式重置回动画的第一帧。

{% hint style="warning" %}
**📔 注意**：重置姿势是一个突变。如果你想让模型平滑过渡到另一个姿势，请播放另一个动画，并通过逐渐调整它们的 `weight` 属性来在两者之间混合。参见 [动画权重](#animation-weight).
{% endhint %}

## 检测动画何时结束

当一个非循环动画播放结束时，引擎会将该动画状态的 `playing` 属性重新设为 *false*。你的场景代码可以读取这个值来判断动画何时结束，例如在其后立即串联另一个动画。

```ts
let wasPlaying = false

engine.addSystem(() => {
	const animator = Animator.get(shark)
	const biteState = animator.states.find((state) => state.clip === 'bite')
	const isPlaying = biteState?.playing ?? false

	if (wasPlaying && !isPlaying) {
		console.log('咬合动画已结束')
		// 串联下一个动画
		Animator.playSingleAnimation(shark, 'swim')
	}

	wasPlaying = isPlaying
})
```

{% hint style="warning" %}
**📔 注意**：当每一帧轮询动画状态时，请始终通过 `Animator.get()` （只读）。不要使用 `Animator.getClip()` 或 `Animator.getMutable()` 进行轮询：这些函数返回的是组件的可变版本，它会在每一帧都将其标记为已更改，并导致不必要的同步工作。

动画状态中的 `playing` 属性只会在动画自行结束时由引擎翻转。循环动画会一直播放直到被停止，因此它们不会自行翻转该属性，而 `speed` 设置为 0 的动画永远不会结束。
{% endhint %}

{% hint style="warning" %}
**📔 注意**：此功能仅在桌面客户端中受支持。
{% endhint %}

## 处理多个动画

如果一个 3D 模型中打包了多个动画，单个 `Animator` 组件可以同时处理它们。

```ts
// 创建实体
const shark = engine.addEntity()

// 向其中添加一个 3D 模型
GltfContainer.create(shark, {
	src: 'models/shark.glb'
})

Animator.create(shark, {
	states:[{
			clip: "swim",
			playing: true,
			loop: true
		}, {
			clip: "bite",
			playing: true,
			loop: true
		}
	]
})
```

在上面的示例中，两个动画由各自独立的 `状态` 对象处理，然后它们都被分配给同一个 `Animator` 组件。

动画中的每根骨骼一次只能受一个动画影响，除非这些动画具有一个 `weight` 权重总和不超过 1 的

如果一个动画只影响角色的腿，而另一个只影响角色的头部，那么它们可以同时播放而没有问题。但如果它们都影响角色的腿，那么你必须一次只播放一个，或者以更低的 `weight` 值播放它们。

如果在上面的例子中， `bite` 动画只影响鲨鱼的嘴巴，而 `swim` 动画只影响鲨鱼脊柱的骨骼，那么它们就可以同时播放。

{% hint style="warning" %}
**📔 注意**: `Animator.playSingleAnimation()` 会停止实体当前正在播放的所有其他动画。要同时播放多个动画，请手动修改 `playing` 属性中的动画状态。
{% endhint %}

## 动画速度

通过更改 `speed` 属性来改变动画播放速度。速度值默认是 1。

```ts
Animator.create(shark, {
	states: [
		{
			clip: 'swim',
			playing: true,
			loop: true,
			speed: 2,
		},
	],
})
```

将速度设为小于 1 的值可以让它播放得更慢，例如设为 0.5 时以一半速度播放。将其设为大于 1 的值可以让它播放得更快，例如设为 2 时以双倍速度播放。

```ts
const swimAnim = Animator.getClip(sharkEntity, 'swim')

swimAnim.speed = 0.5
```

## 动画权重

动画状态中的 `weight` 属性允许单个模型同时执行多个动画，并计算动画中所有动作的加权平均值。 `weight` 决定该动画在平均值中被赋予多少重要性。

默认情况下， `weight` 等于 *1*。该值 `weight` 不能高于 *1*.

```ts
Animator.create(shark, {
	states: [
		{
			clip: 'swim',
			playing: true,
			loop: true,
			weight: 0.2,
		},
	],
})
```

动画状态中的 `weight` 实体中所有正在生效的动画的值应始终总和为 1。如果总和小于 1，加权平均值将在计算的剩余部分使用骨架的默认位置。

例如，在上面的代码示例中，我们正在播放 *swim* 动画，它只有一个 `weight` 为 *0.2*。这种游泳动作会相当微妙：只有动画定义强度的 20%。计算剩余的 80% 取自骨架的默认姿势。

动画状态中的 `weight` 属性可以以有趣的方式使用，例如 `weight` 的 *swim* 属性可以按鲨鱼游泳的速度成比例设置，因此你不需要为快速和慢速游泳创建多个动画。

你也可以在开始和停止动画时逐渐改变 `weight` 值，使过渡更自然，并避免从默认姿势跳到动画中的第一个姿势。

{% hint style="warning" %}
**📔 注意**：所有 `weight` 作用于 3D 模型骨骼的动画值加起来不能超过 1。如果有多个动画同时影响相同的骨骼，它们的权重值之和必须小于 1。
{% endhint %}

```ts
const swimAnim = Animator.getClip(sharkEntity, 'swim')

swimAnim.weight = 0.5
```


---

# 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/3d-nei-rong-ji-chu/3d-model-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.
