Cocos Animator–可视化动画状态机编辑器 https://forum.cocos.org/t/topic/104944
Go to file
2021-01-24 19:29:37 +08:00
animator-editor fix:编辑器文件导入. add:example,runtime 2021-01-24 19:29:37 +08:00
animator-runtime fix:编辑器文件导入. add:example,runtime 2021-01-24 19:29:37 +08:00
examples fix:编辑器文件导入. add:example,runtime 2021-01-24 19:29:37 +08:00
images fix:编辑器文件导入. add:example,runtime 2021-01-24 19:29:37 +08:00
README.md fix:编辑器文件导入. add:example,runtime 2021-01-24 19:29:37 +08:00

Cocos Animator

一个用于Cocos Creator的可视化动画状态机编辑器。
编辑器地址:https://leeyip.github.io/cocos-animator/

目录

前言

当动画数量较多且转换规则复杂的时候手动编写代码去处理转换逻辑会非常痛苦。用过Unity的话都知道Unity的Animator Controller能可视化地编辑状态机在一定程度上减轻了手写逻辑的痛苦但是Cocos Creator目前没有提供这种便利的工具于是便有了这个项目。
动手之前,在网上看到已经有人做了一个这种工具[1]但离我想要的效果差了一点以及runtime的实现也有一些问题同时我也希望能随时根据需求自由更改功能于是便用Cocos Creator另写了一个状态机编辑器runtime的实现也在他的基础上进行了修改。

项目结构

  • animator-editor状态机编辑器项目源码使用CocosCreator2.2.2实现
  • animator-runtime解析状态机的runtime源码里面两个目录分别用于CocosCreator2.x的项目与3.0项目
  • examples用CocosCreator2.2.2与3.0rc写的简单例子

状态机编辑器

操作基本和Unity Animator类似

总览

编辑器界面分为三个部分 image

  • 左侧用于编辑状态机参数参数分为number、boolean、trigger、auto trigger四种类型。
  • 中间用于编辑状态、子状态机以及状态之间的连线关系上侧的导航栏用于表示当视图状态机的层级关系BaseLayer为根状态机。
  • 右侧用于编辑状态与状态转换连线的数据。

状态机视图

image image image

  • 操作方式:
    • 鼠标滚轮可缩放以及拖动视图
    • 鼠标右键空白处可选择生成状态或子状态机节点
    • 鼠标右键状态可选择生成状态转换连线、设置默认状态、删除
    • 鼠标左键按住状态拖动到状态机上出现加号,即可将状态丢入状态机内
    • 鼠标右键子状态机可选择删除
    • 鼠标双击子状态机可进入子状态机视图
    • 鼠标左键选中状态、子状态机、连线时均可用delete键删除
    • 鼠标左键可以拉伸调节Parameters和Inspector两个界面宽度
    • 有上下双向箭头的地方是可拖拽排序的列表
  • 状态用矩形表示橙色为默认状态是整个状态机运行的入口。AnyState是一种殊的状态用于指代任意状态仅能由AnyState将连线指向其余状态无法将连线指向AnyState。
  • 子状态机:用六边形表示,内部包含默认状态则显示为橙色,名字中带有(up)的状态机为当前视图的父状态机。

Parameters

用于编辑状态机参数,可填写初始值表示状态机开始运行时的参数状态

  • number 数字输入框
  • boolean 方形勾选框
  • trigger 圆形勾选框与Unity中trigger表现一致内部是一个boolean在满足包含trigger条件的状态转换时会自动resetTrigger
  • auto trigger 菱形勾选框在满足状态转换时以及每帧状态机逻辑更新时都会自动reset为false

Inspector

用于编辑状态以及连线数据

  • Name--状态/状态机名
  • Motion--状态对应的动画名
  • Speed--动画播放速度
  • Multiplier--动画播放速度混合的number类型参数
  • Loop--动画是否循环播放
  • Transitions(连线数据):
    • 记录状态间的转换关系(当前状态->目标状态)
    • 优先级同一个状态有多个transition的情况下优先满足哪一个就先转向对应的目标状态若同时满足多个transition则以列表中从上往下的排序决定优先级选中状态时才可以拖拽排序
    • Has Exit Time打勾则表示会等待动画播放结束才能转向下一状态
    • Conditions转换条件所有条件都是逻辑与的关系必须同时满足所选择的逻辑关系若勾选了Has Exit Time还需等待动画播放完毕才能转向下一状态

