3.X 关于Animation组件的问题、研究和建议【爱恨交织cocos】

  • Creator 版本: 3.6

个人水平:策划转的程序,刚刚入门级程序

末尾有关于动画编辑器的交互改进建议.

前文提要:前几天用的是2.4.9版本,在使用animation的defaultClip时遇到了坑,无法通过代码动态设定defaultClip,只能在面板里修改和拖动,前后耽搁快2个小时,刚好听了3.6的发布会,随决定升级到3.6体验全新的cocos。

打开3.6,animation的defaultClip已经可以通过代码动态指认了,这样我直接play就可以运行动画,不用再去play(clipName)的方式播放了。感觉3.6 比2.x强很多,bug少多了,很激动继续去做animation相关逻辑。

坑又来了!!!

坑一 直接操作Animation.clips: animationClip[]

  1. clip.push(clisA) 播放失败

因为我是手动添加的clip,所以很自然找到了animation.clips这个属性(引擎面板可见),很愉快的通过代码animation.clips.push(newClip)

animation.clips.push(clipA);
animation.play(clipA.name);

结果play无效,我还以为又是defaultClip的事,研究半天,努力把过程中的变化都log出来,慢慢翻查,时间滴答滴答又过去了…还是找不到答案,打印出来的animation.clips里面明明有我新加入的clipA。
翻查文档,研究来研究去,发现有个animationState的东西,来控制相关播放逻辑,于是继续尝试。

  1. animationState 你在哪?

const animStateClipA = animation.getState(clipA.name);
log(animStateClipA);
打印结果: null

奇怪了,我的clipA的animationState去哪了?翻看文档跟API,都没找到相应答案。
自己写代码尝试,终于发现,当我

animation.defaultClip = clipA

是有创建对应的animationState的,同时我在属性面板里向clips拖进新的clip,也是有对应state产生的,看来问题还是处在animation.clips.push(clipA)这一步上。用代码增加新的clip应该是很常规的操作,总不能大家都遇到这个问题而无法解决吧?可能是我哪里弄错了。
继续深入研究。
时间滴答滴答又过去了…

我终于发现有个方法叫做animation.addClip!!!使用这个方法增加的clip,是能够Play成功的! 也就是它创建了新的state!!!但是文档里并没有写。

animatnion.clips能够get/set,所以我直观认为向一个clip[]对象直接push是很正常的行为,没料到这一步让我跳过了state相关的操作。

是文档不够详细?是代码提示不够?是我i水平太菜?还是设计问题?
生活还要继续,游戏还要继续做,先继续前行吧。

  1. AnimationClip和AnimationState 谁是老大?

好了,前面解决了什么时候创建state的问题,接下来去看了下state的属性,发现也有speed,还有duration跟time,感觉这两个都是时间的意思,查了下还有current。。。头都大了,这3个有什么区别?

clip.speed跟state.speed是什么关系?谁覆盖谁?研究后发现,创建state时speed以clip的为准,创建后state.speed就只跟自己的设定有关,哪怕clip.speed再更改也跟它无关。

setTime似乎是设定的时间跟speed没有关系,只跟原始clip帧数据相关,跟state的speed和clip的speed好像都无关。

time好像是一个累积播放时间,相关操作会重置。

坑二 谁来管管离家出走的animationState ?

发现这个state很好玩,就深入多研究了一会,发现animatnion有个方法叫做removeState,前面研究clip跟state的时候,把state单独引用出来了(代码水平低,大概这个解释)

let animStateClipA = animatnion.getState(clipA.name);
animation.removeState(clipA.name);
animStateClipA.play();

程序竟然还能跑!虽然animatnion里已经没有了stateClipA, 但是可能因为state里有clip相关数据,所以还是可以继续运行的! 震惊了,难道创建完state,animation就管不住state了啊。那如果他们冲突了怎么办?

animation.removeClip(clipA);
animation.addClip(clipA); // 删除再增加,就创建了新的animatnionState;
animation.play(clipA.name);

疑问: /这个时候,如果前面的state也在播放,2个play是谁覆盖谁的关系?还是后面运行覆盖前面的?还是跟两个state的创建顺序相关?这个一直没验证清楚。

坑三 要不我去看下源代码?

