Anim 状态图混合
点击画面获取焦点,然后按 P 键混合到出拳动画。
本教程介绍动画混合的基础知识。
场景中的对象可能需要动画,机器和角色就是常见例子。制作三维内容时,通常会分别创建各段动画;由于它们经常循环播放,也称为动画循环。例如,人形角色可能具有待机、行走、奔跑等循环动画。在 PlayCanvas 中,需要一种机制将这些动画应用到对象上,并避免切换时突然跳变。动画混合可以让两段动画平滑过渡,显著提升动画对象的视觉品质。 下面看看如何通过 PlayCanvas 实现。
Animstategraph 资源
要为模型添加动画,首先必须创建 animstategraph 资源。它控制单个实体关联的多个动画,决定何时播放各动画,以及如何混合它们。
创建 animstategraph 资源后,会看到它的编辑界面:

默认情况下,animstategraph 资源会创建名为 Initial State 的动画状态,并添加从 START 到该状态的默认过渡。因此,状态图开始运行时会立即播放 Initial State。可以将其重命名为 Idle,这也是本教程初始动画的名称。接着右键点击画布,选择 Add new state(添加新状态),再将新状态重命名为 Punch:

接下来要告诉状态图,何时以及如何在 Idle 和 Punch 动画状态之间切换。方法是创建过渡:右键点击 Idle 动画,从上下文菜单选择 Add transition(添加过渡),再选择 Punch 状态,即可创建从 Idle 到 Punch 的过渡。

选中过渡后,右侧面板会显示过渡检查器。在此设置过渡时长,决定两段动画混合持续多久。这里设为 0.2 秒。
然后创建参数来决定何时激活过渡。在左侧参数面板点击 + parameter,将参数命名为 punch,类型设为 Boolean(布尔值)。

现在可以将该参数设为刚创建过渡的条件。选中过渡,在检查器中点击 New Condition(新条件)。
设置过渡检测新建的 punch 参数,并将条件设为 == true:

这样,只有 punch 参数为 true 时,才会从 Idle 状态过渡到 Punch 状态。
最后,再创建一个从 Punch 返回 Idle 的过渡:

这个过渡采用相同的时长,但条件检查 punch 参数是否不再为 true。
状态图完成后,需要将它与动画资源和目标实体关联起来,这正是 Anim 组件的作用。
Anim 组件
首先,为目标实体添加 Anim 组件。

组件创建后会显示 animstategraph 资源槽位。将刚创建的资源拖入,就会为图中每个动画状态显示动画槽位。本例会显示 Idle 与 Punch 的槽位。填入对应动画资源后,Anim 组件即可播放:

键盘输入
动画配置完成后,还需要让用户与系统交互,这就要用到脚本。添加 Script 组件,并编写 keyboard_controls.js 脚本实现该行为,挂载方式如下:

还记得之前设置的 punch 参数吗?脚本根据 P 键是否按下以及角色是否正在出拳,切换该参数的状态:
- ESM
- 传统脚本
import { Script, EVENT_KEYDOWN, EVENT_KEYUP, KEY_P } from 'playcanvas';
export class KeyboardControls extends Script {
static scriptName = 'keyboardControls';
// initialize code called once per entity
initialize() {
this.app.keyboard.on(EVENT_KEYDOWN, this.keyDown, this);
this.app.keyboard.on(EVENT_KEYUP, this.keyUp, this);
}
update(dt) {
if (this.app.keyboard.wasPressed(KEY_P) && (this.entity.anim.baseLayer.activeState !== 'Punch')) {
this.entity.anim.setBoolean('punch', true);
}
if (this.app.keyboard.wasReleased(KEY_P) && (this.entity.anim.baseLayer.activeState === 'Punch')) {
this.entity.anim.setBoolean('punch', false);
}
}
}
var KeyboardControls = pc.createScript('keyboardControls');
// initialize code called once per entity
KeyboardControls.prototype.initialize = function() {
this.app.keyboard.on(pc.EVENT_KEYDOWN, this.keyDown, this);
this.app.keyboard.on(pc.EVENT_KEYUP, this.keyUp, this);
};
KeyboardControls.prototype.update = function(dt) {
if (this.app.keyboard.wasPressed(pc.KEY_P) && (this.entity.anim.baseLayer.activeState !== 'Punch')) {
this.entity.anim.setBoolean('punch', true);
}
if (this.app.keyboard.wasReleased(pc.KEY_P) && (this.entity.anim.baseLayer.activeState === 'Punch')) {
this.entity.anim.setBoolean('punch', false);
}
};
在此基础上,可以向 animstategraph 资源添加更多动画,构建更复杂的动画状态图!
完整场景见此处。