首页 > 学院 > 开发设计 > 正文

NGUI源码剖析——动画(UITweener)

2019-11-09 18:41:24
字体:
来源:转载
供稿:网友

查NGUI动画方面的资料时,发现D.S.Qiu写的一篇UITweener源码剖析的blog,源码面前了无秘密,原文链接。另外,D.S.Qiu写过一系列NGUI源码的blog,值得每个使用NGUI的研读。

NGUI所见即所得之UITweener

        一直没有用过NGUI动画的功能,之前的理解就是:设置始末两个“位置”,然后就是从起始位置移到结束位置。至于中间是怎么变化的,就感觉很神奇了,变化率怎么设置才不会看起来很“傻”,这里不是看“郭靖”,动画一定要有惊奇,摸不着猜不透的感觉。对NGUI主要的几个脚本都已经有点掌握了(猛点查看),一直都没有去”膜拜“Tweening文件夹的各个大神,可能以前会觉得不就是一个动画组件,自己都可以实现。但是看过里面的代码就后悔了,因为至少结合TweenFOV和TweenOrhoSize这两个脚本就可以实现很多效果,竟然轻而易举的集成了,看来人还是不要太看得起自己的好,这样才会走的更快更远。

        每次都觉得前面吹水很写,也写不好(一直都有感觉自己的写作水平太差了),那就来看下Tweening文件夹下到底卖的是什么药——UITweener和它的“孩子”: 

UITweener的Fields        看着很复杂,其实只要把UITweener琢磨透了,其它都只是重写UITweener的OnUpdate方法和封装了Begin方法。还是先看下主要的Field(作用看注释):

C#代码  收藏代码       bool mStarted = false;  //是否开始动画  float mStartTime = 0f;   //动画开始播放的时间, mStarted =true;mStartTime = time + delay;  float mDuration = 0f;    //动画长度(时间)  float mAmountPerDelta = 1000f;   //单位时间动画播放的长度,有点帧率的感觉  float mFactor = 0f;           //当前动画播放的进度    /// <summary>  /// Amount advanced per delta time.  /// </summary>    public float amountPerDelta  {      get      {          if (mDuration != duration)          {              mDuration = duration;              mAmountPerDelta = Mathf.Abs((duration > 0f) ? 1f / duration : 1000f);          }          return mAmountPerDelta;      }  }  

        通过Begin设置需要的参数:

C#代码  收藏代码       static public T Begin<T> (GameObject go, float duration) where T : UITweener  {      T comp = go.GetComponent<T>();  if UNITY_Flash      if ((object)comp == null) comp = (T)go.AddComponent<T>();  else      if (comp == null) comp = go.AddComponent<T>();  endif      comp.mStarted = false;      comp.duration = duration;      comp.mFactor = 0f;      comp.mAmountPerDelta = Mathf.Abs(comp.mAmountPerDelta);      comp.style = Style.Once;      comp.animationCurve = new AnimationCurve(new Keyframe(0f, 0f, 0f, 1f), new Keyframe(1f, 1f, 1f, 0f));      comp.eventReceiver = null;      comp.callWhenFinished = null;      comp.enabled = true;      return comp;  }  

Update函数     

然后再Update函数先计算出时间delta,进一步计算出当前动画播放的mFactor,然后进行Sample采用,执行OnUpdate:

C#代码  收藏代码void Update ()  {      float delta = ignoreTimeScale ? RealTime.deltaTime : Time.deltaTime;      float time = ignoreTimeScale ? RealTime.time : Time.time;        if (!mStarted)      {          mStarted = true;          mStartTime = time + delay;      }        if (time < mStartTime) return;        // Advance the sampling factor      mFactor += amountPerDelta * delta;        // Loop style simply resets the play factor after it exceeds 1.      if (style == Style.Loop)      {          if (mFactor > 1f)          {              mFactor -= Mathf.Floor(mFactor);          }      }      else if (style == Style.PingPong)      {          // Ping-pong style reverses the direction          if (mFactor > 1f)          {              mFactor = 1f - (mFactor - Mathf.Floor(mFactor));              mAmountPerDelta = -mAmountPerDelta;          }          else if (mFactor < 0f)          {              mFactor = -mFactor;              mFactor -= Mathf.Floor(mFactor);              mAmountPerDelta = -mAmountPerDelta;          }      }        // If the factor goes out of range and this is a one-time tweening Operation, disable the script      if ((style == Style.Once) && (mFactor > 1f || mFactor < 0f))      {          mFactor = Mathf.Clamp01(mFactor);          Sample(mFactor, true);            current = this;            // Notify the listener delegates          EventDelegate.Execute(onFinished);            // DePRecated legacy functionality support          if (eventReceiver != null && !string.IsNullOrEmpty(callWhenFinished))              eventReceiver.SendMessage(callWhenFinished, this, SendMessageOptions.DontRequireReceiver);            current = null;            // Disable this script unless the function calls above changed something          if (mFactor == 1f && mAmountPerDelta > 0f || mFactor == 0f && mAmountPerDelta < 0f)              enabled = false;      }      else Sample(mFactor, false);  }  

 Sample采样函数

        前面说的动画要有摸不着猜不透的感觉,就是要考Sample的采样函数来实现的,UITweener支持5种动画曲线:

