【Laya】Animator2D 使用指南

Laya.Animator2D 使用指南

简介

Laya.Animator2D 是 LayaAir 引擎的 2D 动画组件,用于控制 2D 精灵的动画播放。通过动画状态机(AnimatorController2D)管理多个动画状态的切换,支持参数控制、混合过渡、多层动画等高级功能。

适用场景

  • 角色动画状态管理(待机、行走、攻击、死亡等)
  • 复杂动画切换和过渡
  • 参数驱动的动画控制
  • 多层动画混合(如下半身行走 + 上半身攻击)

继承关系

复制代码
Laya.Animator2D → Laya.Component → Laya.Object

目录


API 参考

属性

属性 类型 默认值 说明
controller AnimatorController2D - 2D 动画控制器
parameters Record<string, Animation2DParm> - 动画参数集合
speed number 1.0 播放速度倍率
isPlaying boolean - 是否正在播放(只读)

播放控制

方法 参数 返回值 说明
play(name?, layerIndex?, normalizedTime?) 状态名, 层索引, 归一化时间 void 播放指定动画状态
stop() - void 停止播放动画
crossFade(name, layerIndex, normalizedTime, transitionDuration?) 状态名, 层索引, 起始时间, 过渡时间 boolean 融合过渡到目标状态
gotoAndStop(name, layerIndex, normalizedTime) 状态名, 层索引, 归一化时间 void 跳转到指定时间并停止
gotoAndStopByFrame(name, layerIndex, frame) 状态名, 层索引, 帧号 void 跳转到指定帧并停止

参数控制

方法 参数 返回值 说明
setParamsTrigger(name) 参数名(字符串或数字) void 触发 Trigger 类型参数
setParamsBool(name, value) 参数名, 布尔值 void 设置 Bool 类型参数
setParamsNumber(name, value) 参数名, 数值 void 设置 Number 类型参数
getParamsvalue(name) 参数名(字符串或数字) `number boolean`

层管理

方法 参数 返回值 说明
addControllerLayer(controllerLayer) 控制器层 void 添加动画控制器层
getControllerLayer(layerIndex?) 层索引 AnimatorControllerLayer2D 获取控制器层
getDefaultState(layerIndex?) 层索引 - 获取默认状态

生命周期

方法 参数 返回值 说明
onUpdate() - void 每帧更新(由引擎自动调用)

相关类型

AnimatorController2D - 动画控制器

typescript 复制代码
// 动画控制器,管理动画状态机
let controller = new Laya.AnimatorController2D(data);
animator.controller = controller;

AnimatorState2D - 动画状态

属性 类型 说明
name string 状态名称
clip AnimationClip2D 动画片段
speed number 播放速度
loop number 循环次数(-1=跟随clip, 0=无限, 1=一次)
yoyo boolean 是否往返播放
clipStart number 播放起始时间(0-1)
clipEnd number 播放结束时间(0-1)
cycleOffset number 播放起始偏移

状态事件

事件 说明
EVENT_OnStateEnter 进入状态时触发
EVENT_OnStateUpdate 状态更新时触发
EVENT_OnStateExit 退出状态时触发
EVENT_OnStateLoop 状态循环时触发
EVENT_OnStateSwitch 状态切换时触发

基础用法

1. 添加动画组件

typescript 复制代码
@regClass()
export class Hero extends Laya.Sprite {
    private animator: Laya.Animator2D;

    onAwake(): void {
        // 添加动画组件
        this.animator = this.addComponent(Laya.Animator2D);
    }
}

2. 设置动画控制器

typescript 复制代码
// 加载动画控制器资源
Laya.loader.load("res/hero/HeroController.controller", Laya.Handler.create(this, (controller: Laya.AnimatorController2D) => {
    this.animator.controller = controller;
    this.animator.play();  // 播放默认动画
}));

3. 播放指定动画

typescript 复制代码
// 播放名为 "Walk" 的动画状态
this.animator.play("Walk");

// 播放第 0 层的 "Run" 动画
this.animator.play("Run", 0);

// 从动画中间开始播放(normalizedTime: 0.5 = 50% 位置)
this.animator.play("Attack", 0, 0.5);

4. 停止动画

typescript 复制代码
// 停止所有动画
this.animator.stop();

// 跳转到指定时间并停止
this.animator.gotoAndStop("Idle", 0, 0.0);

// 跳转到指定帧并停止
this.animator.gotoAndStopByFrame("Attack", 0, 10);

5. 融合过渡

typescript 复制代码
// 融合过渡到 "Run" 状态,过渡时间为 0.3 秒
this.animator.crossFade("Run", 0, 0.0, 0.3);

实用示例

示例1: 角色状态机