数据导入与导出

  • 导出数据
    • ctrl+s--导出状态机编辑器工程文件此文件记录了编辑器与runtime所需的所有数据此文件必须保存好
    • ctrl+e--仅导出runtime所需的数据文件
  • 导入数据
    • 将ctrl+s所保存文件拖入状态机编辑器内将还原整个状态机视图
    • cocos creator的animation文件、spine的json文件、dragonbones的json文件均可拖入状态机编辑器内自动生成对应的状态节点 image

导出的数据格式

  • 状态机编辑器工程数据格式
{
    /** 编辑器版本号 */
    animator: string;
    parameters: [
        {
            /** 参数名 */
            param: string;
            /** 参数类型 */
            type: ParamType;
            /** 初始值 */
            init: number;
        }
    ];
    mainStateMachine: {
        layerPos: [number, number];
        layerScale: number;
        anyStatePos: [number, number];

        subStates: string[];
        subStateMachines: string[];
    };
    subStateMachines: [
        {
            /** 此状态机视图坐标 */
            layerPos: [number, number];
            /** 此状态机视图缩放 */
            layerScale: number;
            /** 此状态机视图内AnyState坐标 */
            anyStatePos: [number, number];

            /** 状态机名 **/
            name: string;
            /** 在父状态机视图下的坐标 */
            position: [number, number];
            /** 父状态机 **/
            upStateMachine: string;
            /** 在此状态机视图内父状态机的坐标 */
            upStateMachinePos: [number, number];
            /** 子状态 */
            subStates: string[];
            /** 子状态机 */
            subStateMachines: string[];
        }
    ];
    defaultState: string;
    anyState: {
        transitions: [
            {
                toState: string;
                hasExitTime: boolean;
                conditions: [
                    {
                        param: string;
                        value: number;
                        logic: LogicType;
                    }
                ]
            }
        ]
    };
    states: [
        {
            /** 在父状态机视图下的坐标 */
            position: [number, number];
            /** 父状态机 **/
            upStateMachine: string;

            /** 状态名 */
            state: string;
            /** 动画名 */
            motion: string;
            /** 动画播放速度 */
            speed: number;
            /** 动画播放速度混合参数 */
            multiplier: string;
            /** 动画是否循环播放 */
            loop: boolean;
            /** 转向别的状态的连线 */
            transitions: [
                {
                    /** 目标状态名 */
                    toState: string;
                    /** 是否需等动画播放完毕才可转换 */
                    hasExitTime: boolean;
                    /** 转换需满足的参数条件 */
                    conditions: [
                        {
                            /** 此条件对应的参数名 */
                            param: string;
                            /** 此条件对应的值 */
                            value: number;
                            /** 此条件与值比较的逻辑 */
                            logic: LogicType;
                        }
                    ]
                }
            ]
        }
    ];
}
  • runtime解析所需的数据格式
{
    parameters: [
        {
            param: string;
            type: ParamType;
            init: number;
        }
    ];
    defaultState: string;
    anyState: {
        transitions: [
            {
                toState: string;
                hasExitTime: boolean;
                conditions: [
                    {
                        param: string;
                        value: number;
                        logic: LogicType;
                    }
                ]
            }
        ]
    };
    states: [
        {
            state: string;
            motion: string;
            speed: number;
            multiplier: string;
            loop: boolean;
            transitions: [
                {
                    toState: string;
                    hasExitTime: boolean;
                    conditions: [
                        {
                            param: string;
                            value: number;
                            logic: LogicType;
                        }
                    ]
                }
            ]
        }
    ];
}

Runtime

