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;
        }
    }
}

注意事项

  1. 资源预加载:使用 source 设置 IDE 动画文件时,需先预加载图集资源
  2. 对象池复用:使用 clear() 清理后可放入对象池复用
  3. 性能优化:复杂动画建议使用 cacheAs 缓存
  4. 内存管理:大量动画实例时注意内存,及时销毁不需要的动画
  5. 帧索引范围index 取值范围为 0count - 1
  6. 事件清理:回收动画前使用 offAll() 移除事件监听
  7. 避免重复加载:相同资源只加载一次,通过 framesMap 缓存复用

相关文档

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