typescript 复制代码
@regClass()
export class HeroController extends Laya.Script {
    @property(Laya.Animator2D)
    public animator: Laya.Animator2D = null;

    private speed: number = 0;

    onAwake(): void {
        // 加载动画控制器
        Laya.loader.load("res/hero/HeroController.controller", Laya.Handler.create(this, (controller: Laya.AnimatorController2D) => {
            this.animator.controller = controller;
            this.playIdle();
        }));
    }

    // 待机状态
    public playIdle(): void {
        this.animator.setParamsBool("isMoving", false);
        this.animator.setParamsBool("isAttacking", false);
    }

    // 移动状态
    public playWalk(): void {
        this.animator.setParamsBool("isMoving", true);
        this.animator.setParamsNumber("speed", 1.0);
    }

    public playRun(): void {
        this.animator.setParamsBool("isMoving", true);
        this.animator.setParamsNumber("speed", 2.0);
    }

    // 攻击状态
    public playAttack(): void {
        this.animator.setParamsTrigger("attack");
    }

    // 死亡状态
    public playDie(): void {
        this.animator.setParamsBool("isDead", true);
    }

    // 设置移动速度
    public setSpeed(value: number): void {
        this.speed = value;
        this.animator.setParamsNumber("speed", value);

        if (value > 0) {
            this.animator.setParamsBool("isMoving", true);
        } else {
            this.animator.setParamsBool("isMoving", false);
        }
    }
}

示例2: 动画事件监听

typescript 复制代码
@regClass()
export class AnimationEventListener extends Laya.Script {
    @property(Laya.Animator2D)
    public animator: Laya.Animator2D = null;

    onAwake(): void {
        this.setupAnimationEvents();
    }

    private setupAnimationEvents(): void {
        let controller = this.animator.controller;

        // 获取动画状态
        let attackState = this.getAnimatorState("Attack");

        if (attackState) {
            // 监听状态进入事件
            attackState.on(Laya.AnimatorState2D.EVENT_OnStateEnter, this, () => {
                console.log("进入攻击状态");
            });

            // 监听状态更新事件
            attackState.on(Laya.AnimatorState2D.EVENT_OnStateUpdate, this, () => {
                console.log("攻击状态更新");
            });

            // 监听状态退出事件
            attackState.on(Laya.AnimatorState2D.EVENT_OnStateExit, this, () => {
                console.log("退出攻击状态");
            });
        }
    }

    private getAnimatorState(name: string): Laya.AnimatorState2D {
        // 通过层获取状态
        let layer = this.animator.getControllerLayer(0);
        if (layer && layer.states) {
            for (let state of layer.states) {
                if (state.name === name) {
                    return state;
                }
            }
        }
        return null;
    }
}

示例3: 平滑动画过渡

typescript 复制代码
@regClass()
export class SmoothTransition extends Laya.Script {
    @property(Laya.Animator2D)
    public animator: Laya.Animator2D = null;

    // 使用 crossFade 实现平滑过渡
    public transitToWalk(): void {
        // 在 0.2 秒内过渡到 Walk 状态
        this.animator.crossFade("Walk", 0, Number.NEGATIVE_INFINITY, 0.2);
    }

    public transitToRun(): void {
        // 在 0.15 秒内过渡到 Run 状态
        this.animator.crossFade("Run", 0, Number.NEGATIVE_INFINITY, 0.15);
    }

    public transitToAttack(): void {
        // 攻击动画快速过渡(0.05 秒)
        this.animator.crossFade("Attack", 0, 0.0, 0.05);
    }

    public transitToIdle(): void {
        // 较慢的过渡到待机(0.3 秒)
        this.animator.crossFade("Idle", 0, Number.NEGATIVE_INFINITY, 0.3);
    }
}

示例4: 多层动画混合

typescript 复制代码
@regClass()
export class LayerAnimation extends Laya.Script {
    @property(Laya.Animator2D)
    public animator: Laya.Animator2D = null;

    onAwake(): void {
        this.setupLayers();
    }

    private setupLayers(): void {
        // Base Layer - 控制下半身动画
        // 已在控制器中配置

        // 添加 UpperBody 层 - 控制上半身动画
        let upperLayer = new Laya.AnimatorControllerLayer2D("UpperBody");
        upperLayer.blendingMode = Laya.AnimatorControllerLayer2D.BLENDINGMODE_ADDTIVE;
        this.animator.addControllerLayer(upperLayer);

        // 播放下半身行走动画
        this.animator.play("Walk", 0);  // Base Layer

        // 同时播放上半身攻击动画
        this.animator.play("Attack", 1);  // UpperBody Layer
    }

