> 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-creator/blob/main/creator/3d-modeling/animations/README.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 模型包含任何动画，默认行为是始终循环播放其中第一个动画。

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

{% hint style="info" %}
**💡 提示**：在 [场景编辑器](/creator/content-creator-zh/chang-jing-bian-ji-qi/kai-shi-shi-yong/about-editor.md)中，你可以可视化地添加一个 **动画器** 组件。参见 [添加组件](/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 %}

## 显式处理动画

一个 `动画器` 组件用于访问实体的所有动画，并可用于明确告诉实体播放或停止某个动画。 `动画器` 组件包含一个 `状态`数组，该列表必须为 3D 模型可执行的每个动画包含一个对象。单个 `动画器` 可以包含所需数量的状态。

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

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

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

每个 `state` 对象会跟踪动画当前是否正在播放。

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

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

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

## 获取动画

从 `动画器` 按名称获取一个片段，使用 `.Animator.getClip()` 函数。该函数返回动画状态对象的可变版本。

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

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

* `实体`：该 `动画器` 组件所属的实体，你要查询它。
* `clipName`：要获取的片段名称字符串。

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

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

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

## 播放动画

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

使用 `Animator.playSingleAnimation()` 函数作用于一个 `AnimationState` 对象。

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

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

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

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

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

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

|              | `resetCursor` = *false* | `resetCursor` = *真* （默认） |
| ------------ | ----------------------- | ------------------------ |
| **当前正在播放**   | 无效果。                    | 从头开始播放。                  |
| **已暂停**      | 从上次播放的帧继续播放。            | 从头开始播放。                  |
| **已完成（非循环）** | 从头开始播放。                 | 从头开始播放。                  |

## 循环动画

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

通过设置 `loop` 中的属性 `state` 对象。

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

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

## 停止动画

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

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

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

* `实体`：该 `动画器` 你想要影响的组件所属实体。
* `resetCursor`: *（可选）* 如果 *真*，它会返回到动画第一帧的姿势。若 *false*，则会停留在当前姿势下暂停。默认值： *真*.

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

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

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

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

{% hint style="warning" %}
**📔 注意**：重置姿势是一个突兀的变化。如果你想让模型平滑过渡到另一种姿势，请播放另一个动画，并通过逐渐调整它们的 `权重` 属性在两者之间进行混合。参见 [动画权重](#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('bite animation finished')
		// 串联下一个动画
		Animator.playSingleAnimation(shark, 'swim')
	}

	wasPlaying = isPlaying
})
```

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

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

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

## 处理多个动画

如果一个 3D 模型中打包了多个动画，单个 `动画器` 组件即可处理所有动画。

```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
		}
	]
})
```

在上面的示例中，两个动画分别由不同的 `state` 对象处理，然后它们都被分配给同一个 `动画器` 组件。

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

如果一个动画只影响角色的腿，另一个只影响角色的头，那么它们可以同时播放而没有问题。但如果它们都影响角色的腿，那么你要么一次只播放一个，要么以更低的 `权重` 取值列表。

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

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

## 动画速度

通过更改 `速度` 属性来改变动画的播放速度。速度的默认值为 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
```

## 动画权重

该 `权重` 属性允许单个模型同时执行多个动画，并计算动画中涉及的所有动作的加权平均值。 `权重` 的值决定该动画在平均值中所占的权重。

默认情况下， `权重` 等于 *1*。 `权重` 不能高于 *1*.

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

该 `权重` 实体中所有激活动画的值应始终加起来等于 1。如果总和小于 1，加权平均会在计算的剩余部分使用骨架的默认位置。

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

该 `权重` 属性可以以有趣的方式使用，例如 `权重` 属性的 *swim* 可以按鲨鱼游动的速度按比例设置，这样你就不需要为快速和慢速游泳创建多个动画。

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

{% hint style="warning" %}
**📔 注意**：所有动画相加后的 `权重` 作用于 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 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/3d-nei-rong-ji-chu/3d-model-animations.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.
