本帖最后由 哭唧唧的细狗 于 2024-7-9 14:35 编辑
功能描述
为对象通过可视化工具,添加关键帧,实现移动、旋转、缩放、显隐等等设置预览,并在游戏中通过脚本应用这些设定
效果演示
Editor 编辑使用说明
打开窗口
导入动画工具后,在视图中会出现名为 Animator 的入口
打开动画工具,其直接出现在工程内容并行的位置
UI 窗口中点击视图找到 Animator 入口
首次点击打开 Animator 窗口会悬浮在中间,将其拖入到 UI 窗口布局中后,该 UI 窗口会记住此布局
界面介绍
左侧工具栏
红点 1 从左至右
- 录制工具
- 选中第一个关键帧
- 选中上一个关键帧
- 播放
- 选中下一个关键帧
- 选中最后一个关键帧
- 时间轴选中时间对应的帧数
红点 2 从左至右
红点 3 - 添加属性
右侧时间轴
红点 1 - 关键帧选中位置拖动轴
红点 2 - 当前可调节的关键帧属性
红点 3
- close - 重置选中状态,清空 animator 界面
- save - 编辑了关键帧和事件的动画 clip 需要通过该按钮进行保存
红点 4 - 时间轴操作
- seconds - 以时间显示轴单位
- frames - 以帧显示轴单位
- frameRate - 时间轴里显示的帧数比例:包含30fps、60fps、120fps可选
- playMode - 分为stop、continue、loop三种
- stop 则在播放完成最后一帧停下来
- continue 则在播放完成后一直往下播,即使后边什么也没有
- loop 在播放完成后从头播放
显示帧数
播放模式
创建动画
在主视口或对象管理器选中任意对象:
该对象没有动画时,动画工具右侧 UI 界面显示创建按钮
已有动画时,则会直接显示动画时间轴
创建状态点击 create 按钮后弹出 create new clip 界面,可以修改创建的动画 clip 名字
点击确认按钮后会弹出路径选择框,当前必须选择将其放在 JavaScripts\lighter\assets\mwanim 路径下
编辑动画
新增属性
如下图展示了一个空的剪辑,左侧未显示任何属性。
点击 addproperty 按钮,弹出的 AddTrackPopupWindow 窗口里,能够选择想要增加的属性值,选中右侧“+号”即可新增节点
新增属性节点成功后,会在右侧时间轴里看到初始位置添加了对应的关键帧
编辑属性
右键选中已有的属性,此时会弹出操作框,当前能进行移除属性的操作,其它功能待添加
添加关键帧
方法 1:双击时间轴
方法 2:在时间轴上右键 add key
方法 3:属性修改后确定
删除关键帧
时间轴选中关键帧右键进行删除,如果删除没成功,可以尝试通过鼠标滚轮方法时间轴操作
录制关键帧
调整时间轴长短
通过鼠标滚轮滑动,能调节时间轴的长短,方便把握设置的整体和细节
添加事件
动画编辑器的案例中,天空盒开始的右下角名字显示,都是通过事件来做到的,事件能进行的操作包括:
- 新建编辑可在弹窗中设置事件名字及加入参数
- 双击已有事件可以进行编辑名字及参数
常用曲线
右键点击一个关键帧(xyz 节点)弹出窗口会包含 easing 曲线选择以及 cons、tangent、linear 选项。
- 默认选择是 linear,即一条斜向直线(当前没有默认选择上表现)
- tangent 是第二期将支持的自定义曲线
- const 是当前帧与下一帧之间效果不会缓动,在最后一帧瞬间切换
- easing 则是曲线集合,可与下下图效果对应(名字去掉 ease 和下划线)
https://easings.net/
插件运行时使用说明
环境注入
请将插件runtime文件zip,放入项目场景的根目录下解压即可
生命周期
初始化
export default class GameStart extends Script { /** 当脚本被实例后,会在第一帧更新前调用此函数 */ protected onStart(): void { if (SystemUtil.isClient()) { // 需要设置update为true this.useUpdate = true; // 初始化 lighter.asset.startLoad(); // 按键F1执行逻辑 InputUtil.onKeyDown(mw.Keys.F1, () => { // 播放UI的逻辑,具体看UI动画案例部分 this.playUIAnimate(); }) } }}
更新周期
update 可以传 dt,也可以固定帧率更新动画。
为了让动画不跳帧,推荐使用固定帧率播放
/*** 周期函数 每帧执行* 此函数执行需要将this.useUpdate赋值为true* @param dt 当前帧与上一帧的延迟 / 秒*/protected onUpdate(dt: number): void { lighter.animation.getGlobalAnimationManager().update(1 / 60);}
销毁
动画播放完毕后,调用destroy来回收Animator,以供下次复用
// 通过获取持有animatorUI的rootCanvas// 再传入RootCanvas以获取Animatorconst animator = lighter.animation.Animator.getAnimator(rootCanvas);// 销毁animator.destroy()
监听动画事件
当前有两种事件可监听
1.使用 Animator.onAnimationLifeEvent监听动画生命周期事件
2.使用Aniamtor.onAnimationEvent 监听用户在动画编辑器中配置的事件
动画生命周期包含:
Play - 开始播放时触发
Stop - 停止播放时触发
Pause - 暂停播放时触发
Lastframe - 当播放循环次数大于1时会在当次播放最后一帧触发
Finished - 动画完成播放时触发
控制动画的 API
参数释义
AnimationClip : 保存基于关键帧的动画,即文档里提到的动画片段,用于动画播放
AnimationState : 动画混合状态,用于衔接两段动画片段
export class Animator { static pools: Animator[]; static getAnimator(target: unknown): Animator; onAnimationEvent: mw.Action2<string, string[]>; onAnimationLifeEvent: mw.Action2<EventType, AnimationState>; /** * 播放动画片段 * @param animationClip 动画片段 | 动画片段的名字 */ play(animationClip: AnimationClip | string): void; // 暂停动画播放 pause(): void; // 恢复动画播放 resume(): void; /** * 动画混合 * 在一段动画播放过程中根据参数2的时间,间隔切换到名为name的动画混合 * @param name 动画混合的名字 * @param duration 间隔时间 */ crossFade(name: string, duration?: number): void; /** * 根据名字返回动画混合 * @param name 动画混合的名字 * @returns 动画混合 */ getAnimationState(name: string): AnimationState; /** * 通过动画片段创建一个动画混合 * 与addClip接口效果类似,但该接口能多个AnimationClip创建AnimationState * @param clip 动画片段 * @param name 动画混合的名字 * @returns 动画混合 */ createState(clip: AnimationClip, name?: string): AnimationState; /** * 通过名字移除一个动画混合 * @param clip 动画片段 */ removeState(name: string): void; /** * 给底层AnimationState容器添加一个动画片段 * 与createState接口效果类似,但该接口会对AnimationClip进行去重 * 可用于播放连着播放多段动画 * @param clip 动画片段 * @param name 动画混合的名字 * @returns 动画混合 */ addClip(clip: AnimationClip, name?: string): AnimationState; /** * 通过动画片段移除一个动画混合 * 当动画播放中时,强制停止会使得当前动画立刻停止,否则无效 * @param clip 动画片段 * @param force 强制停止 */ removeClip(clip: AnimationClip, force?: boolean): void; // 销毁 destroy(): void;}
UI 动画案例
保证 UI 文件夹下有相应名称的 UI 文件
创建及播放完整代码
private playUIAnimate() {// 代码创建对象并获取其RootCanvas const widget = createUIByPath('UI/ExampleAnimate.ui'); const rootCanvas = widget.findChildByPath("RootCanvas"); UIService.canvas.addChild(rootCanvas); // 通过传入RootCanvas获取Animator const animator = lighter.animation.Animator.getAnimator(rootCanvas); // 监听动画生命周期事件 animator.onAnimationLifeEvent.add(this.onAnimationLifeEvent, this); // 监听动画事件 animator.onAnimationEvent.add(this. , this); // 获取想要播放的动画片段。这里传入的是动画.ts文件的guid const clip = AnimationAssetMgr.getAnimationClipWithUUID('0761464D4C972C5B6E8A81BD96093C53'); // 播放加载的动画 animator.play(clip);}
1.创建 UI 并获取RootCanvas
2.通过给lighter.animation.Animator.getAnimator传入RootCanvas获取Animator
3.通过 AnimationAssetMgr 和动画 guid 获取创建好的动画片段
所有动画文件都存放在 JavaScripts/lighter/asssets/mwanim 目录下,文件中存放了 guid,该 guid 一般等于编辑器中脚本的 guid ,两种方式都可以获取 clip
方法 1:右键对应动画复制资源 ID
方法 2:打开对应目录下。ts 动画信息文件后找到 guid 一行
4.传入动画片段到animator.play(clip)播放动画
完整项目 Demo
|