使用log跟不断打断点,还有写验证逻辑去一点点推测代码内部细节,实在是一件很耗时的事情。前前后后零零散散大概从周四晚上研究到周六晚上。决定还是去看下cocos代码,也许很多问题看了逻辑就清楚了,不需要自己写代码推测验证。

  • 研究怎么安装cocos引擎环境
  • 研究怎么git拉不下来cocos代码
  • 研究是不是因为我没有梯子的原因才拉不下代码,如果弄梯子?
  • 找别人帮忙从引擎版本安装文件里找到了代码,翻看了下,animation相关代码一大堆,animationState继承自playable,animation-clip代码1500行! animation代码在哪我却找不到…这逻辑跟我想的不太一样啊,太复杂了。。。

学习和研究cocos的乐趣真是…太庞大和深邃了啊

animation的旅程暂时就到这,毕竟还要去写其他的游戏逻辑,这里面真是有很多细节如果不留意,很容易在未来开发中埋坑,需要更多的时间来排查和修复。

认真把游戏的模块研究透彻,避免开发中出现问题是对每一个尽职尽责开发者的基本要求。但是也希望引擎后续提供更加安全的接口和更加完备详细的文档,让新手开发者甚至新手程序能够顺利入坑cocos!

使用animation动画编辑器时想到的优化建议

在初次使用3.6动画编辑器时,新增要调整的属性,结果没有任何反应,研究半天,才知道是下面属性列表默认关闭状态,并且新增要调整属性后,也不会自动展开,导致编辑器界面没有任何变化,让人误解,希望后续新增属性时,面板能够自动展开,一个是给出操作反馈,另外也方面后续调整。
cocos_动画属性窗口默认不展开

1赞

内部已反馈给动画系统团队

clips.push 不行,因为需要监听到 clips 访问器的变动。所以需要 clips = clips.concat(…)。又因为这可能比较多会用到,所以加了个 addClip 方法。
这个组件文档中有说到它会管理一组clips,并为他们创建相应的 state,每一个clip 都会创建一个,要获取对应的 state,就用 getState 传入名字获取

至于clip.speed 和 state.speed,以及 wrapMode等属性:state就是clip的一个播放状态,播放速度会初始化为clip.speed,但是后面state.speed要是变了,不会影响clip.speed。为啥呢?因为一个clip可以用到不同的组件上,创建不同的state

明白了,主要是这个clips能访问到,但是不能对它做哪些操作,这个是不知道的。
我可以自由对它进行操作,却会引发不确定的问题,对新手来说,有点门槛。

还想问一下,一个animation上前后2个state,管理同一个clip, 他们同时工作,会引发什么效果?
先创建一个state,然后remove它,接着再移除clip再加入clip创建一个新的state。
可能造成什么后果?

不会有什么副作用

2个state共同管理一个节点上的clip,前面的play,后面的state进行stop,这个会停还是会继续播放?

前面后面两个会是独立的。后面的状态因为之前并没有 play,所以它的 stop 不会有效果。因此一直都是前面的在播放。

如果两个状态同时播放,那么后面的会覆盖前面的。

验证这个时,又发现了一个新问题,animation.play跟state.play当有前后2个播放时,效果不一致的问题。

使用state来切换播放

this.stateOld.play();
this.scheduleOnce(()=>{this.stateNew.play()}, 5);

stateOld的持续时间很长大概20秒,5秒后播放stateNew的动画,持续1秒,播放完成后sateOld的动画继续播剩下的20-5-1=14秒。

如果换成animation.play来测试

this.anim.play(this.clip.name);
this.scheduleOnce(()=>{this.anim.play(this.clipB.name)}, 5);
第一个动画应该播放20秒,5秒后切换播放动画B(1秒时长),播放完后,
!!!没有继续播放第一个动画。

image
animation.play的解释是,切换到对应的animtionState,然后play,实际好像隐含了一个上一个播放的state.stop的操作。

个人觉得,animation.play的逻辑应该跟animationState.play的效果是完全一致的?

不一致。组件上的播放会先停掉目前正在播放的

是的,经过验证,state.play类似层级覆盖关系,后play的位于最上面,会覆盖前面play的state,但是前面state依然在进行.
当上层state(后play)结束后,对下面state的覆盖关系结束,下面state的play效果继续呈现出来。

确实很蛋疼,我记得之前是改播放的循环模式还是啥来着,藏得太深了,必须要先这样,再那样才搞定 :joy:

如果你需要播放、暂停、切换,就用 Animation.play 之类的方法。如果你想设置速度、循环模式、重复次数什么的,就获取到 AnimationState 然后在上面设置

1赞

明白了,谢谢!