跳到主要内容

播放动画

anim 组件在所属实体下方播放动画片段。本页介绍添加组件、为片段创建状态、切换和控制播放。

添加 Anim 组件​

将 anim 添加到动画层级根,通常是模型根实体。片段按名称查找后代节点;若节点位于其他实体下,将 Root Bone(rootBone)指向它。

以 container 加载 GLB,创建实体后在根节点添加组件。应用需注册 anim 系统和 container 处理器:

const asset = new pc.Asset('robot-arm', 'container', {
url: 'https://developer.playcanvas.com/assets/robot-arm.glb'
});
asset.ready(() => {
const arm = asset.resource.instantiateRenderEntity();
arm.addComponent('anim', { activate: true });
app.root.addChild(arm);
});
app.assets.add(asset);
app.assets.load(asset);

activate: true 使组件获得片段后立即播放。

播放片段​

各片段保存在状态中。按名称播放时,为各片段创建同名状态。GLB 动画名来自文件,组件从首先添加的片段开始。需要按速度等参数选择播放项时,使用状态图。

container 的动画资源持有 AnimTrack 片段。assignAnimation() 为片段创建状态:

for (const animation of asset.resource.animations) {
arm.anim.assignAnimation(animation.resource.name, animation.resource);
}
console.log(arm.anim.baseLayer.states); // ['START', 'idle', 'pick', 'place', 'inspect', 'stow']

切换片段​

切换可立即跳转,也可交叉淡化。几十分之一秒的过渡通常能隐藏姿态跳变。

arm.anim.baseLayer.transition('pick', 0.4); // cross-fade over 0.4 seconds
arm.anim.baseLayer.play('idle'); // cut straight to the start of idle

示例​

下面加载带五个动画的机械臂,每个片段一个状态,点击画布淡化到下一个。

实时示例
import * as pc from 'playcanvas';

// create the application with the component systems and resource handlers it uses
const canvas = document.getElementById('application');
const device = await pc.createGraphicsDevice(canvas, {
deviceTypes: [pc.DEVICETYPE_WEBGPU]
});
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);

const options = new pc.AppOptions();
options.graphicsDevice = device;
options.componentSystems = [
pc.RenderComponentSystem,
pc.CameraComponentSystem,
pc.LightComponentSystem,
pc.AnimComponentSystem
];
options.resourceHandlers = [pc.ContainerHandler, pc.TextureHandler];

const app = new pc.AppBase(canvas);
app.init(options);
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);
window.addEventListener('resize', () => app.resizeCanvas());
app.start();

app.scene.ambientLight = new pc.Color(0.35, 0.35, 0.4);

// load the robot arm, a GLB with five animations
const asset = new pc.Asset('robot-arm', 'container', {
url: 'https://developer.playcanvas.com/assets/robot-arm.glb'
});
await new Promise((resolve) => {
asset.ready(resolve);
app.assets.add(asset);
app.assets.load(asset);
});

const camera = new pc.Entity('camera');
camera.addComponent('camera', {
clearColor: new pc.Color(0.11, 0.12, 0.17)
});
camera.setPosition(1.75, 1.4, 2.15);
camera.lookAt(0, 0.75, 0);
app.root.addChild(camera);

const light = new pc.Entity('light');
light.addComponent('light', {
castShadows: true,
shadowBias: 0.2,
normalOffsetBias: 0.05
});
light.setEulerAngles(48, 38, 0);
app.root.addChild(light);

const floor = new pc.Entity('floor');
const floorMaterial = new pc.StandardMaterial();
floorMaterial.diffuse = new pc.Color(0.24, 0.26, 0.31);
floorMaterial.update();
floor.addComponent('render', { type: 'plane', material: floorMaterial });
floor.setLocalScale(20, 1, 20);
app.root.addChild(floor);

// create the arm's entities, and give its root an anim component
const arm = asset.resource.instantiateRenderEntity({ castShadows: true });
arm.addComponent('anim', { activate: true });
app.root.addChild(arm);

// give each animation in the GLB a state of its own, named after it
const clips = asset.resource.animations.map((animation) => {
arm.anim.assignAnimation(animation.resource.name, animation.resource);
return animation.resource.name;
});

// click to cross-fade to the next clip over 0.4 seconds
let current = 0;
canvas.addEventListener('click', () => {
current = (current + 1) % clips.length;
arm.anim.baseLayer.transition(clips[current], 0.4);
});

const label = document.createElement('div');
label.style.cssText = 'position: absolute; left: 12px; bottom: 10px; color: #ccc; font: 14px sans-serif;';
document.body.appendChild(label);
app.on('update', () => {
label.textContent = `Playing: ${arm.anim.baseLayer.activeState}. Click for the next clip.`;
});

可以尝试:

  • 将 0.4 改为 2,观察混合。
  • 用 play() 替换 transition() 并移除时长,立即切换。
  • 创建组件后添加 arm.anim.speed = 0.5;,半速播放。

速度、暂停与循环​

状态速度与组件 speed 相乘。0 冻结播放。状态速度为负会倒放,但组件速度为负只会使片段停在首帧。

arm.anim.speed = 0.5; // everything at half speed
arm.anim.assignAnimation('pick', pickTrack, undefined, 2); // this state at double speed

状态默认循环。非循环状态结束后保持末帧直到退出。assignAnimation() 最后传 false 可只播放一次。编辑器在状态图设置 Speed、Loop,Web Components 在 <pc-anim-clip> 设置 speed、loop。

组件 playing 设为 false 暂停全部图层,设回 true 继续。各图层也有独立 pause()、play()。

React 通过 <Anim> props 设置,例如 <Anim playing={!paused} speed={0.5} />。每次渲染会重新应用,代码直接设置的引擎值不会长期保留。

警告

关闭 Activate 时,还需启动图层:设置 entity.anim.playing = true,再调用 entity.anim.baseLayer.play()。

判断片段完成​

组件不自动发出结束事件,非循环状态保持末帧,可通过以下方式处理:

  • 状态图过渡。退出时间为 1 时,首次播放完成后退出。
  • **末尾事件。**在片段时长位置添加动画事件。
  • **比较时间与时长。**图层 activeStateCurrentTime 为片段自身秒数计的已播放时间,activeStateDuration 为长度:
const layer = arm.anim.baseLayer;
const finished = layer.activeStateCurrentTime >= layer.activeStateDuration;

适用于正向播放一次。循环时间持续增长,不回到 0;倒放从 0 向下计数。不要使用 activeStateProgress,状态速度不为 1 时它会重复应用速度。

另请参阅​