C#代码  收藏代码       public enum Method  {      Linear,      EaseIn,      EaSEOut,      EaseInOut,      BounceIn,      BounceOut,  }  

 采样的函数,原理很简单:根据当前播放的进度mFactor,计算出实际的动画播放刻度,然后执行OnUpdate操作:

C#代码  收藏代码public void Sample (float factor, bool isFinished)  {      // Calculate the sampling value      float val = Mathf.Clamp01(factor);        if (method == Method.EaseIn)      {          val = 1f - Mathf.Sin(0.5f * Mathf.PI * (1f - val));          if (steeperCurves) val *= val;      }      else if (method == Method.EaseOut)      {          val = Mathf.Sin(0.5f * Mathf.PI * val);            if (steeperCurves)          {              val = 1f - val;              val = 1f - val * val;          }      }      else if (method == Method.EaseInOut)      {          const float pi2 = Mathf.PI * 2f;          val = val - Mathf.Sin(val * pi2) / pi2;            if (steeperCurves)          {              val = val * 2f - 1f;              float sign = Mathf.Sign(val);              val = 1f - Mathf.Abs(val);              val = 1f - val * val;              val = sign * val * 0.5f + 0.5f;          }      }      else if (method == Method.BounceIn)      {          val = BounceLogic(val);      }      else if (method == Method.BounceOut)      {          val = 1f - BounceLogic(1f - val);      }        // Call the virtual update      OnUpdate((animationCurve != null) ? animationCurve.Evaluate(val) : val, isFinished);  }  

 缓动函数(easing fuction)

        上面说的动画曲线,中文叫缓动函数(曲线):        通过上面这张图可以很感性的认识不同函数的具体的效果,也可以自己尝试推导一边加深理解,不过D.S.Qiu已经有点“廉颇老矣”,凭着记忆“奇变偶不变,符号看象限”,慢的只能到easeInSine,要想详细了解可以参考②和③。

 

妙用mAmountPerDelta

         mAmountPerDelta就是动画播放速度,只对mAmountPerData就可以有更多控制:Toggle,PlayForward,PlayResverse:

C#代码  收藏代码/// <summary>  /// Manually activate the tweening process, reversing it if necessary.  /// </summary>    public void Play (bool forward)  {      mAmountPerDelta = Mathf.Abs(amountPerDelta);      if (!forward) mAmountPerDelta = -mAmountPerDelta;      enabled = true;      Update();  }  /// <summary>  /// Manually start the tweening process, reversing its direction.  /// </summary>    public void Toggle ()  {      if (mFactor > 0f)      {          mAmountPerDelta = -amountPerDelta;      }      else      {          mAmountPerDelta = Mathf.Abs(amountPerDelta);      }      enabled = true;  }  

 

『Bug修复和吐槽

        之前用TweenRotation这个脚本,做游戏等待转圈等待界面,发现总是不能旋转360度,总是一个小于180的角度,无论from和to如何设置:

C#代码  收藏代码public Vector3 from;  public Vector3 to;  

 后来无奈之下,只好去看下TweenRotation的OnUpdate函数,发现使用的是 Quaternion.Slerp这个函数,发现确实是这样,所以就做了下面的修改:

C#代码  收藏代码protected override void OnUpdate (float factor, bool isFinished)  {      //cachedTransform.localRotation = Quaternion.Slerp(Quaternion.Euler(from), Quaternion.Euler(to), factor);      //NGUI的实现是上一行,有Bug,不能达到要求      cachedTransform.localEulerAngles = Vector3.Lerp(from, to, factor);    }  

 

       NGUI提供了UIPlayTween,这个脚本管理一组Tween脚本的Play,提供了不同的Tirgger,然后在不同的事件函数中触发Play(true):

C#代码  收藏代码void OnClick ()  {      if (enabled && trigger == Trigger.OnClick)      {          Play(true);      }  }  

       虽然UIPlayTween提供了很多参数都是没有满足要其交替地进行PlayForward和PlayReverse,因为其都是执行Play(true),开始的时候我想到了给其中一个UITween添加OnFinished委托,在播放结束的时候改变playDiretion的方向:

C#代码  收藏代码public Direction playDirection = Direction.Forward;  

       但是这个控制因为动画是有时间的,会有问题。所以我只能添加一个Trigger的类型:Trigger.None,然后自己去调用,就是自己管理动画的播放,而不是在OnClick中触发。』

                                                                                           增补于 2013,12,23  21:50

小结:

       确实很简单,主要是对缓动函数的理解,有了这些基础可以做的事情(特效和动画)就很多了——屏幕抖动和刀光剑影(下次自己动手尝试下,哈哈),NGUI的ButtonScale等脚本也是通过UITweener来完成的。但是收获蛮多的,又一点多了,晚安!

参考:

①NGUI: Next-Gen UI kit  3.0.0:http://www.tasharen.com/ngui/docs/class_u_i_tweener.html

② 缓动函数:http://easings.net/zh-cn

③Easing Equations by Robbert Penner: http://www.gizma.com/easing/#sin2

④Unity3dPack: http://www.unity3dpack.com/?p=300


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表