    // 下半身行走,上半身攻击
    public walkAndAttack(): void {
        this.animator.crossFade("Walk", 0, Number.NEGATIVE_INFINITY, 0.2);
        this.animator.crossFade("Attack", 1, 0.0, 0.1);
    }

    // 下半身站立,上半身瞄准
    public idleAndAim(): void {
        this.animator.crossFade("Idle", 0, Number.NEGATIVE_INFINITY, 0.2);
        this.animator.crossFade("Aim", 1, 0.0, 0.3);
    }
}

示例5: 动画速度控制

typescript 复制代码
@regClass()
export class AnimationSpeedControl extends Laya.Script {
    @property(Laya.Animator2D)
    public animator: Laya.Animator2D = null;

    // 慢动作
    public setSlowMotion(): void {
        this.animator.speed = 0.5;  // 0.5 倍速
    }

    // 正常速度
    public setNormalSpeed(): void {
        this.animator.speed = 1.0;  // 1.0 倍速
    }

    // 快进
    public setFastForward(): void {
        this.animator.speed = 2.0;  // 2.0 倍速
    }

    // 暂停动画
    public pause(): void {
        this.animator.speed = 0;
    }

    // 根据输入动态调整速度
    public updateSpeed(inputValue: number): void {
        // inputValue 范围 0~1,映射到速度 0.5~2.0
        this.animator.speed = 0.5 + inputValue * 1.5;
    }
}

高级技巧

1. 动画参数驱动

typescript 复制代码
// 通过参数控制状态切换
let animator = sprite.getComponent(Laya.Animator2D);

// 设置 Bool 参数
animator.setParamsBool("isGrounded", true);
animator.setParamsBool("hasWeapon", true);

// 设置 Number 参数
animator.setParamsNumber("moveSpeed", 5.0);
animator.setParamsNumber("health", 100);

// 触发 Trigger 参数(一次性触发)
animator.setParamsTrigger("jump");
animator.setParamsTrigger("fire");

2. 获取当前动画状态

typescript 复制代码
// 获取控制器层信息
let layer = animator.getControllerLayer(0);
if (layer) {
    let playState = layer.getCurrentPlayState();
    if (playState && playState.animatorState) {
        console.log("当前状态:", playState.animatorState.name);
    }
}

3. 动画状态脚本

typescript 复制代码
// 在 AnimatorState2D 上添加脚本
@regClass()
export class AttackStateScript extends Laya.AnimatorState2DScript {
    // 状态进入时调用
    onStateEnter(): void {
        console.log("攻击开始");
        // 创建攻击判定
    }

    // 状态更新时调用
    onStateUpdate(): void {
        // 每帧检测
    }

    // 状态退出时调用
    onStateExit(): void {
        console.log("攻击结束");
        // 清理攻击判定
    }

    // 状态循环时调用(动画循环一次)
    onStateLoop(): void {
        console.log("攻击动画循环");
    }
}

// 在状态上添加脚本
let attackState = this.getAnimatorState("Attack");
attackState.addScript(AttackStateScript);

4. 条件过渡检测

typescript 复制代码
// 根据条件判断是否可以切换状态
public canTransitToAttack(): boolean {
    // 检查当前是否在播放特定动画
    let layer = this.animator.getControllerLayer(0);
    if (!layer) return false;

    let playState = layer.getCurrentPlayState();
    if (!playState || !playState.animatorState) return true;

    let currentStateName = playState.animatorState.name;

    // 如果当前在受伤状态,不能攻击
    if (currentStateName === "Hurt") {
        return false;
    }

    // 如果当前在死亡状态,不能攻击
    if (currentStateName === "Die") {
        return false;
    }

    return true;
}

5. 动画完成回调

typescript 复制代码
@regClass()
export class AnimationCallback extends Laya.Script {
    @property(Laya.Animator2D)
    public animator: Laya.Animator2D = null;

    private onComplete: Laya.Handler = null;

    // 播放一次性动画并监听完成
    public playOnce(animName: string, callback: Laya.Handler): void {
        this.onComplete = callback;

        let state = this.getAnimatorState(animName);
        if (state) {
            state.once(Laya.AnimatorState2D.EVENT_OnStateExit, this, this.onAnimationComplete);
        }

        this.animator.play(animName);
    }

    private onAnimationComplete(): void {
        if (this.onComplete) {
            this.onComplete.run();
            this.onComplete = null;
        }
    }
}

最佳实践

1. 状态机设计原则

原则 说明
单一职责 每个状态只负责一个明确的行为
最小化状态 避免创建过多相似的状态
参数驱动 优先使用参数控制,而非硬编码状态切换
明确过渡 每个状态应有清晰的进入/退出条件

2. 动画层使用建议

typescript 复制代码
// Layer 0: Base Layer - 基础动画
// 用途:待机、行走、跑步、跳跃等全身动画
animator.play("Walk", 0);