只要将对应版本的runtime整个文件夹丢入项目中即可使用。
core中是runtime的核心实现而AnimatorAnimation、AnimatorSpine、AnimatorDragonBones、AnimatorCustomization是用于与节点绑定的cc.Component脚本分别用于不同类型的动画。

  • Animator脚本组件 API
    • onInit 手动初始化状态机
    • manualUpdate 手动调用更新
    • setBool 设置boolean类型参数的值
    • getBool 获取boolean类型参数的值
    • setNumber 设置number类型参数的值
    • getNumber 获取number类型参数的值
    • setTrigger 设置trigger类型参数的值
    • resetTrigger 重置trigger类型参数的值
    • autoTrigger 设置autoTrigger类型参数的值
    • play 无视条件直接跳转状态
    • curStateName 当前状态名
    • curStateMotion 当前动画名

Example

image

  • AssetRawUrl 拖入状态机编辑器导出的文件两种文件均可但ctrl+e导出的文件更小
  • PlayOnStart 如果勾选则自动初始化状态机否则需要主动调用onInit去初始化
  • AutoUpdate 如果勾选则自动每帧更新状态机逻辑否则需要主动调用manualUpdate去更新

绑定脚本后只需要在对应的点设置参数值,就可以靠参数去驱动状态机进行状态转换了

this.Animator.setNumber('speed', this.speed);
this.Animator.setTrigger('hit');

执行以下操作时必须取消勾选PlayOnStart主动调用onInit去初始化onInit可传入0-3个不同类型的参数传参不需要区分先后顺序

  1. 如果需要每个状态都有单独的逻辑回调onEntry、onUpdate、onExit
// 单个状态的逻辑控制类
export default class SheepIdle extends AnimatorStateLogic {
    public onEntry() {
        cc.log('idle entry');
    }
    public onUpdate() {
        cc.log('idle update');
    }
    public onExit() {
        cc.log('idle exit');
    }
}
let map: Map<string, AnimatorStateLogic> = new Map();
map.set('sheep_idle', new SheepIdle());
map.set('sheep_run', new SheepRun());
map.set('sheep_hit', new SheepHit(this));
this.Animator.onInit(map);
  1. 如果需要监听状态切换
this.Animator.onInit((fromState: string, toState: string) => {
    cc.log(`state change: ${fromState} -> ${toState}`);
});
  1. 如果使用自定义的方式播放动画AnimatorCustomization
/**
 * 自定义控制动画播放的接口
 */
export interface AnimationPlayer {
    /** 设置动画播放结束的回调 */
    setFinishedCallback(callback: () => void, target: any): void;
    /** 播放动画 */
    playAnimation(animName: string, loop: boolean): void;
    /** 缩放动画播放速率 */
    scaleTime(scale: number): void;
}
this.Animator.onInit(AnimationPlayer);

如果需要使用spine在不同的trackindex播放动画可以用AnimatorSpine和AnimatorSpineSecondary挂在同一个spine节点上实现并行状态机

image
AnimatorSpine为主状态机trackindex为0负责播放walk、run、jump三个动画
AnimatorSpineSecondary为次状态机trackindex为1负责播放shoot动画
编辑好两个不同的状态机,运行时便可将实现播放别的动画的同时开枪
image
ps但实际项目中如果需要使用并行状态机往往比这会复杂很多很可能需要你自行修改runtime的实现

注意点

首先虽然名为动画状态机但实际上把动画剥离开仅仅用作一个纯粹的逻辑上的状态机也是没有问题的只需要编辑状态机时不填入动画名Motion然后调用onInit传入逻辑控制参数即可。

  • 参数名、子状态机名、状态名都不可重名
  • 目前不支持状态转向自身
  • Parameters和Inspector两个界面如果内容超出请使用鼠标滚轮滚动因为为了防止与拖拽行为产生冲突触摸事件已被屏蔽
  • 当未勾选Has Exit Time以及没有添加任何condition时此transition会被忽略
  • 状态机组件添加了@executionOrder(-1000)
  • spine的动画播放速度完全由主状态机控制
  • 由于cocos中spine的setCompleteListener内部实现问题每次调用会直接覆盖之前的回调所以如果需要监听动画播完请使用AnimatorSpine中addCompleteListener等api
  • 目前不支持动画混合例如调用spine的setMix会影响动画结束Has Exit Time的判定所以如果需要做spine、dragonbones、cocos animation这些动画混合都需要自行处理

参考资料

  1. https://blog.csdn.net/vikingsc2007_1/article/details/81070029
  2. https://docs.unity3d.com/2018.4/Documentation/Manual/AnimatorControllers.html