Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
动画文档优化 (#955)
Browse files Browse the repository at this point in the history
* fix: animator doc
  • Loading branch information
luzhuang authored Feb 19, 2024
1 parent d59ce49 commit 9480c64
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 310 deletions.
75 changes: 33 additions & 42 deletions docs/animation-animator.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type: 动画
label: Animation
---

动画控制组件([Animator](${api}core/Animator)可以通过状态机组织动画片段([AnimationClip](${api}core/AnimationClip))实现更加灵活丰富的动画效果
动画控制组件([Animator](${api}core/Animator)的作用是读取[动画控制器](${docs}animation-animatorController)[AnimatorController](${api}core/AnimatorController))的数据,并播放其内容

### 参数说明

Expand All @@ -15,53 +15,44 @@ label: Animation

## 编辑器使用

通过 `AnimatorController` 编辑器,用户可以很方便的添加过渡和混合等动画效果。
1. 当我们把模型拖入到场景中,模型以初始姿态展示出来,但是并不会播放任何动画,我们需要在模型实体上添加动画控制组件([Animator](${api}core/Animator)

1. 将带动画的模型上传到编辑器上,编辑器会自动加载其上的动画片段到资源面板中
![2](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*kuSLTaxomrUAAAAAAAAAAAAADsJ_AQ/original)

![4](https://gw.alipayobjects.com/zos/OasisHub/2ee85519-4f48-4e65-8dcc-b6afe9d1f7d9/4.jpg)
2. 动画控制组件([Animator](${api}core/Animator))需要绑定一个 [动画控制器](${docs}animation-animatorController) 资产,我们创建并绑定

2. 当我们把模型拖入到场景中,模型以初始姿态展示出来,但是并不会播放任何动画,我们需要在模型实体上添加 Animator 组件,来控制动画的播放
![3](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*irT7SZvw4N8AAAAAAAAAAAAADsJ_AQ/original)

![image-20210902230821166](https://gw.alipayobjects.com/zos/OasisHub/405ebaa7-8c03-4fd0-816e-cbcb39562b68/1667457441830-207e0940-4a82-4bc2-8d9c-d12d44c3eb31.png)
![4](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*VtX3RJR8kdMAAAAAAAAAAAAADsJ_AQ/original)

3. Animator 组件需要引入一个 AnimatorController 资产,我们创建并引入
3. 至此你在导出的项目中就可以通过 `animator.play` 播放[动画控制器](${docs}animation-animatorController)中的动画了。

![image](https://gw.alipayobjects.com/zos/OasisHub/35f5788a-7544-4231-b11e-373fcce31267/1667457702054-45c9d61a-1e9b-49b5-a719-36724471aaa2.png)

![image](https://gw.alipayobjects.com/zos/OasisHub/68de5813-be5f-4669-91bc-d8d3f4077c5a/1667457755170-565aaa77-ec4b-462a-9a38-dc7ad66e9c19.png)

4. 刚创建的 AnimatorController 中没有任何数据,我们需要对他进行编辑, 双击资产, 并为它添加一个 AnimatorState

![3](https://gw.alipayobjects.com/zos/OasisHub/4f4139aa-eaaf-4b9d-b077-1570e783843d/3.jpg)

5. 点击 AnimatorState 为它绑定一个 AnimationClip

![image](https://gw.alipayobjects.com/zos/OasisHub/8e29b9fa-eeed-4e5c-84c1-ea68f9732a92/1667457999371-e0ed9c57-d44c-4f2a-abda-12eba6e3a934.png)

6. 至此你在导出的项目中就可以通过 `animator.play("New State")` 播放 `running` 动画了。如果你没有为实体添加 Animator 组件的话 Galacean Engine 会为你默认创建一个并且 AnimatorController 中默认添加了模型的所有动画,拿上图的模型举例,你只需要直接调用 `animator.play("running")` 就可以了。以上内容是可以帮助你更清晰的了解 Animator 的运行机制,当然除此以外你可以通过 AnimatorController 的编辑器实现更多的功能。
如果你没有为实体添加 动画控制组件([Animator](${api}core/Animator))的话 Galacean Engine 会为你默认创建一个并且 [动画控制器](${docs}animation-animatorController) 中默认添加了模型的所有动画片段,当然除此以外你可以通过 [动画控制器](${docs}animation-animatorController) 实现更多的功能。

## 脚本使用

> 在使用脚本之前,最好阅读[动画系统构成](${docs}animation-system)文档,以帮助你更好的了解动画系统的运行逻辑
### 播放动画

在加载GLTF模型后引擎会自动为模型添加一个Animator组件,并将模型中的动画片段加入其中。可以直接在模型的根实体上获取Animator组件,并播放指定动画。
在加载 GLTF 模型后引擎会自动为模型添加一个 Animator 组件,并将模型中的动画片段加入其中。可以直接在模型的根实体上获取 Animator 组件,并播放指定动画。

```typescript
engine.resourceManager
.load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb")
.load<GLTFResource>(
"https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb"
)
.then((asset) => {
const { defaultSceneRoot } = asset;
rootEntity.addChild(defaultSceneRoot);
const animator = defaultSceneRoot.getComponent(Animator);
animator.play("run");
});
});
```

#### 控制播放速度

你可以通过 [speed](${api}core/Animator#speed)  属性来控制动画的播放速度。 `speed` 默认值为 `1.0` ,值越大播放的越快,越小播放的越慢。当值为负数时,进行倒播。

你可以通过 [speed](${api}core/Animator#speed)  属性来控制动画的播放速度。 `speed`  默认值为 `1.0` ,值越大播放的越快,越小播放的越慢。当值为负数时,进行倒播。

```typescript
animator.speed = 2.0
Expand All @@ -78,18 +69,18 @@ animator.enabled = false;
animator.enabled = true;
```

如果你只想针对某一个动画状态进行暂停,可以通过将它的速度设置为0来实现
如果你只想针对某一个动画状态进行暂停,可以通过将它的速度设置为 0 来实现

```typescript
const state = animator.findAnimatorState('xxx');
const state = animator.findAnimatorState("xxx");
state.speed = 0;
```

#### 播放指定动画状态

<playground src="skeleton-animation-play.ts"></playground>

你可以使用 [play](${api}core/Animator#play) 方法来播放指定的AnimatorState。参数为AnimatorState的`name`,其他参数说明详见[API文档](${api}core/Animator#play)
你可以使用 [play](${api}core/Animator#play)  方法来播放指定的 AnimatorState。参数为 AnimatorState 的`name`,其他参数说明详见[API 文档](${api}core/Animator#play)

```typescript
animator.play("run");
Expand All @@ -103,26 +94,26 @@ const normalizedTimeOffset = 0.5; // 归一化的时间
animator.play("run", layerIndex, normalizedTimeOffset);
```

### 动画数据

#### 设置动画数据

你可以通过 [animatorController](${api}core/Animator#animatorController)  属性来设置动画控制器的动画数据,加载完成的GLTF模型会自动添加一个默认的AnimatorController。
### 获取当前在播放的动画状态

你可以使用 [getCurrentAnimatorState](${api}core/Animator#getCurrentAnimatorState)  方法来获取当前正在播放的 AnimatorState。参数为动画状态所在层的序号`layerIndex`, 详见[API 文档](${api}core/Animator#getCurrentAnimatorState)。获取之后可以设置动画状态的属性,比如将默认的循环播放改为一次。

```typescript
animator.animatorController = new AnimatorController();
const currentState = animator.getCurrentAnimatorState(0);
// 播放一次
currentState.wrapMode = WrapMode.Once;
// 循环播放
currentState.wrapMode = WrapMode.Loop;
```

#### 复用动画数据

有的时候模型的动画数据存储在其他模型中,可以用如下的方式引入使用:

<playground src="skeleton-animation-reuse.ts"></playground>
### 获取动画状态

除此以外还有一种方式,Animator的 [AnimatorController](${api}core/AnimatorController) 就是一个数据存储的类,它不会包含运行时的数据,基于这种设计只要绑定Animator组件的模型的**骨骼节点的层级结构和命名相同**,我们就可以对动画数据进行复用
你可以使用 [findAnimatorState](${api}core/Animator#findAnimatorState)  方法来获取指定名称的 AnimatorState。详见[API 文档](${api}core/Animator#getCurrentAnimatorState)。获取之后可以设置动画状态的属性,比如将默认的循环播放改为一次

```typescript
const animator = model1.getComponent(Animator);
animator.animatorController = model2.getComponent(Animator).animatorController;
const state = animator.findAnimatorState("xxx");
// 播放一次
state.wrapMode = WrapMode.Once;
// 循环播放
state.wrapMode = WrapMode.Loop;
```
201 changes: 201 additions & 0 deletions docs/animation-animatorController.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
---
order: 3
title: 动画控制器
type: 动画
label: Animation
---

动画控制器([AnimatorController](${api}core/AnimatorController))用于组织[动画片段](${docs}animation-clip)[AnimationClip](${api}core/AnimationClip))实现更加灵活丰富的动画效果。

## 编辑器使用

### 基础使用

通过动画控制器的编辑器,用户可以在其中组织[动画片段](${docs}animation-clip)的播放逻辑

1. 准备好动画片段([制作动画片段](${docs}animation-clip-)

![1](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*Qc8sQ6iJd8IAAAAAAAAAAAAADsJ_AQ/original)

2. 要组织播放这些动画片段我们需要创建一个动画控制器([AnimatorController](${api}core/AnimatorController))资产

![3](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*irT7SZvw4N8AAAAAAAAAAAAADsJ_AQ/original)

3. 刚创建的动画控制器中没有任何数据,我们需要对他进行编辑,双击资产, 并为它添加一个 AnimatorState

![5](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*BcYXSI6OTyoAAAAAAAAAAAAADsJ_AQ/original)

4. 点击 AnimatorState 为它绑定一个动画片段

![6](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*KwFzRZCmbxoAAAAAAAAAAAAADsJ_AQ/original)

5.[动画控制组件](${docs}animation-animator)上绑定该动画控制器([AnimatorController](${api}core/AnimatorController))资产

![4](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*VtX3RJR8kdMAAAAAAAAAAAAADsJ_AQ/original)

6. 至此你在导出的项目中就可以通过 `animator.play("New State")` 播放 `run` 动画了

你还可以通过动画控制器的编辑器实现更多的功能:

### 默认播放

将 AnimatorState 连接到`entry`上你导出的项目运行时就会自动播放其上的动画,而不需再调用 `animator.play`。同时你也会看到编辑器的模型也开始播放动画了。
![2](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*t2JlQ7PGqikAAAAAAAAAAAAADsJ_AQ/original)

### 动画过渡

将两个想要过渡的 `AnimatorState` 连接即可实现动画过渡的效果, 点击两个动画间的连线,可以修改动画过渡的参数调整效果

![animationcrossfade](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*7_OFQqABtc0AAAAAAAAAAAAADsJ_AQ/original)

#### 参数说明

| 属性 | 功能说明 |
| :------- | :----------------------------------------------------------------- |
| duration | 过渡时长,时间为相对目标状态的归一化时间, 默认值为 1.0 |
| offset | 目标状态向前的偏移时间,时间为相对目标状态的归一化时间, 默认值为 0 |
| exitTime | 起始状态过渡开始时间,时间为相对起始状态的归一化时间, 默认值为 0.3 |

### 动画叠加

Galacean 引擎支持多层的动画叠加。动画叠加是通过 `AnimatorControllerLayer` 间的混合达到的效果。第一层是基础动画层,修改它的权重及混合模式将不会生效。

双击 `AnimatorController` 资源文件编辑动画,添加 Layer,将混合的动作也连接`entry`

![animationadditive](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*vF7fS6mRnmYAAAAAAAAAAAAADsJ_AQ/original)

有的时候你想要得到一个固定的姿势,需要裁减设计师给到的动画切片,可以修改 `AnimatorState``StartTime``EndTime`,点击 `AnimatorState` 即可对其进行编辑:

![1](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*JNFGTboEM5QAAAAAAAAAAAAADsJ_AQ/original)

| 属性 | 功能说明 |
| :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Name | 修改 `AnimatorState` 的名字,名字在所在的层要是**唯一**的。 |
| AnimationClip | 用于绑定 `AnimationClip` 资产,`AnimationClip` 存储了模型的动画数据。 |
| WrapMode | `AnimatorState` 是循环播放还是播放一次,默认为 `Once` 即播放一次。 |
| Speed | `AnimatorState` 的播放速度,默认值为 1.0 ,值越大动画速度越快 |
| StartTime | `AnimatorState``AnimationClip` 的哪个时间开始播放,时间为相对 `AnimationClip` 时长的归一化时间。默认值为 0 ,即从头开始播放。 例如:值为 1.0 ,则是 `AnimationClip` 的最后一帧状态。 |
| EndTime | `AnimatorState` 播放到 `AnimationClip` 的哪个时间结束播放,时间为相对 `AnimationClip` 时长的归一化时间。默认值为 1.0 ,即播放到最后。 |

你也可以通过修改 `Layer``Weight` 参数来调整 `Layer` 在混合中的权重,通过修改 `Blending` 来修改混合模式。

![animationadditive2](https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*_3aNSKP44FgAAAAAAAAAAAAADsJ_AQ/original)

| 属性 | 功能说明 |
| :------- | :--------------------------------------------------------------------------------- |
| Name | 该层的名字。 |
| Weight | 该层的混合权重,默认值为 1.0 。 |
| Blending | 该层的混合模式,`Additive` 为叠加模式, `Override` 为覆盖模式,默认值为 `Override` |

## 脚本使用

> 在使用脚本之前,最好阅读[动画系统构成](${docs}animation-system)文档,以帮助你更好的了解动画系统的运行逻辑
### 默认播放

你可以通过设置 AnimatorStateMachine 的[defaultState](${api}core/AnimatorStateMachine#defaultState) 来设置所在层的默认播放动画,这样当 Animator `enabled=true` 时你不需要调用 `play` 方法即可默认播放。

```typescript
const layers = animator.animatorController.layers;
layers[0].stateMachine.defaultState = animator.findAnimatorState("walk");
layers[1].stateMachine.defaultState = animator.findAnimatorState("sad_pose");
layers[1].blendingMode = AnimatorLayerBlendingMode.Additive;
```

### 动画过渡

你可以通过为 `AnimatorState` 添加 `AnimatorTransition` 实现动画状态间的过渡。

```typescript
const walkThenRunState = animatorStateMachine.addState("walkThenRun");
walkThenRunState.clip = walkClip;
const runState = animatorStateMachine.addState("run");
runState.clip = runClip;
const transition = new AnimatorStateTransition();
transition.duration = 1;
transition.offset = 0;
transition.exitTime = 0.5;
transition.destinationState = runState;
walkThenRunState.addTransition(transition);
animator.play("walkThenRun");
```

通过这样的方式你之后每次在该动画状态机所在的层播放 `walkThenRun` 动画时都会在 `walk` 动画播放一半时开始过渡到 `run` 动画。

### 动画叠加

将想要叠加的动画状态添加到其他层并将它的混合模式设置为 `AnimatorLayerBlendingMode.Additive` 即可实现动画叠加效果,

<playground src="skeleton-animation-additive.ts"></playground>

### 动画数据

#### 设置动画数据

你可以通过 [animatorController](${api}core/Animator#animatorController)  属性来设置动画控制器的动画数据,加载完成的 GLTF 模型会自动添加一个默认的 AnimatorController。

```typescript
animator.animatorController = new AnimatorController();
```

#### 复用动画数据

有的时候模型的动画数据存储在其他模型中,可以用如下的方式引入使用:

<playground src="skeleton-animation-reuse.ts"></playground>

除此以外还有一种方式,Animator 的 [AnimatorController](${api}core/AnimatorController) 就是一个数据存储的类,它不会包含运行时的数据,基于这种设计只要绑定 Animator 组件的模型的**骨骼节点的层级结构和命名相同**,我们就可以对动画数据进行复用。

```typescript
const animator = model1.getComponent(Animator);
animator.animatorController = model2.getComponent(Animator).animatorController;
```

### 状态机脚本

<playground src="animation-stateMachineScript.ts"></playground>

状态机脚本为用户提供了动画状态的生命周期钩子函数来编写自己的游戏逻辑代码。用户可以通过继承 [StateMachineScript](${api}core/StateMachineScript) 类来使用状态机脚本。

状态机脚本提供了三个动画状态周期:

- `onStateEnter`:动画状态开始播放时回调。
- `onStateUpdate`:动画状态更新时回调。
- `onStateExit`:动画状态结束时回调。

```typescript
class theScript extends StateMachineScript {
// onStateEnter is called when a transition starts and the state machine starts to evaluate this state
onStateEnter(animator: Animator, stateInfo: any, layerIndex: number) {
console.log("onStateEnter", animator, stateInfo, layerIndex);
}

// onStateUpdate is called on each Update frame between onStateEnter and onStateExit callbacks
onStateUpdate(animator: Animator, stateInfo: any, layerIndex: number) {
console.log("onStateUpdate", animator, stateInfo, layerIndex);
}

// onStateExit is called when a transition ends and the state machine finishes evaluating this state
onStateExit(animator: Animator, stateInfo: any, layerIndex: number) {
console.log("onStateExit", animator, stateInfo, layerIndex);
}
}

animatorState.addStateMachineScript(theScript);
```

如果你的脚本不用复用的话你也可以这么写:

```typescript
state.addStateMachineScript(
class extends StateMachineScript {
onStateEnter(
animator: Animator,
animatorState: AnimatorState,
layerIndex: number
): void {
console.log("onStateEnter: ", animatorState);
}
}
);
```
2 changes: 1 addition & 1 deletion docs/animation-clip-for-artist.zh-CN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
order: 4
order: 6
title: 美术动画切片
type: 动画
label: Animation
Expand Down
Loading

0 comments on commit 9480c64

Please sign in to comment.