先给出素材:
Tip Panel:
动画状态机:
用于控制Tip Panel的动画,通过将Exit变量设置为True时执行Tip Panel的退出动画。
ClosePanel()代码:
某用于统一管理Panel的管理类中统一关闭Panel的方法。通过传入参数name(要关闭的Panel的名字),先调用对应panel的OnClosing方法执行一些逻辑后才Destroy掉panel的实例。
///
/// close a panel.
///
/// type string name, panel's name.
public void ClosePanel(string name)
{
//...
panel.OnClosing ();
GameObject.Destroy (panel.gameObject);
}
OnClosing()代码:
panel关闭前执行的逻辑在这里实现。
其中,exit2Hash是状态Exit变量的hash值。
public void OnClosing ()
{
animator.SetBool (exit2Hash, true);
Debug.Log (animator.GetBool (exit2Hash).ToString ());
Debug.Log (animator.GetCurrentAnimatorClipInfo (0) [0].clip.name);
}
OK按钮的点击事件是动态添加的:
okBtn.onClick.AddListener (OnOKButtonClick);
OnOKButtonClick()代码:
注意,ClosePanel方法和其他方法(如OnClosing、OnOKButtonClick以及ok安寝的动态添加点击事件都在同一个Panel类中)不在同一个类中。
void OnOKButtonClick()
{
XXX.ClosePanel(gameObject.name);
}
在实现如下流程时,碰到了问题:
点击ok按钮 -> 执行Panel关闭动画 -> Destroy Panel对象
问题1:
动画还未执行完毕,Panel便被Destroy掉了。
解决方案一:
通过给tippanel_exit_clip动画添加事件,在tippanel_exit_clip动画播放解释后,触发指定事件Destroy掉panel实例,但这样显然就破坏掉了ClosePanel本身的意义。
解决方案二:
在ClosePanel方法中的Destroy方法添加延迟参数,等待一段时间再Destroy掉panel实例。
GameObject.Destroy (panel.gameObject, 1f);
缺点在于不能够根据动画具体结束时间再去Destroy,如果panel的Exit动画的执行时间超过1f,仍然会出现动画未执行完毕便被Destroy的现象。
尝试:
判断tippanel_exit_clip是否播放完毕,播放完毕后再执行后续代码。
有问题!animator.SetBool(exit2Hash, true)执行后,状态变量Exit的值的确是变为了True,但是实际上,animator并没有转换到tippanel_exit_clip,animator此时的clip仍然是tippanel_enter_clip。这就很迷了。。。
修改OnClosing代码如下:
public void OnClosing ()
{
base.OnClosing ();
animator.SetBool (exit2Hash, true);
Debug.Log (animator.GetBool (exit2Hash).ToString ());
Debug.Log (animator.GetCurrentAnimatorClipInfo (0) [0].clip.name);
}
这里显现出一个问题,动画过渡。
Unity3d中Animator进行动画切换时会有一个过渡阶段,也就是Transition,在完成Transition之前,状态机的State会一直保持原State不变直到完成Transition后才会改变State,这也就是为什么我们看到Exit的值改变后,状态机对应的State并没有转换到对应State的原因了。
解决这个问题,我选择通过设置Transition的Transition Duration来处理。
参考文章hongfei233: Unity3D——Mecanim动画 AnimatorTransitionInfo和AnimatorStateInfo 在角色移动和待机平滑切换中的应用
问题:
即使把Transition Duration的值设置为0,调试结果仍然为
嗯。。。很迷。。。虽然修改了TransitionDuration的值,点击按钮后,响应tippanel_exit_clip动画执行的速度变快了,但是debug的值仍然没有变,这样便不能通过检测animator当前的状态来实现等待动画执行完毕再执行Destroy方法了。。。
听说使用animator.Play()可以立即切换clip,兴奋的我赶紧去试了一下结果…
Debug:
说明一下:exit2Hash = Animator.StringToHash(“tippanel_exit_clip”);
。。没错,视觉上动画的确转换了,然而为何debug出来的信息仍然显示为tippanel_enter_clip。。。。。(被困扰的第二天)
结果:
Unity3d在状态切换时,会进行两个状态的融合也就是动画过渡,而Transition就是这个混合过程,只有当Transition完成混合后,才会真正的切换到另一个状态。即使Animator.Play可以忽略混合过程直接切换状态,但事实上,当前帧是检测不出来的,只有在下一帧才能检测到状态切换为另一个状态。
最后选择的处理方法:设置一个bool变量来标记状态,使用协程判断当前的动画是否为tippanel_exit_cip且播放完毕,达到条件则改变标记状态。在Update中检测标记变量的值来决定下一步是做其他还是Destroy掉Panel。
代码如下:
// XXX CLASS
public void ClosePanel(string name)
{
//...
panel.OnClosing ();
GameObject.Destroy (panel.gameObject);
}
//======================================
private int exit2Hash = Animator.StringToHash("tippanel_exit_clip");
private bool isFinished = false;
//...
void Update()
{
if(isFinished)
{
XXX.ClosePanel();
}
}
public void OnClosing ()
{
animator.SetBool (exit2Hash, true);
StartCoroutine (PlayExitAnimation ());
}
void OnOKButtonClick()
{
OnClosing();
}
IEnumerator PlayExitAnimation()
{
while (true) {
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo (0);
if (stateInfo.IsName ("tippanel_exit_clip") && stateInfo.normalizedTime >= 1.0f) {
break;
}
yield return null;
}
isFinished = true;
}