【Laya】Animation 使用指南
Laya.Animation是LayaAir引擎提供的序列帧动画类,支持图集、图片序列和IDE动画文件等多种资源格式。本文详细介绍了其API属性、播放控制方法以及基础用法,包括加载图集动画、图片序列、使用source属性和帧标签管理等核心功能。同时提供了角色动作切换和特效动画播放器等实用示例,帮助开发者快速掌握动画播放、停止、跳转等操作技巧。适用于游戏角色动作、特效动画、UI动画和场景动画等多种场
·
Laya.Animation 使用指南
简介
Laya.Animation 是 LayaAir 引擎提供的序列帧动画类,用于播放帧动画。支持图集、图片序列、IDE 动画文件等多种资源格式,提供完整的播放控制功能。
适用场景:
- 角色动作(待机、行走、攻击、受伤等)
- 特效动画(爆炸、烟雾、光效等)
- UI 动画(按钮状态切换、图标动画等)
- 场景动画(流水、闪烁、动态元素等)
继承关系:
Laya.Animation → Laya.AnimationBase → Laya.Sprite → Laya.Node
目录
API 参考
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
source |
string |
- | 动画数据源(IDE 动画 / 图集 / 图片数组) |
images |
string[] |
- | 动画的图片路径数组 |
interval |
number |
- | 帧间隔时间(毫秒) |
loop |
boolean |
true |
是否循环播放 |
wrapMode |
AnimationWrapMode |
- | 播放顺序类型 |
index |
number |
- | 当前帧索引(可读写) |
count |
number |
- | 当前动画帧总数(只读) |
isPlaying |
boolean |
- | 是否正在播放(只读) |
autoPlay |
boolean |
false |
是否添加到舞台后自动播放 |
播放控制
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
play(start?, loop?, name?) |
起始帧/标签, 是否循环, 动画名 | void |
开始播放动画 |
stop() |
- | void |
停止动画播放 |
gotoAndStop(position) |
帧索引或帧标签 | void |
跳转到指定帧并停止 |
资源加载
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
loadImages(urls) |
图片路径数组 | this |
加载图片序列 |
loadAtlas(url) |
图集路径 | this |
加载图集动画 |
帧标签管理
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
addLabel(label, index) |
标签名, 帧索引 | void |
添加帧标签 |
removeLabel(label) |
标签名 | void |
删除帧标签(空字符串删除全部) |
对象管理
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
clear() |
- | this |
停止播放并清理,可复用 |
静态方法
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
createFrames(urls, key) |
图片数组, 缓存键 | void |
创建动画帧缓存(已废弃) |
clearCache(key) |
缓存键 | void |
清除指定动画缓存 |
AnimationWrapMode 播放模式
enum Laya.AnimationWrapMode {
Positive = 0, // 正序播放:0 → 1 → 2 → ... → end
Reverse = 1, // 倒序播放:end → ... → 2 → 1 → 0
PingPong = 2 // 往返播放:0 → ... → end → ... → 0
}
基础用法
1. 加载图集动画
最常用的方式,适合 IDE 导出的动画资源:
let ani = new Laya.Animation();
ani.loadAtlas("res/fighter/fighter.atlas");
ani.interval = 30;
ani.play();
Laya.stage.addChild(ani);
2. 加载图片序列
适合独立图片帧的动画:
let ani = new Laya.Animation();
// 构建图片序列
let frames: string[] = [];
for (let i = 1; i <= 25; i++) {
let numStr = ("0000" + i).slice(-4);
frames.push(`res/phoenix/phoenix${numStr}.jpg`);
}
ani.loadImages(frames);
ani.play();
Laya.stage.addChild(ani);
3. 使用 source 属性
直接设置动画数据源:
let ani = new Laya.Animation();
ani.source = "res/hero/hero.atlas"; // 图集路径
ani.source = "res/hero/hero.ani"; // IDE 动画文件
ani.play();
4. 控制播放
let ani = new Laya.Animation();
ani.loadAtlas("res/hero/hero.atlas");
// 从头播放
ani.play();
// 从第5帧开始播放
ani.play(5);
// 从帧标签开始播放
ani.play("walk");
// 循环播放
ani.play(0, true);
// 播放指定动画名称
ani.play(0, true, "attack");
// 停止播放
ani.stop();
// 跳转到指定帧并停止
ani.gotoAndStop(10);
ani.gotoAndStop("idle");
5. 帧标签使用
let ani = new Laya.Animation();
ani.loadAtlas("res/hero/hero.atlas");
// 添加动作标签
ani.addLabel("idle", 0);
ani.addLabel("walk", 10);
ani.addLabel("attack", 20);
ani.addLabel("hit", 35);
// 监听标签事件
ani.on(Laya.Event.LABEL, this, (label: string) => {
console.log("动作标签:", label);
});
ani.play();
实用示例
示例1: 角色动作切换
@regClass()
export class HeroAnimation extends Laya.Sprite {
private ani: Laya.Animation;
constructor() {
super();
this.setupAnimation();
}
private setupAnimation(): void {
this.ani = new Laya.Animation();
this.ani.loadAtlas("res/hero/hero.atlas");
// 设置动作标签
this.ani.addLabel("idle", 0);
this.ani.addLabel("walk", 10);
this.ani.addLabel("attack", 25);
this.ani.addLabel("hurt", 40);
// 监听攻击完成
this.ani.on(Laya.Event.LABEL, this, (label: string) => {
if (label === "attack_end") {
this.playWalk();
}
});
this.addChild(this.ani);
this.playIdle();
}
public playIdle(): void {
this.ani.play(0, true, "idle");
}
public playWalk(): void {
this.ani.play(0, true, "walk");
}
public playAttack(): void {
this.ani.play(0, false, "attack");
}
public playHurt(): void {
this.ani.gotoAndStop("hurt");
Laya.timer.once(500, this, this.playIdle);
}
}
示例2: 特效动画播放器
@regClass()
export class EffectPlayer extends Laya.Script {
@property(Laya.Animation)
public effectAni: Laya.Animation = null;
// 播放一次性特效(如爆炸)
public playOnce(effectName: string, x: number, y: number): void {
let effect = new Laya.Animation();
effect.loadAtlas(`res/effect/${effectName}.atlas`);
effect.pos(x, y);
effect.play(0, false);
// 播放完成后销毁
effect.on(Laya.Event.COMPLETE, this, () => {
effect.destroy();
});
Laya.stage.addChild(effect);
}
// 播放循环特效(如光环)
public playLoop(effectName: string, parent: Laya.Sprite): Laya.Animation {
let effect = new Laya.Animation();
effect.loadAtlas(`res/effect/${effectName}.atlas`);
effect.play(0, true);
parent.addChild(effect);
return effect;
}
}
示例3: 逐帧动画控制
@regClass()
export class FrameControlAnimation extends Laya.Sprite {
private ani: Laya.Animation;
private currentFrame: number = 0;
private totalFrames: number = 0;
constructor() {
super();
this.setupAnimation();
}
private setupAnimation(): void {
this.ani = new Laya.Animation();
let frames: string[] = [];
for (let i = 1; i <= 60; i++) {
frames.push(`res/sequence/frame${i}.png`);
}
this.ani.loadImages(frames);
this.totalFrames = this.ani.count;
this.ani.gotoAndStop(0);
this.addChild(this.ani);
// 设置手动播放
Laya.timer.loop(50, this, this.nextFrame);
}
private nextFrame(): void {
this.currentFrame++;
if (this.currentFrame >= this.totalFrames) {
this.currentFrame = 0;
}
this.ani.index = this.currentFrame;
}
public pause(): void {
Laya.timer.clear(this, this.nextFrame);
}
public resume(): void {
Laya.timer.loop(50, this, this.nextFrame);
}
public gotoFrame(frame: number): void {
this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames - 1));
this.ani.index = this.currentFrame;
}
}
示例4: 往返播放动画
@regClass()
export class PingPongAnimation extends Laya.Sprite {
createPhoenix(): void {
let ani = new Laya.Animation();
// 构建图片序列
let frames: string[] = [];
for (let i = 1; i <= 25; i++) {
let numStr = ("0000" + i).slice(-4);
frames.push(`res/phoenix/phoenix${numStr}.jpg`);
}
ani.loadImages(frames);
// 设置往返播放
ani.wrapMode = Laya.AnimationWrapMode.PingPong;
ani.interval = 50;
ani.play();
// 设置混合模式实现发光效果
ani.blendMode = "lighter";
this.addChild(ani);
}
createReverse(): void {
let ani = new Laya.Animation();
ani.loadAtlas("res/effect/effect.atlas");
// 倒序播放
ani.wrapMode = Laya.AnimationWrapMode.Reverse;
ani.play();
this.addChild(ani);
}
}
示例5: 动画对象池管理
@regClass()
export class AnimationPool extends Laya.Script {
private static readonly POOL_SIGN = "AnimationEffect";
// 从对象池获取动画
public static get(): Laya.Animation {
let ani = Laya.Pool.getItemByClass(this.POOL_SIGN, Laya.Animation);
ani.visible = true;
return ani;
}
// 回收动画到对象池
public static recover(ani: Laya.Animation): void {
ani.stop();
ani.clear();
ani.removeSelf();
ani.visible = false;
ani.offAll();
Laya.Pool.recover(this.POOL_SIGN, ani);
}
// 播放特效(自动回收)
public static playEffect(atlasUrl: string, x: number, y: number): void {
let ani = this.get();
ani.loadAtlas(atlasUrl);
ani.pos(x, y);
ani.play(0, false);
ani.on(Laya.Event.COMPLETE, this, () => {
this.recover(ani);
});
Laya.stage.addChild(ani);
}
// 预填充对象池
public static preload(count: number = 10): void {
for (let i = 0; i < count; i++) {
let ani = new Laya.Animation();
this.recover(ani);
}
}
}
// 使用示例
AnimationPool.preload(20);
AnimationPool.playEffect("res/explosion.atlas", 100, 200);
高级技巧
1. 动画事件处理
let ani = new Laya.Animation();
ani.loadAtlas("res/hero/hero.atlas");
// 帧标签事件
ani.on(Laya.Event.LABEL, this, (label: string) => {
console.log("到达标签:", label);
if (label === "attack_frame") {
// 创建攻击判定
}
});
// 播放完成事件(非循环模式)
ani.on(Laya.Event.COMPLETE, this, () => {
console.log("动画播放完成");
});
ani.play();
2. 动画中心点设置
let ani = new Laya.Animation();
ani.loadAtlas("res/monster/monster.atlas");
// 等待资源加载完成后获取边界
ani.on(Laya.Event.LOADED, this, () => {
let bounds = ani.getGraphicBounds();
ani.pivot(bounds.width / 2, bounds.height / 2);
ani.pos(400, 300);
});
Laya.stage.addChild(ani);
3. 多动画数据源切换
@regClass()
export class MultiAnimation extends Laya.Sprite {
private ani: Laya.Animation;
private currentAction: string = "";
constructor() {
super();
this.ani = new Laya.Animation();
this.addChild(this.ani);
}
// 切换动画数据源
public switchAtlas(atlasPath: string, actionName: string): void {
if (this.currentAction === actionName) return;
this.ani.clear();
this.ani.loadAtlas(atlasPath);
this.ani.play();
this.currentAction = actionName;
}
// 使用示例
public showWalk(): void {
this.switchAtlas("res/hero/walk.atlas", "walk");
}
public showAttack(): void {
this.switchAtlas("res/hero/attack.atlas", "attack");
}
}
4. 动画速度控制
let ani = new Laya.Animation();
ani.loadAtlas("res/effect/effect.atlas");
// 设置播放速度(帧间隔)
ani.interval = 30; // 正常速度(约33fps)
ani.interval = 15; // 2倍速
ani.interval = 60; // 0.5倍速
// 动态调整速度
function setSpeed(speed: number): void {
ani.interval = 30 / speed;
}
setSpeed(2.0); // 2倍速播放
5. 动画缓存优化
let ani = new Laya.Animation();
ani.loadAtlas("res/complex/complex.atlas");
// 启用缓存提高性能
ani.cacheAs = "bitmap";
// 播放完成后刷新缓存
ani.on(Laya.Event.COMPLETE, this, () => {
ani.reCache();
});
ani.play();
最佳实践
1. 资源预加载
使用 source 属性前需预加载图集:
// ❌ 错误:直接使用 source 可能失败
let ani = new Laya.Animation();
ani.source = "res/hero/hero.ani";
ani.play(); // 资源可能未加载
// ✅ 正确:先预加载资源
Laya.loader.load("res/hero/hero.atlas", Laya.Handler.create(this, () => {
let ani = new Laya.Animation();
ani.source = "res/hero/hero.atlas";
ani.play();
}));
2. 合理使用对象池
| 场景 | 是否使用对象池 | 原因 |
|---|---|---|
| 频繁播放的特效(爆炸、打击) | ✅ 推荐 | 减少创建开销 |
| 角色动作(长期存在) | ❌ 不必要 | 实例复用即可 |
| 一次性 UI 动画 | ❌ 不必要 | 播放完直接销毁 |
3. 及时清理资源
@regClass()
export class AnimationManager extends Laya.Script {
private animations: Laya.Animation[] = [];
public createAnimation(url: string): Laya.Animation {
let ani = new Laya.Animation();
ani.loadAtlas(url);
this.animations.push(ani);
return ani;
}
// 场景销毁时清理
public onDestroy(): void {
for (let ani of this.animations) {
ani.offAll();
ani.destroy();
}
this.animations.length = 0;
// 清理动画缓存
Laya.Animation.clearCache("res/hero.ani#");
}
}
4. 帧标签命名规范
// 使用清晰的标签命名
ani.addLabel("action_start", 0); // 动作开始
ani.addLabel("action_frame", 10); // 关键帧(如攻击判定)
ani.addLabel("action_end", 20); // 动作结束
// 按功能分类
ani.addLabel("walk_left_foot", 5);
ani.addLabel("walk_right_foot", 15);
ani.addLabel("attack_hit_frame", 8);
5. 性能优化建议
// 1. 复杂动画使用缓存
ani.cacheAs = "bitmap";
// 2. 屏幕外动画暂停渲染
ani.visible = false; // 或设置 viewport
// 3. 使用图集而非单图
ani.loadAtlas("res/ani.atlas"); // ✅ 推荐
// ani.loadImages([...单图...]); // ❌ 性能较差
// 4. 合理设置播放间隔
ani.interval = 30; // 不要过小,避免性能问题
6. 动画状态管理
@regClass()
export class AnimationState extends Laya.Sprite {
private ani: Laya.Animation;
private state: string = "idle";
constructor() {
super();
this.ani = new Laya.Animation();
this.ani.loadAtlas("res/hero/hero.atlas");
this.addChild(this.ani);
this.playIdle();
}
public changeState(newState: string): void {
if (this.state === newState) return;
this.state = newState;
switch (newState) {
case "idle":
this.ani.play(0, true, "idle");
break;
case "walk":
this.ani.play(0, true, "walk");
break;
case "attack":
this.ani.play(0, false, "attack");
this.ani.once(Laya.Event.COMPLETE, this, () => {
this.changeState("idle");
});
break;
case "die":
this.ani.play(0, false, "die");
break;
}
}
}
注意事项
- 资源预加载:使用
source设置 IDE 动画文件时,需先预加载图集资源 - 对象池复用:使用
clear()清理后可放入对象池复用 - 性能优化:复杂动画建议使用
cacheAs缓存 - 内存管理:大量动画实例时注意内存,及时销毁不需要的动画
- 帧索引范围:
index取值范围为0到count - 1 - 事件清理:回收动画前使用
offAll()移除事件监听 - 避免重复加载:相同资源只加载一次,通过
framesMap缓存复用
相关文档
更多推荐


所有评论(0)