// Layer 1: Upper Body - 上半身动画
// 用途:瞄准、射击、挥砍等上半身动作
animator.play("Attack", 1);

// Layer 2: Face - 面部表情
// 用途:眨眼、说话口型等面部动画
animator.play("Talking", 2);

3. 性能优化

typescript 复制代码
// 1. 减少不必要的状态检查
// ❌ 每帧检查所有参数
onUpdate(): void {
    this.animator.setParamsBool("condition1", this.check1());
    this.animator.setParamsBool("condition2", this.check2());
    this.animator.setParamsBool("condition3", this.check3());
}

// ✅ 只在条件变化时设置
onConditionChanged(): void {
    this.animator.setParamsBool("condition1", this.check1());
}

// 2. 合理使用动画缓存
animator.controller.enableCache = true;

// 3. 避免频繁创建/销毁组件
// 预先添加组件,通过 play/stop 控制

4. 参数命名规范

typescript 复制代码
// 使用清晰的前缀区分参数类型
// is/has - Bool 类型
animator.setParamsBool("isGrounded", true);
animator.setParamsBool("hasWeapon", true);
animator.setParamsBool("isDead", false);

// 数值 - Number 类型
animator.setParamsNumber("speed", 5.0);
animator.setParamsNumber("health", 100);
animator.setParamsNumber("direction", 1);

// 动作 - Trigger 类型
animator.setParamsTrigger("jump");
animator.setParamsTrigger("attack");
animator.setParamsTrigger("interact");

5. 调试技巧

typescript 复制代码
// 查看当前状态
let layer = animator.getControllerLayer(0);
if (layer) {
    let playState = layer.getCurrentPlayState();
    if (playState && playState.animatorState) {
        console.log("当前状态:", playState.animatorState.name);
    }
}

// 查看所有参数
let params = animator.parameters;
for (let key in params) {
    console.log(key, ":", params[key]);
}

6. 常见状态机结构

复制代码
HeroAnimatorController
├── Base Layer (Layer 0)
│   ├── Idle (默认状态)
│   ├── Walk (条件: isMoving && speed < 2)
│   ├── Run (条件: isMoving && speed >= 2)
│   ├── Jump (条件: trigger "jump")
│   ├── Fall (条件: !isGrounded)
│   ├── Attack (条件: trigger "attack")
│   ├── Hurt (条件: trigger "hurt")
│   └── Die (条件: isDead)
├── Upper Body Layer (Layer 1)
│   ├── None (默认)
│   ├── Aim (条件: isAiming)
│   └── Shoot (条件: trigger "shoot")
└── Face Layer (Layer 2)
    ├── Neutral (默认)
    ├── Happy (条件: mood > 0.5)
    └── Angry (条件: mood < -0.5)

注意事项

  1. 资源预加载 :使用 AnimatorController2D 前需先加载 .controller 资源
  2. 参数类型匹配:设置参数时确保类型与控制器定义一致(Bool/Number/Trigger)
  3. 归一化时间normalizedTime 取值范围为 01Number.NEGATIVE_INFINITY 表示从头播放
  4. 层索引 :层索引从 0 开始,确保添加层后再访问
  5. Trigger 特性:Trigger 参数触发后会被自动重置,适合一次性事件
  6. 性能考虑:过多动画层会影响性能,建议不超过 3 层
  7. 状态脚本 :使用 AnimatorState2DScript 实现状态内逻辑,避免在主逻辑中频繁查询

相关文档

相关推荐
web小白成长日记3 小时前
从零起步,用TypeScript写一个Todo App:踩坑与收获分享
前端·javascript·typescript
速冻鱼Kiel4 小时前
GASP笔记02
笔记·ue5·游戏引擎·虚幻
__water4 小时前
RHK《Unity接入PicoSDK入门》
unity·游戏引擎·picosdk
我的golang之路果然有问题5 小时前
unity 资源导入 godot
unity·游戏引擎·godot
南村群童欺我老无力.5 小时前
Flutter 框架跨平台鸿蒙开发 - 打造一款精美的手机日历应用
flutter·华为·typescript·harmonyos
迪普阳光开朗很健康5 小时前
Unity+Vscode+EmmyLua+XLua 调试实战
vscode·unity·游戏引擎
南村群童欺我老无力.5 小时前
Flutter 框架跨平台鸿蒙开发 - 打造精美记事本备忘录应用
flutter·华为·typescript·harmonyos
Howrun7775 小时前
虚幻引擎_UI搭建流程
c++·游戏引擎·虚幻
CreasyChan6 小时前
Unity 中的 IEnumerator协程详解
unity·c#·游戏引擎